Skip to content
Snippets Groups Projects
Commit f6b218a4 authored by Jared Hancock's avatar Jared Hancock
Browse files

draft: Migrate Draft to use ORM

parent 8076a7f7
Branches
Tags
No related merge requests found
...@@ -7,29 +7,24 @@ require_once(INCLUDE_DIR.'class.draft.php'); ...@@ -7,29 +7,24 @@ require_once(INCLUDE_DIR.'class.draft.php');
class DraftAjaxAPI extends AjaxController { class DraftAjaxAPI extends AjaxController {
function _createDraft($vars) { function _createDraft($vars) {
$field_list = array('response', 'note', 'answer', 'body', if (!isset($vars['body'])) {
'message', 'issue'); $field_list = array('response', 'note', 'answer', 'body',
foreach ($field_list as $field) { 'message', 'issue');
if (isset($_POST[$field])) { foreach ($field_list as $field) {
$vars['body'] = urldecode($_POST[$field]); if (isset($_POST[$field])) {
break; $vars['body'] = $_POST[$field];
break;
}
} }
} }
if (!isset($vars['body'])) if (!isset($vars['body']) || !$vars['body'])
return Http::response(422, "Draft body not found in request"); return JsonDataEncoder::encode(array(
'error' => __("Draft body not found in request"),
'code' => 422,
));
$errors = array(); if (!($draft = Draft::create($vars)) || !$draft->save())
if (!($draft = Draft::create($vars, $errors))) Http::response(500, 'Unable to create draft');
Http::response(500, print_r($errors, true));
// If the draft is created from an existing document, ensure inline
// attachments from the cloned document are attachned to the draft
// XXX: Actually, I think this is just wasting time, because the
// other object already has the items attached, so the database
// won't clean up the files. They don't have to be attached to
// the draft for Draft::getAttachmentIds to return the id of the
// attached file
//$draft->syncExistingAttachments();
echo JsonDataEncoder::encode(array( echo JsonDataEncoder::encode(array(
'draft_id' => $draft->getId(), 'draft_id' => $draft->getId(),
...@@ -232,6 +227,9 @@ class DraftAjaxAPI extends AjaxController { ...@@ -232,6 +227,9 @@ class DraftAjaxAPI extends AjaxController {
'namespace' => $namespace, 'namespace' => $namespace,
); );
if (isset($_POST['name']))
$vars['body'] = $_POST[$_POST['name']];
return self::_createDraft($vars); return self::_createDraft($vars);
} }
......
<?php <?php
class Draft { /**
* Class: Draft
var $id; *
var $ht; * Defines a simple draft-saving mechanism for osTicket which supports draft
* fetch and update via an ajax mechanism (include/ajax.draft.php).
var $_attachments; *
* Fields:
function Draft($id) { * id - (int:auto:pk) Draft ID number
$this->id = $id; * body - (text) Body of the draft
$this->load(); * namespace - (string) Identifier of draft grouping — useful for multiple
} * drafts on the same document by different users
* staff_id - (int:null) Staff owner of the draft
function load() { * extra - (text:json) Extra attributes of the draft
$this->attachments = new GenericAttachments($this->id, 'D'); * created - (date) Date draft was initially created
$sql = 'SELECT * FROM '.DRAFT_TABLE.' WHERE id='.db_input($this->id); * updated - (date:null) Date draft was last updated
return (($res = db_query($sql)) */
&& ($this->ht = db_fetch_array($res))); class Draft extends VerySimpleModel {
static $meta = array(
'table' => DRAFT_TABLE,
'pk' => array('id'),
);
var $attachments;
function __construct() {
call_user_func_array(array('parent', '__construct'), func_get_args());
if (isset($this->id))
$this->attachments = new GenericAttachments($this->id, 'D');
} }
function getId() { return $this->id; } function getId() { return $this->id; }
function getBody() { return $this->ht['body']; } function getBody() { return $this->body; }
function getStaffId() { return $this->ht['staff_id']; } function getStaffId() { return $this->staff_id; }
function getNamespace() { return $this->ht['namespace']; } function getNamespace() { return $this->namespace; }
static function getDraftAndDataAttrs($namespace, $id=0, $original='') {
$draft_body = null;
$attrs = array(sprintf('data-draft-namespace="%s"', $namespace));
$criteria = array('namespace'=>$namespace);
if ($id) {
$attrs[] = sprintf('data-draft-object-id="%s"', $id);
$criteria['namespace'] .= '.' . $id;
}
if ($draft = static::lookup($criteria)) {
$attrs[] = sprintf('data-draft-id="%s"', $draft->getId());
$draft_body = $draft->getBody();
}
$attrs[] = sprintf('data-draft-original="%s"', Format::htmlchars($original));
return array($draft_body, implode(' ', $attrs));
}
function getAttachmentIds($body=false) { function getAttachmentIds($body=false) {
$attachments = array(); $attachments = array();
...@@ -63,69 +92,49 @@ class Draft { ...@@ -63,69 +92,49 @@ class Draft {
function setBody($body) { function setBody($body) {
// Change image.php urls back to content-id's // Change image.php urls back to content-id's
$body = Format::sanitize($body, false); $body = Format::sanitize($body, false);
$this->ht['body'] = $body;
$sql='UPDATE '.DRAFT_TABLE.' SET updated=NOW()' $this->body = $body;
.',body='.db_input($body) $this->updated = SqlFunction::NOW();
.' WHERE id='.db_input($this->getId()); return $this->save();
return db_query($sql) && db_affected_rows() == 1;
} }
function delete() { function delete() {
$this->attachments->deleteAll(); $this->attachments->deleteAll();
$sql = 'DELETE FROM '.DRAFT_TABLE return parent::delete();
.' WHERE id='.db_input($this->getId());
return (db_query($sql) && db_affected_rows() == 1);
} }
function save($id, $vars, &$errors) { function isValid() {
// Required fields // Required fields
if (!$vars['namespace'] || !isset($vars['body']) || !isset($vars['staff_id'])) return $this->namespace && isset($this->body) && isset($this->staff_id);
}
function save($refetch=false) {
if (!$this->isValid())
return false; return false;
$sql = ' SET `namespace`='.db_input($vars['namespace']) return parent::save($refetch);
.' ,body='.db_input(Format::sanitize($vars['body'], false)) }
.' ,staff_id='.db_input($vars['staff_id']);
if (!$id) { static function create($vars) {
$sql = 'INSERT INTO '.DRAFT_TABLE.$sql $attachments = @$vars['attachments'];
.' ,created=NOW()'; unset($vars['attachments']);
if(!db_query($sql) || !($draft=self::lookup(db_insert_id())))
return false;
// Cloned attachments... $vars['created'] = SqlFunction::NOW();
if($vars['attachments'] && is_array($vars['attachments'])) $draft = parent::create($vars);
$draft->attachments->upload($vars['attachments'], true);
return $draft; // Cloned attachments ...
} if (false && $attachments && is_array($attachments))
else { // XXX: This won't work until the draft is saved
$sql = 'UPDATE '.DRAFT_TABLE.$sql $draft->attachments->upload($attachments, true);
.' WHERE id='.db_input($id);
if (db_query($sql) && db_affected_rows() == 1)
return $this;
}
}
function create($vars, &$errors) {
return self::save(0, $vars, $errors);
}
function lookup($id) { return $draft;
return ($id && is_numeric($id)
&& ($d = new Draft($id))
&& $d->getId()==$id
) ? $d : null;
} }
function findByNamespaceAndStaff($namespace, $staff_id) { static function lookupByNamespaceAndStaff($namespace, $staff_id) {
$sql = 'SELECT id FROM '.DRAFT_TABLE return static::lookup(array(
.' WHERE `namespace`='.db_input($namespace) 'namespace'=>$namespace,
.' AND staff_id='.db_input($staff_id); 'staff_id'=>$staff_id
if (($res = db_query($sql)) && (list($id) = db_fetch_row($res))) ));
return $id;
else
return false;
} }
/** /**
......
...@@ -21,10 +21,17 @@ RedactorPlugins.draft = { ...@@ -21,10 +21,17 @@ RedactorPlugins.draft = {
var autosave_url = 'ajax.php/draft/' + this.opts.draftNamespace; var autosave_url = 'ajax.php/draft/' + this.opts.draftNamespace;
if (this.opts.draftObjectId) if (this.opts.draftObjectId)
autosave_url += '.' + this.opts.draftObjectId; autosave_url += '.' + this.opts.draftObjectId;
this.opts.autosave = autosave_url; this.opts.autosave = this.opts.autoCreateUrl = autosave_url;
this.opts.autosaveInterval = 10; this.opts.autosaveInterval = 10;
this.opts.autosaveCallback = this.setupDraftUpdate; this.opts.autosaveCallback = this.afterUpdateDraft;
this.opts.initCallback = this.recoverDraft; this.opts.autosaveErrorCallback = this.autosaveFailed;
if (this.opts.draftId) {
this.opts.autosave = 'ajax.php/draft/'+this.opts.draftId;
this.opts.clipboardUploadUrl =
this.opts.imageUpload =
'ajax.php/draft/'+this.opts.draftId+'/attach';
}
this.opts.imageUploadErrorCallback = this.displayError;
this.$draft_saved = $('<span>') this.$draft_saved = $('<span>')
.addClass("pull-right draft-saved") .addClass("pull-right draft-saved")
...@@ -33,73 +40,55 @@ RedactorPlugins.draft = { ...@@ -33,73 +40,55 @@ RedactorPlugins.draft = {
.text(__('Draft Saved'))); .text(__('Draft Saved')));
// Float the [Draft Saved] box with the toolbar // Float the [Draft Saved] box with the toolbar
this.$toolbar.append(this.$draft_saved); this.$toolbar.append(this.$draft_saved);
// Add [Delete Draft] button to the toolbar
if (this.opts.draftDelete) { if (this.opts.draftDelete) {
var trash = this.buttonAdd('deleteDraft', __('Delete Draft'), this.deleteDraft); var trash = this.draftDeleteButton =
this.buttonAdd('deleteDraft', __('Delete Draft'),
this.deleteDraft);
this.buttonAwesome('deleteDraft', 'icon-trash'); this.buttonAwesome('deleteDraft', 'icon-trash');
trash.parent().addClass('pull-right'); trash.parent().addClass('pull-right');
trash.addClass('delete-draft'); trash.addClass('delete-draft');
if (!this.opts.draftId)
trash.hide();
} }
}, },
recoverDraft: function() { afterUpdateDraft: function(data) {
var self = this;
$.ajax(this.opts.autosave, {
dataType: 'json',
statusCode: {
200: function(json) {
self.draft_id = json.draft_id;
// Replace the current content with the draft, sync, and make
// images editable
self.setupDraftUpdate(json);
if (!json.body) return;
self.set(json.body, false);
self.observeStart();
},
205: function() {
// Save empty draft immediately;
var ai = self.opts.autosaveInterval;
// Save immediately -- capture the created autosave
// interval and clear it as soon as possible. Note that
// autosave()ing doesn't happen immediately. It happens
// async after the autosaveInterval expires.
self.opts.autosaveInterval = 0;
self.autosave();
var interval = self.autosaveInterval;
setTimeout(function() {
clearInterval(interval);
}, 1);
// Reinstate previous autosave interval timing
self.opts.autosaveInterval = ai;
}
}
});
},
setupDraftUpdate: function(data) {
if (this.get())
this.$draft_saved.show().delay(5000).fadeOut();
// Slight workaround. Signal the 'keyup' event normally signaled // Slight workaround. Signal the 'keyup' event normally signaled
// from typing in the <textarea> // from typing in the <textarea>
if ($.autoLock && this.opts.draftNamespace == 'ticket.response') if ($.autoLock && this.opts.draftNamespace == 'ticket.response') {
if (this.get()) if (this.get())
$.autoLock.handleEvent(); $.autoLock.handleEvent();
}
if (typeof data != 'object') // If the draft was created, a draft_id will be sent back — update
data = $.parseJSON(data); // the URL to send updates in the future
if (data.draft_id) {
this.opts.draftId = data.draft_id;
this.opts.autosave = 'ajax.php/draft/' + data.draft_id;
}
if (!data || !data.draft_id) // Only show the [Draft Saved] notice if there is content in the
// field that has been touched
if (this.opts.draftOriginal && this.opts.draftOriginal == this.get()) {
// No change yet — dont't show the button
return;
}
if (data) {
this.$draft_saved.show().delay(5000).fadeOut();
}
// Show the button if there is a draft to delete
if (this.opts.draftId && this.opts.draftDelete)
this.draftDeleteButton.show();
},
autosaveFailed: function(error) {
if (error.code == 422)
// Unprocessable request (Empty message)
return; return;
$('input[name=draft_id]', this.$box.closest('form')) this.displayError(error);
.val(data.draft_id); // Cancel autosave
this.draft_id = data.draft_id; clearInterval(this.autosaveInterval);
this.opts.clipboardUploadUrl = this.hideDraftSaved();
this.opts.imageUpload =
'ajax.php/draft/'+data.draft_id+'/attach';
this.opts.imageUploadErrorCallback = this.displayError;
this.opts.original_autosave = this.opts.autosave;
this.opts.autosave = 'ajax.php/draft/'+data.draft_id;
}, },
displayError: function(json) { displayError: function(json) {
...@@ -111,18 +100,19 @@ RedactorPlugins.draft = { ...@@ -111,18 +100,19 @@ RedactorPlugins.draft = {
}, },
deleteDraft: function() { deleteDraft: function() {
if (!this.draft_id) if (!this.opts.draftId)
// Nothing to delete // Nothing to delete
return; return;
var self = this; var self = this;
$.ajax('ajax.php/draft/'+this.draft_id, { $.ajax('ajax.php/draft/'+this.opts.draftId, {
type: 'delete', type: 'delete',
async: false, async: false,
success: function() { success: function() {
self.draft_id = undefined; self.draft_id = undefined;
self.hideDraftSaved(); self.hideDraftSaved();
self.set('', false, false); self.set(self.opts.draftOriginal || '', false, false);
self.opts.autosave = self.opts.original_autosave; self.opts.autosave = self.opts.autoCreateUrl;
self.draftDeleteButton.hide();
} }
}); });
} }
...@@ -141,10 +131,10 @@ RedactorPlugins.signature = { ...@@ -141,10 +131,10 @@ RedactorPlugins.signature = {
else else
this.$signatureBox.hide(); this.$signatureBox.hide();
$('input[name='+$el.data('signatureField')+']', $el.closest('form')) $('input[name='+$el.data('signatureField')+']', $el.closest('form'))
.on('change', false, false, $.proxy(this.updateSignature, this)) .on('change', false, false, $.proxy(this.updateSignature, this));
if ($el.data('deptField')) if ($el.data('deptField'))
$(':input[name='+$el.data('deptField')+']', $el.closest('form')) $(':input[name='+$el.data('deptField')+']', $el.closest('form'))
.on('change', false, false, $.proxy(this.updateSignature, this)) .on('change', false, false, $.proxy(this.updateSignature, this));
// Expand on hover // Expand on hover
var outer = this.$signatureBox, var outer = this.$signatureBox,
inner = $('.inner', this.$signatureBox).get(0), inner = $('.inner', this.$signatureBox).get(0),
...@@ -181,14 +171,14 @@ RedactorPlugins.signature = { ...@@ -181,14 +171,14 @@ RedactorPlugins.signature = {
url += 'dept/' + $el.data('deptId'); url += 'dept/' + $el.data('deptId');
else if (selected == 'dept' && $el.data('deptField')) { else if (selected == 'dept' && $el.data('deptField')) {
if (dept) if (dept)
url += 'dept/' + dept url += 'dept/' + dept;
else else
return inner.empty().parent().hide(); return inner.empty().parent().hide();
} }
else if (type == 'none') else if (type == 'none')
return inner.empty().parent().hide(); return inner.empty().parent().hide();
else else
url += selected url += selected;
inner.load(url).parent().show(); inner.load(url).parent().show();
} }
...@@ -206,10 +196,6 @@ $(function() { ...@@ -206,10 +196,6 @@ $(function() {
.attr('height',img.clientHeight); .attr('height',img.clientHeight);
html = html.replace(before, img.outerHTML); html = html.replace(before, img.outerHTML);
}); });
// Drop <inline> elements if found in the text (shady mojo happening
// inside the Redactor editor)
// DELME: When this is fixed upstream in Redactor
html = html.replace(/<inline /, '<span ').replace(/<\/inline>/, '</span>');
return html; return html;
}, },
redact = $.redact = function(el, options) { redact = $.redact = function(el, options) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment