diff --git a/include/api.tickets.php b/include/api.tickets.php index d6b04c98ccf126bfdaa7e952bacaf1aea3aa480c..32b7d6feb9ec544f015426b3790c71b61f7af815 100644 --- a/include/api.tickets.php +++ b/include/api.tickets.php @@ -80,7 +80,8 @@ class TicketApiController extends ApiController { } // Validate and save immediately try { - $file['id'] = $fileField->uploadAttachment($file); + $F = $fileField->uploadAttachment($file); + $file['id'] = $F->getId(); } catch (FileUploadError $ex) { $file['error'] = $file['name'] . ': ' . $ex->getMessage(); diff --git a/include/class.attachment.php b/include/class.attachment.php index 9126cff8c0c7f9f68b6071dae3c50dc09e38c7ca..bb208411157836127f7ef5f9622f2499c9d8b39e 100644 --- a/include/class.attachment.php +++ b/include/class.attachment.php @@ -16,36 +16,37 @@ require_once(INCLUDE_DIR.'class.ticket.php'); require_once(INCLUDE_DIR.'class.file.php'); -class Attachment { - var $id; - var $file_id; +class Attachment extends VerySimpleModel { + static $meta = array( + 'table' => ATTACHMENT_TABLE, + 'pk' => array('id'), + 'select_related' => array('file'), + 'joins' => array( + 'thread_entry' => array( + 'constraint' => array( + 'object_id' => 'ThreadEntry.id', + 'type' => "'H'", + ), + ), + 'file' => array( + 'constraint' => array( + 'file_id' => 'AttachmentFile.id', + ), + ), + ), + ); - var $ht; var $object; - function Attachment($id) { - - $sql = 'SELECT a.* FROM '.ATTACHMENT_TABLE.' a ' - . 'WHERE a.id='.db_input($id); - if (!($res=db_query($sql)) || !db_num_rows($res)) - return; - - $this->ht = db_fetch_array($res); - $this->file = $this->object = null; - } - function getId() { - return $this->ht['id']; + return $this->id; } function getFileId() { - return $this->ht['file_id']; + return $this->file_id; } function getFile() { - if(!$this->file && $this->getFileId()) - $this->file = AttachmentFile::lookup($this->getFileId()); - return $this->file; } @@ -66,44 +67,23 @@ class Attachment { return $this->object; } - static function getIdByFileHash($hash, $objectId=0) { - $sql='SELECT a.id FROM '.ATTACHMENT_TABLE.' a ' - .' INNER JOIN '.FILE_TABLE.' f ON(f.id=a.file_id) ' - .' WHERE f.`key`='.db_input($hash); + static function lookupByFileHash($hash, $objectId=0) { + $file = static::objects() + ->filter(array('file__key' => $hash)); + if ($objectId) - $sql.=' AND a.object_id='.db_input($objectId); + $file->filter(array('object_id' => $objectId)); - return db_result(db_query($sql)); + return $file->first(); } static function lookup($var, $objectId=0) { - - $id = is_numeric($var) ? $var : self::getIdByFileHash($var, - $objectId); - - return ($id - && is_numeric($id) - && ($attach = new Attachment($id, $objectId)) - && $attach->getId()==$id - ) ? $attach : null; + return is_numeric($var) + ? parent::lookup($var) + : static::lookupByFileHash($var, $objectId); } } -class AttachmentModel extends VerySimpleModel { - static $meta = array( - 'table' => ATTACHMENT_TABLE, - 'pk' => array('id'), - 'joins' => array( - 'thread' => array( - 'constraint' => array( - 'object_id' => 'ThreadEntryModel.id', - 'type' => "'H'", - ), - ), - ), - ); -} - class GenericAttachment extends VerySimpleModel { static $meta = array( 'table' => ATTACHMENT_TABLE, @@ -132,24 +112,26 @@ class GenericAttachments { $fileId = $file; elseif (is_array($file) && isset($file['id'])) $fileId = $file['id']; - elseif (!($fileId = AttachmentFile::upload($file))) + elseif ($F = AttachmentFile::upload($file)) + $fileId = $F->getId(); + else continue; $_inline = isset($file['inline']) ? $file['inline'] : $inline; - $sql ='INSERT INTO '.ATTACHMENT_TABLE - .' SET `type`='.db_input($this->getType()) - .',object_id='.db_input($this->getId()) - .',file_id='.db_input($fileId) - .',inline='.db_input($_inline ? 1 : 0); + $att = Attachment::create(array( + 'type' => $this->getType(), + 'object_id' => $this->getId(), + 'file_id' => $fileId, + 'inline' => $_inline ? 1 : 0, + )); if ($lang) - $sql .= ',lang='.db_input($lang); + $att->lang = $lang; // File may already be associated with the draft (in the // event it was deleted and re-added) - if (db_query($sql, function($errno) { return $errno != 1062; }) - || db_errno() == 1062) - $i[] = $fileId; + $att->save(); + $i[] = $fileId; } return $i; @@ -161,15 +143,18 @@ class GenericAttachments { $fileId = $file; elseif (is_array($file) && isset($file['id'])) $fileId = $file['id']; - elseif (!($fileId = AttachmentFile::save($file))) + elseif ($file = AttachmentFile::create($file)) + $fileId = $file->getId(); + else return false; - $sql ='INSERT INTO '.ATTACHMENT_TABLE - .' SET `type`='.db_input($this->getType()) - .',object_id='.db_input($this->getId()) - .',file_id='.db_input($fileId) - .',inline='.db_input($inline ? 1 : 0); - if (!db_query($sql) || !db_affected_rows()) + $att = Attachment::create(array( + 'type' => $this->getType(), + 'object_id' => $this->getId(), + 'file_id' => $fileId, + 'inline' => $inline ? 1 : 0, + )); + if (!$att->save()) return false; return $fileId; @@ -181,51 +166,29 @@ class GenericAttachments { function count($lang=false) { return count($this->getSeparates($lang)); } function _getList($separate=false, $inlines=false, $lang=false) { - if(!isset($this->attachments)) { - $this->attachments = array(); - $sql='SELECT f.id, f.size, f.`key`, f.signature, f.name ' - .', a.inline, a.lang, a.id as attach_id ' - .' FROM '.FILE_TABLE.' f ' - .' INNER JOIN '.ATTACHMENT_TABLE.' a ON(f.id=a.file_id) ' - .' WHERE a.`type`='.db_input($this->getType()) - .' AND a.object_id='.db_input($this->getId()); - if(($res=db_query($sql)) && db_num_rows($res)) { - while($rec=db_fetch_array($res)) { - $rec['download_url'] = AttachmentFile::generateDownloadUrl( - $rec['id'], $rec['key'], $rec['signature']); - $this->attachments[] = $rec; - } - } - } - $attachments = array(); - foreach ($this->attachments as $a) { - if (($a['inline'] != $separate || $a['inline'] == $inlines) - && $lang == $a['lang']) { - $a['file_id'] = $a['id']; - $a['hash'] = md5($a['file_id'].session_id().$a['key']); - $attachments[] = $a; - } - } - return $attachments; + return Attachment::objects()->filter(array( + 'type' => $this->getType(), + 'object_id' => $this->getId(), + )); } function delete($file_id) { - $deleted = 0; - $sql='DELETE FROM '.ATTACHMENT_TABLE - .' WHERE object_id='.db_input($this->getId()) - .' AND `type`='.db_input($this->getType()) - .' AND file_id='.db_input($file_id); - return db_query($sql) && db_affected_rows() > 0; + return Attachment::objects()->filter(array( + 'type' => $this->getType(), + 'object_id' => $this->getId(), + 'file_id' => $file_id, + ))->delete(); } function deleteAll($inline_only=false){ - $deleted=0; - $sql='DELETE FROM '.ATTACHMENT_TABLE - .' WHERE object_id='.db_input($this->getId()) - .' AND `type`='.db_input($this->getType()); + $objects = Attachment::objects()->filter(array( + 'type' => $this->getType(), + 'object_id' => $this->getId(), + )); if ($inline_only) - $sql .= ' AND inline = 1'; - return db_query($sql) && db_affected_rows() > 0; + $objects->filter(array('inline' => 1)); + + return $objects->delete(); } function deleteInlines() { diff --git a/include/class.config.php b/include/class.config.php index fc488ee4619d4e5fdc8edf5761f8253546e07a18..b8c35095396f5866b47e79b023338ce2289794a9 100644 --- a/include/class.config.php +++ b/include/class.config.php @@ -1111,7 +1111,7 @@ class OsticketConfig extends Config { ; // Pass elseif ($logo['error']) $errors['logo'] = $logo['error']; - elseif (!($id = AttachmentFile::uploadLogo($logo, $error))) + elseif (!AttachmentFile::uploadLogo($logo, $error)) $errors['logo'] = sprintf(__('Unable to upload logo image: %s'), $error); } diff --git a/include/class.draft.php b/include/class.draft.php index 49503ed7c66bcc367580ab55779cc109ad4be07f..659325ede98dd6281a83857f92d5b4f989f9df96 100644 --- a/include/class.draft.php +++ b/include/class.draft.php @@ -62,11 +62,13 @@ class Draft extends VerySimpleModel { $body = Format::localizeInlineImages($body); $matches = array(); if (preg_match_all('/"cid:([\\w.-]{32})"/', $body, $matches)) { - foreach ($matches[1] as $hash) { - if ($file_id = AttachmentFile::getIdByHash($hash)) - $attachments[] = array( - 'id' => $file_id, - 'inline' => true); + $files = AttachmentFile::objects() + ->filter(array('key__in' => $matches[1])); + foreach ($files as $F) { + $attachments[] = array( + 'id' => $F->getId(), + 'inline' => true + ); } } return $attachments; @@ -86,9 +88,12 @@ class Draft extends VerySimpleModel { // Purge current attachments $this->attachments->deleteInlines(); - foreach ($matches[1] as $hash) - if ($file = AttachmentFile::getIdByHash($hash)) - $this->attachments->upload($file, true); + foreach (AttachmentFile::objects() + ->filter(array('key__in' => $matches[1])) + as $F + ) { + $this->attachments->upload($F->getId(), true); + } } function setBody($body) { diff --git a/include/class.file.php b/include/class.file.php index 2c2ab0fc69a37f19c3cda28e49b2cfa22d7bd925..a540aebd2b986f4ebe8df6881303e8992f33f30c 100644 --- a/include/class.file.php +++ b/include/class.file.php @@ -14,43 +14,17 @@ require_once(INCLUDE_DIR.'class.signal.php'); require_once(INCLUDE_DIR.'class.error.php'); -class AttachmentFile { - - var $id; - var $ht; - - function AttachmentFile($id) { - $this->id =0; - return ($this->load($id)); - } - - function load($id=0) { - - if(!$id && !($id=$this->getId())) - return false; - - $sql='SELECT f.id, f.type, size, name, `key`, signature, ft, bk, f.created, ' - .' count(DISTINCT a.object_id) as canned, ' - .' count(DISTINCT t.id) as entries ' - .' FROM '.FILE_TABLE.' f ' - .' LEFT JOIN '.ATTACHMENT_TABLE.' a - ON(a.file_id=f.id) ' - .' LEFT JOIN '.ATTACHMENT_TABLE.' t - ON(t.file_id = f.id) ' - .' WHERE f.id='.db_input($id) - .' GROUP BY f.id'; - if(!($res=db_query($sql)) || !db_num_rows($res)) - return false; - - $this->ht=db_fetch_array($res); - $this->id =$this->ht['id']; - - return true; - } - - function reload() { - return $this->load(); - } +class AttachmentFile extends VerySimpleModel { + + static $meta = array( + 'table' => FILE_TABLE, + 'pk' => array('id'), + 'joins' => array( + 'attachments' => array( + 'reverse' => 'Attachment.file' + ), + ), + ); function getHashtable() { return $this->ht; @@ -61,15 +35,15 @@ class AttachmentFile { } function getNumEntries() { - return $this->ht['entries']; + return $this->attachments->count(); } function isCanned() { - return ($this->ht['canned']); + return $this->getNumEntries(); } function isInUse() { - return ($this->getNumEntries() || $this->isCanned()); + return $this->getNumEntries(); } function getId() { @@ -77,11 +51,11 @@ class AttachmentFile { } function getType() { - return $this->ht['type']; + return $this->type; } function getBackend() { - return $this->ht['bk']; + return $this->bk; } function getMime() { @@ -89,25 +63,23 @@ class AttachmentFile { } function getSize() { - return $this->ht['size']; + return $this->size; } function getName() { - return $this->ht['name']; + return $this->name; } function getKey() { - return $this->ht['key']; + return $this->key; } function getSignature() { - $sig = $this->ht['signature']; - if (!$sig) return $this->getKey(); - return $sig; + return $this->signature ?: $this->getKey(); } function lastModified() { - return $this->ht['created']; + return $this->created; } function open() { @@ -145,8 +117,7 @@ class AttachmentFile { function delete() { - $sql='DELETE FROM '.FILE_TABLE.' WHERE id='.db_input($this->getId()).' LIMIT 1'; - if(!db_query($sql) || !db_affected_rows()) + if (!parent::delete()) return false; if ($bk = $this->open()) @@ -298,7 +269,7 @@ class AttachmentFile { } /* Function assumes the files types have been validated */ - function upload($file, $ft='T') { + static function upload($file, $ft='T') { if(!$file['name'] || $file['error'] || !is_uploaded_file($file['tmp_name'])) return false; @@ -314,10 +285,10 @@ class AttachmentFile { 'tmp_name'=>$file['tmp_name'], ); - return AttachmentFile::save($info, $ft); + return static::create($info, $ft); } - function uploadLogo($file, &$error, $aspect_ratio=3) { + static function uploadLogo($file, &$error, $aspect_ratio=3) { /* Borrowed in part from * http://salman-w.blogspot.com/2009/04/crop-to-fit-image-using-aspphp.html */ @@ -348,8 +319,13 @@ class AttachmentFile { return false; } - function save(&$file, $ft='T') { - + static function create(&$file, $ft='T') { + if (isset($file['encoding'])) { + switch ($file['encoding']) { + case 'base64': + $file['data'] = base64_decode($file['data']); + } + } if (isset($file['data'])) { // Allow a callback function to delay or avoid reading or // fetching ihe file contents @@ -364,15 +340,18 @@ class AttachmentFile { if (isset($file['size'])) { // Check and see if the file is already on record - $sql = 'SELECT id, `key` FROM '.FILE_TABLE - .' WHERE signature='.db_input($file['signature']) - .' AND size='.db_input($file['size']); - - // If the record exists in the database already, a file with the - // same hash and size is already on file -- just return its ID - if (list($id, $key) = db_fetch_row(db_query($sql))) { - $file['key'] = $key; - return $id; + $existing = static::objects()->filter(array( + 'signature' => $file['signature'], + 'size' => $file['size'] + )) + ->first(); + + // If the record exists in the database already, a file with + // the same hash and size is already on file -- just return + // the file + if ($existing) { + $file['key'] = $existing->key; + return $existing; } } elseif (!isset($file['data'])) { @@ -393,20 +372,19 @@ class AttachmentFile { if (!$file['type']) $file['type'] = 'application/octet-stream'; - $sql='INSERT INTO '.FILE_TABLE.' SET created=NOW() ' - .',type='.db_input(strtolower($file['type'])) - .',name='.db_input($file['name']) - .',`key`='.db_input($file['key']) - .',ft='.db_input($ft ?: 'T') - .',signature='.db_input($file['signature']); - if (isset($file['size'])) - $sql .= ',size='.db_input($file['size']); + $f = parent::create(array( + 'type' => strtolower($file['type']), + 'name' => $file['name'], + 'key' => $file['key'], + 'ft' => $ft ?: 'T', + 'signature' => $file['signature'], + )); - if (!(db_query($sql) && ($id = db_insert_id()))) - return false; + if (isset($file['size'])) + $f->size = $file['size']; - if (!($f = AttachmentFile::lookup($id))) + if (!$f->save()) return false; // Note that this is preferred over $f->open() because the file does @@ -440,26 +418,22 @@ class AttachmentFile { return false; } - $sql = 'UPDATE '.FILE_TABLE.' SET bk='.db_input($bk->getBkChar()); + $f->bk = $bk->getBkChar(); if (!isset($file['size'])) { if ($size = $bk->getSize()) - $file['size'] = $size; + $f->size = $size; // Prefer mb_strlen, because mbstring.func_overload will // automatically prefer it if configured. elseif (extension_loaded('mbstring')) - $file['size'] = mb_strlen($file['data'], '8bit'); + $f->size = mb_strlen($file['data'], '8bit'); // bootstrap.php include a compat version of mb_strlen else - $file['size'] = strlen($file['data']); - - $sql .= ', `size`='.db_input($file['size']); + $f->size = strlen($file['data']); } - $sql .= ' WHERE id='.db_input($f->getId()); - db_query($sql); - - return $f->getId(); + $f->save(); + return $f; } /** @@ -523,10 +497,8 @@ class AttachmentFile { return false; } - $sql = 'UPDATE '.FILE_TABLE.' SET bk=' - .db_input($target->getBkChar()) - .' WHERE id='.db_input($this->getId()); - if (!db_query($sql) || db_affected_rows()!=1) + $this->bk = $target->getBkChar(); + if (!$this->save()) return false; return $source->unlink(); @@ -554,31 +526,14 @@ class AttachmentFile { return FileStorageBackend::lookup($char, $file); } - /* Static functions */ - function getIdByHash($hash) { - - $sql='SELECT id FROM '.FILE_TABLE.' WHERE `key`='.db_input($hash); - if(($res=db_query($sql)) && db_num_rows($res)) - list($id)=db_fetch_row($res); - - return $id; - } - - function lookup($id) { - - $id = is_numeric($id)?$id:AttachmentFile::getIdByHash($id); - - return ($id && ($file = new AttachmentFile($id)) && $file->getId()==$id)?$file:null; + static function lookupByHash($hash) { + return parent::lookup(array('key' => $hash)); } - static function create($info, &$errors) { - if (isset($info['encoding'])) { - switch ($info['encoding']) { - case 'base64': - $info['data'] = base64_decode($info['data']); - } - } - return self::save($info); + static function lookup($id) { + return is_numeric($id) + ? parent::lookup($id) + : static::lookupByHash($id); } /* @@ -620,35 +575,31 @@ class AttachmentFile { * Removes files and associated meta-data for files which no ticket, * canned-response, or faq point to any more. */ - /* static */ function deleteOrphans() { + static function deleteOrphans() { // XXX: Allow plugins to define filetypes which do not represent // files attached to tickets or other things in the attachment // table and are not logos - //FIXME: Just user straight up left join - $sql = 'SELECT id FROM '.FILE_TABLE.' WHERE id NOT IN (' - .'SELECT file_id FROM '.ATTACHMENT_TABLE - .") AND `ft` = 'T' AND TIMESTAMPDIFF(DAY, `created`, CURRENT_TIMESTAMP) > 1"; - - if (!($res = db_query($sql))) - return false; - - while (list($id) = db_fetch_row($res)) - if (($file = self::lookup($id)) && !$file->delete()) + $files = static::objects() + ->filter(array( + 'attachments__object_id__isnull' => true, + 'ft' => 'T', + 'created__gt' => new DateTime('now -1 day'), + )); + + foreach ($files as $f) { + if (!$f->delete()) break; + } return true; } /* static */ function allLogos() { - $sql = 'SELECT id FROM '.FILE_TABLE.' WHERE ft="L" - ORDER BY created'; - $logos = array(); - $res = db_query($sql); - while (list($id) = db_fetch_row($res)) - $logos[] = AttachmentFile::lookup($id); - return $logos; + return static::objects() + ->filter(array('ft' => 'L')) + ->order_by('created'); } } diff --git a/include/class.format.php b/include/class.format.php index 7c06ee1c464dd85db22afb8d2fabcf5f4a71e360..f922a6c5ac3141f505c4aee36818063974637fe6 100644 --- a/include/class.format.php +++ b/include/class.format.php @@ -410,10 +410,19 @@ class Format { function viewableImages($html, $script=false) { + $cids = $images = array(); + // Try and get information for all the files in one query + if (preg_match_all('/"cid:([\w._-]{32})"/', $html, $cids)) { + foreach (AttachmentFile::objects() + ->filter(array('key__in' => $cids[1])) + as $file + ) { + $images[strtolower($file->getKey())] = $file; + } + } return preg_replace_callback('/"cid:([\w._-]{32})"/', - function($match) use ($script) { - $hash = $match[1]; - if (!($file = AttachmentFile::lookup($hash))) + function($match) use ($script, $images) { + if (!($file = $images[strtolower($match[1])])) return $match[0]; return sprintf('"%s" data-cid="%s"', $file->getDownloadUrl(false, 'inline', $script), $match[1]); diff --git a/include/class.forms.php b/include/class.forms.php index dfccf962c5d1e307fec038fd028f400c889de122..5122ddd933bacf16c1bf3a91e46ca377067f48bd 100644 --- a/include/class.forms.php +++ b/include/class.forms.php @@ -1929,10 +1929,10 @@ class FileUploadField extends FormField { if (!$bypass && $file['size'] > $config['size']) Http::response(413, 'File is too large'); - if (!($id = AttachmentFile::upload($file))) + if (!($F = AttachmentFile::upload($file))) Http::response(500, 'Unable to store file: '. $file['error']); - return $id; + return $F->getId(); } /** @@ -1972,10 +1972,10 @@ class FileUploadField extends FormField { if ($file['size'] > $config['size']) throw new FileUploadError(__('File size is too large')); - if (!$id = AttachmentFile::save($file)) + if (!$F = AttachmentFile::create($file)) throw new FileUploadError(__('Unable to save file')); - return $id; + return $F; } function isValidFileType($name, $type=false) { @@ -2718,7 +2718,8 @@ class FileUploadWidget extends Widget { if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_FILES[$this->name])) { foreach (AttachmentFile::format($_FILES[$this->name]) as $file) { try { - $ids[] = $this->field->uploadFile($file); + $F = $this->field->uploadFile($file); + $ids[] = $F->getId(); } catch (FileUploadError $ex) {} } diff --git a/include/class.mailer.php b/include/class.mailer.php index a3093c203a14eac9f8e7a2082830f715352b2128..02b7f5b6860e284aa16a10f1d30d0e3b59ad055c 100644 --- a/include/class.mailer.php +++ b/include/class.mailer.php @@ -79,15 +79,25 @@ class Mailer { return $this->attachments; } - function addAttachment($attachment) { + function addAttachment(Attachment $attachment) { // XXX: This looks too assuming; however, the attachment processor // in the ::send() method seems hard coded to expect this format - $this->attachments[$attachment['file_id']] = $attachment; + $this->attachments[$attachment->file_id] = $attachment->file; + } + + function addFile(AttachmentFile $file) { + // XXX: This looks too assuming; however, the attachment processor + // in the ::send() method seems hard coded to expect this format + $this->attachments[$file->file_id] = $file; } function addAttachments($attachments) { - foreach ($attachments as $a) - $this->addAttachment($a); + foreach ($attachments as $a) { + if ($a instanceof Attachment) + $this->addAttachment($a); + elseif ($a instanceof AttachmentFile) + $this->addFile($a); + } } function send($to, $subject, $message, $options=null) { @@ -211,7 +221,14 @@ class Mailer { $self = $this; $message = preg_replace_callback('/cid:([\w.-]{32})/', function($match) use ($domain, $mime, $self) { - if (!($file = AttachmentFile::lookup($match[1]))) + $file = false; + foreach ($self->attachments as $id=>$F) { + if (strcasecmp($F->getKey(), $match[1]) === 0) { + $file = $F; + break; + } + } + if (!$file) return $match[0]; $mime->addHTMLImage($file->getData(), $file->getType(), $file->getName(), false, @@ -225,12 +242,9 @@ class Mailer { } //XXX: Attachments if(($attachments=$this->getAttachments())) { - foreach($attachments as $attachment) { - if ($attachment['file_id'] - && ($file=AttachmentFile::lookup($attachment['file_id']))) { - $mime->addAttachment($file->getData(), - $file->getType(), $file->getName(),false); - } + foreach($attachments as $id=>$file) { + $mime->addAttachment($file->getData(), + $file->getType(), $file->getName(),false); } } diff --git a/include/class.organization.php b/include/class.organization.php index fbaa19df50dd8328b900e2c235421a3148862c15..05b23ca1230eb089dc285f8344116a454f1ca0ac 100644 --- a/include/class.organization.php +++ b/include/class.organization.php @@ -370,6 +370,7 @@ class Organization extends OrganizationModel { $org->addDynamicData($vars); } + Signal::send('organization.created', $user); return $org; } diff --git a/include/class.orm.php b/include/class.orm.php index 17dd050357a5e1b55d24083e8cdea1a5aad59606..26ea31529dea4bb51e68094bee960dc2230cf6f4 100644 --- a/include/class.orm.php +++ b/include/class.orm.php @@ -256,8 +256,8 @@ class VerySimpleModel { // replaced in the dirty array if (!array_key_exists($field, $this->dirty)) $this->dirty[$field] = $old; - $this->ht[$field] = $value; } + $this->ht[$field] = $value; } function __set($field, $value) { return $this->set($field, $value); @@ -2102,6 +2102,10 @@ class MysqlExecutor { $types .= 'd'; elseif (is_string($p)) $types .= 's'; + elseif ($p instanceof DateTime) { + $types .= 's'; + $p = $p->format('Y-m-d h:i:s'); + } // TODO: Emit error if param is null $ps[] = &$p; } diff --git a/include/class.search.php b/include/class.search.php index 538aa7fddf51f48650c9a8ed114c28a4ddef3b23..5436ff519333b7ff3e578703fc13672cd194ed34 100644 --- a/include/class.search.php +++ b/include/class.search.php @@ -197,7 +197,12 @@ class SearchInterface { // Tickets, which can be edited as well // Knowledgebase articles (FAQ and canned responses) // Users, organizations - Signal::connect('model.created', array($this, 'createModel')); + Signal::connect('threadentry.created', array($this, 'createModel')); + Signal::connect('ticket.created', array($this, 'createModel')); + Signal::connect('user.created', array($this, 'createModel')); + Signal::connect('organization.created', array($this, 'createModel')); + Signal::connect('model.created', array($this, 'createModel'), 'FAQ'); + Signal::connect('model.updated', array($this, 'updateModel')); #Signal::connect('model.deleted', array($this, 'deleteModel')); } diff --git a/include/class.staff.php b/include/class.staff.php index 3201db170e8e1364d992e1d753ac2932dc1103eb..522a09f81b55b015d7352cae253fd212ab338335 100644 --- a/include/class.staff.php +++ b/include/class.staff.php @@ -640,7 +640,7 @@ implements AuthenticatedUser { return $row ? $row[0] : 0; } - function getIdByEmail($email) { + static function getIdByEmail($email) { $row = static::objects()->filter(array('email' => $email)) ->values_flat('staff_id')->first(); return $row ? $row[0] : 0; diff --git a/include/class.task.php b/include/class.task.php index 54da8170f882a43bc4d4935835be8ee505f051b6..610c0271fb39452354fe4eb5e2348fbcd6f82fe0 100644 --- a/include/class.task.php +++ b/include/class.task.php @@ -225,9 +225,11 @@ class Task extends TaskModel { return $this->getThread()->getEntry($id); } - function getThreadEntries($type, $order='') { - return $this->getThread()->getEntries( - array('type' => $type, 'order' => $order)); + function getThreadEntries($type=false) { + $thread = $this->getThread()->getEntries(); + if ($type && is_array($type)) + $thread->filter(array('type__in' => $type)); + return $thread; } function getForm() { @@ -437,7 +439,7 @@ class Task extends TaskModel { // Create a thread + message. $thread = TaskThread::create($task); $thread->addDescription($vars); - Signal::send('model.created', $task); + Signal::send('task.created', $task); return $task; } @@ -574,10 +576,12 @@ class TaskThread extends ObjectThread { static function create($task) { $id = is_object($task) ? $task->getId() : $task; - return parent::create(array( + $thread = parent::create(array( 'object_id' => $id, 'object_type' => ObjectModel::OBJECT_TYPE_TASK )); + if ($thread->save()) + return $thread; } } diff --git a/include/class.thread.php b/include/class.thread.php index 11a9ce9e5044251a82a56fd0fc533957955c1695..63d8fb6f12eedeea4f6667e04ffe4a0207d8c940 100644 --- a/include/class.thread.php +++ b/include/class.thread.php @@ -17,7 +17,8 @@ include_once(INCLUDE_DIR.'class.ticket.php'); include_once(INCLUDE_DIR.'class.draft.php'); -class ThreadModel extends VerySimpleModel { +//Ticket thread. +class Thread extends VerySimpleModel { static $meta = array( 'table' => THREAD_TABLE, 'pk' => array('id'), @@ -29,66 +30,23 @@ class ThreadModel extends VerySimpleModel { ), ), 'entries' => array( - 'reverse' => 'ThreadEntryModel.thread', + 'reverse' => 'ThreadEntry.thread', ), ), ); -} - -//Ticket thread. -class Thread { - - var $ht; - - function Thread($criteria) { - $this->load($criteria); - } - - function load($criteria=null) { - - if (!$criteria && !($criteria=$this->getId())) - return null; - - $sql='SELECT thread.* ' - .' ,count(DISTINCT a.id) as attachments ' - .' ,count(DISTINCT entry.id) as entries ' - .' FROM '.THREAD_TABLE.' thread ' - .' LEFT JOIN '.THREAD_ENTRY_TABLE.' entry - ON (entry.thread_id = thread.id) ' - .' LEFT JOIN '.ATTACHMENT_TABLE.' a - ON (a.object_id=entry.id AND a.`type` = "H") '; - - if (is_numeric($criteria)) - $sql.= ' WHERE thread.id='.db_input($criteria); - else - $sql.= sprintf(' WHERE thread.object_id=%d AND - thread.object_type=%s', - $criteria['object_id'], - db_input($criteria['object_type'])); - - $sql.= ' GROUP BY thread.id'; - - $this->ht = array(); - if (($res=db_query($sql)) && db_num_rows($res)) - $this->ht = db_fetch_array($res); - - return ($this->ht); - } - function reload() { - return $this->load(); - } + var $_object; function getId() { - return $this->ht['id']; + return $this->id; } function getObjectId() { - return $this->ht['object_id']; + return $this->object_id; } function getObjectType() { - return $this->ht['object_type']; + return $this->object_type; } function getObject() { @@ -101,54 +59,22 @@ class Thread { } function getNumAttachments() { - return $this->ht['attachments']; + return Attachment::objects()->filter(array( + 'thread_entry__thread' => $this + ))->count(); } function getNumEntries() { - return $this->ht['entries']; - } - - function getEntries($criteria) { - - if (!$criteria['order'] || !in_array($criteria['order'], array('DESC','ASC'))) - $criteria['order'] = 'ASC'; - - $sql='SELECT entry.* - , COALESCE(user.name, - IF(staff.staff_id, - CONCAT_WS(" ", staff.firstname, staff.lastname), - NULL)) as name ' - .' ,count(DISTINCT attach.id) as attachments ' - .' FROM '.THREAD_ENTRY_TABLE.' entry ' - .' LEFT JOIN '.USER_TABLE.' user - ON (entry.user_id=user.id) ' - .' LEFT JOIN '.STAFF_TABLE.' staff - ON (entry.staff_id=staff.staff_id) ' - .' LEFT JOIN '.ATTACHMENT_TABLE.' attach - ON (attach.object_id = entry.id AND attach.`type`="H") ' - .' WHERE entry.thread_id='.db_input($this->getId()); - - if ($criteria['type'] && is_array($criteria['type'])) - $sql.=' AND entry.`type` IN (' - .implode(',', db_input($criteria['type'])).')'; - elseif ($criteria['type']) - $sql.=' AND entry.`type` = '.db_input($criteria['type']); - - $sql.=' GROUP BY entry.id ' - .' ORDER BY entry.created '.$criteria['order']; - - if ($criteria['limit']) - $sql.=' LIMIT '.$criteria['limit']; - - $entries = array(); - if(($res=db_query($sql)) && db_num_rows($res)) { - while($rec=db_fetch_array($res)) { - $rec['body'] = ThreadEntryBody::fromFormattedText($rec['body'], $rec['format']); - $entries[] = $rec; - } - } + return $this->entries->count(); + } - return $entries; + function getEntries($criteria=false) { + $base = $this->entries->annotate(array( + 'has_attachments' => SqlAggregate::COUNT('attachments') + )); + if ($criteria) + $base->filter($criteria); + return $base; } function getEntry($id) { @@ -261,7 +187,7 @@ class Thread { if ($object instanceof Threadable) return $object->postNote($vars, $errors); elseif ($this instanceof ObjectThread) - $this->addNote($vars, $errors); + return $this->addNote($vars, $errors); else throw new Exception('Cannot continue discussion with abstract thread'); } @@ -278,7 +204,7 @@ class Thread { if ($object instanceof Threadable) return $object->postNote($vars, $errors); elseif ($this instanceof ObjectThread) - $this->addNote($vars, $errors); + return $this->addNote($vars, $errors); else throw new Exception('Cannot continue discussion with abstract thread'); } @@ -287,14 +213,15 @@ class Thread { else { //XXX: Are we potentially leaking the email address to // collaborators? - $vars['message'] = sprintf("Received From: %s\n\n%s", - $mailinfo['email'], $body); + // Try not to destroy the format of the body + $body->prepend(sprintf('Received From: %s', $mailinfo['email'])); + $vars['message'] = $body; $vars['userId'] = 0; //Unknown user! //XXX: Assume ticket owner? $vars['origin'] = 'Email'; if ($object instanceof Threadable) return $object->postMessage($vars, $errors); elseif ($this instanceof ObjectThread) - $this->addMessage($vars, $errors); + return $this->addMessage($vars, $errors); else throw new Exception('Cannot continue discussion with abstract thread'); } @@ -304,15 +231,11 @@ class Thread { } function deleteAttachments() { + $deleted = Attachment::objects()->filter(array( + 'thread_entry__thread' => $this, + ))->delete(); - // Clear reference table - $sql = 'DELETE `a`.* FROM '.ATTACHMENT_TABLE. ' `a` ' - . 'INNER JOIN '.THREAD_ENTRY_TABLE.' `e` - ON(`e`.id = `a`.object_id AND `a`.`type`= "H") ' - . ' WHERE `e`.thread_id='.db_input($this->getId()); - - $deleted=0; - if (($res=db_query($sql)) && ($deleted=db_affected_rows())) + if ($deleted) AttachmentFile::deleteOrphans(); return $deleted; @@ -321,114 +244,80 @@ class Thread { function delete() { //Self delete - $sql = 'DELETE FROM '.THREAD_TABLE.' WHERE - id='.db_input($this->getId()); - - if (!db_query($sql) || !db_affected_rows()) + if (!parent::delete()) return false; // Clear email meta data (header..etc) - $sql = 'UPDATE '.THREAD_ENTRY_EMAIL_TABLE.' email ' - . 'INNER JOIN '.THREAD_ENTRY_TABLE.' entry - ON (entry.id = email.thread_entry_id) ' - . 'SET email.headers = null ' - . 'WHERE entry.thread_id = '.db_input($this->getId()); - db_query($sql); + ThreadEntryEmailInfo::objects() + ->filter(array('thread_entry__thread' => $this)) + ->update(array('headers' => null)); // Mass delete entries $this->deleteAttachments(); - $sql = 'DELETE FROM '.THREAD_ENTRY_TABLE - . ' WHERE thread_id='.db_input($this->getId()); - db_query($sql); + + $this->entries->delete(); return true; } static function create($vars) { - - if (!$vars || !$vars['object_id'] || !$vars['object_type']) - return false; - - $sql = 'INSERT INTO '.THREAD_TABLE.' SET created=NOW() ' - .', object_id='.db_input($vars['object_id']) - .', object_type='.db_input($vars['object_type']); - - if (db_query($sql)) - return static::lookup(db_insert_id()); - - return null; + $inst = parent::create($vars); + $inst->created = SqlFunction::NOW(); + return $inst; } +} - static function lookup($id) { - - return ($id - && ($thread = new Thread($id)) - && $thread->getId() - ) - ? $thread : null; - } +class ThreadEntryEmailInfo extends VerySimpleModel { + static $meta = array( + 'table' => THREAD_ENTRY_EMAIL_TABLE, + 'pk' => array('id'), + 'joins' => array( + 'thread_entry' => array( + 'constraint' => array('thread_entry_id' => 'ThreadEntry.id'), + ), + ), + ); } -class ThreadEntryModel extends VerySimpleModel { +class ThreadEntry extends VerySimpleModel { static $meta = array( 'table' => THREAD_ENTRY_TABLE, 'pk' => array('id'), + 'select_related' => array('staff', 'user', 'email_info'), 'joins' => array( 'thread' => array( 'constraint' => array('thread_id' => 'ThreadModel.id'), ), + 'parent' => array( + 'constraint' => array('pid' => 'ThreadEntry.id'), + 'null' => true, + ), + 'children' => array( + 'reverse' => 'ThreadEntry.parent', + ), + 'email_info' => array( + 'reverse' => 'ThreadEntryEmailInfo.thread_entry', + 'list' => false, + ), 'attachments' => array( - 'reverse' => 'AttachmentModel.thread', + 'reverse' => 'Attachment.thread_entry', + 'null' => true, + ), + 'staff' => array( + 'constraint' => array('staff_id' => 'Staff.staff_id'), + 'null' => true, + ), + 'user' => array( + 'constraint' => array('user_id' => 'User.id'), 'null' => true, ), ), ); -} - -class ThreadEntry { - - var $id; - var $ht; - var $thread; - var $attachments; + var $_headers; + var $_thread; var $_actions; - - function ThreadEntry($id, $threadId=0, $type='') { - $this->load($id, $threadId, $type); - } - - function load($id=0, $threadId=0, $type='') { - - if (!$id && !($id=$this->getId())) - return false; - - $sql='SELECT entry.*, email.mid, email.headers ' - .' ,count(DISTINCT attach.id) as attachments ' - .' FROM '.THREAD_ENTRY_TABLE.' entry ' - .' LEFT JOIN '.THREAD_ENTRY_EMAIL_TABLE.' email - ON (email.thread_entry_id=entry.id) ' - .' LEFT JOIN '.ATTACHMENT_TABLE.' attach - ON (attach.object_id=entry.id AND attach.`type` = "H") ' - .' WHERE entry.id='.db_input($id); - - if ($type) - $sql.=' AND entry.type='.db_input($type); - - if ($threadId) - $sql.=' AND entry.thread_id='.db_input($threadId); - - $sql.=' GROUP BY entry.id '; - - if (!($res=db_query($sql)) || !db_num_rows($res)) - return false; - - $this->ht = db_fetch_array($res); - $this->id = $this->ht['id']; - $this->attachments = new GenericAttachments($this->id, 'H'); - - return true; - } + var $_attachments; function postEmail($mailinfo) { if (!($thread = $this->getThread())) @@ -442,36 +331,32 @@ class ThreadEntry { return $thread->postEmail($mailinfo); } - function reload() { - return $this->load(); - } - function getId() { return $this->id; } function getPid() { - return $this->ht['pid']; + return $this->pid; } function getType() { - return $this->ht['type']; + return $this->type; } function getSource() { - return $this->ht['source']; + return $this->source; } function getPoster() { - return $this->ht['poster']; + return $this->poster; } function getTitle() { - return $this->ht['title']; + return $this->title; } function getBody() { - return ThreadEntryBody::fromFormattedText($this->ht['body'], $this->ht['format']); + return ThreadEntryBody::fromFormattedText($this->body, $this->format); } function setBody($body) { @@ -484,36 +369,37 @@ class ThreadEntry { $body = new TextThreadEntryBody($body); } - $sql='UPDATE '.THREAD_ENTRY_TABLE.' SET updated=NOW()' - .',format='.db_input($body->getType()) - .',body='.db_input((string) $body) - .' WHERE id='.db_input($this->getId()); - return db_query($sql) && db_affected_rows(); + $this->format = $body->getType(); + $this->body = (string) $body; + return $this->save(); } function getCreateDate() { - return $this->ht['created']; + return $this->created; } function getUpdateDate() { - return $this->ht['updated']; + return $this->updated; } function getNumAttachments() { - return $this->ht['attachments']; + return $this->attachments->count(); } function getEmailMessageId() { - return $this->ht['mid']; + if ($this->email_info) + return $this->email_info->mid; } function getEmailHeaderArray() { require_once(INCLUDE_DIR.'class.mailparse.php'); - if (!isset($this->ht['@headers'])) - $this->ht['@headers'] = Mail_Parse::splitHeaders($this->ht['headers']); - - return $this->ht['@headers']; + if (!isset($this->_headers) && $this->email_info + && isset($this->email_info->headers) + ) { + $this->_headers = Mail_Parse::splitHeaders($this->email_info->headers); + } + return $this->_headers; } function getEmailReferences($include_mid=true) { @@ -558,44 +444,46 @@ class ThreadEntry { } function getThreadId() { - return $this->ht['thread_id']; + return $this->thread_id; } function getThread() { - if(!$this->thread && $this->getThreadId()) + if (!isset($this->_thread) && $this->thread_id) // TODO: Consider typing the thread based on its type field - $this->thread = ObjectThread::lookup($this->getThreadId()); + $this->_thread = ObjectThread::lookup($this->getThreadId()); - return $this->thread; + return $this->_thread; } function getStaffId() { - return $this->ht['staff_id']; + return isset($this->staff_id) ? $this->staff_id : 0; } function getStaff() { - - if(!$this->staff && $this->getStaffId()) - $this->staff = Staff::lookup($this->getStaffId()); - return $this->staff; } function getUserId() { - return $this->ht['user_id']; + return isset($this->user_id) ? $this->user_id : 0; } function getUser() { + return $this->user; + } - if (!isset($this->user)) - $this->user = User::lookup($this->getUserId()); + function getName() { + if ($this->staff_id) + return $this->staff->getName(); + if ($this->user_id) + return $this->user->getName(); - return $this->user; + return $this->poster; } function getEmailHeader() { - return $this->ht['headers']; + if ($this->email_info) + return $this->email_info->headers; } function isAutoReply() { @@ -632,8 +520,8 @@ class ThreadEntry { continue; if(!$file['error'] - && ($id=AttachmentFile::upload($file)) - && $this->saveAttachment($id)) + && ($F=AttachmentFile::upload($file)) + && $this->saveAttachment($F)) $uploaded[]=$id; else { if(!$file['error']) @@ -674,8 +562,8 @@ class ThreadEntry { if(!$attachment || !is_array($attachment)) return null; - $id=0; - if ($attachment['error'] || !($id=$this->saveAttachment($attachment))) { + $A=null; + if ($attachment['error'] || !($A=$this->saveAttachment($attachment))) { $error = $attachment['error']; if(!$error) $error = sprintf(_S('Unable to import attachment - %s'), @@ -685,7 +573,7 @@ class ThreadEntry { _S('File Import Error'), $error, _S('SYSTEM'), false); } - return $id; + return $A; } /* @@ -696,28 +584,47 @@ class ThreadEntry { $inline = is_array($file) && @$file['inline']; - return $this->attachments->save($file, $inline); + if (is_numeric($file)) + $fileId = $file; + elseif ($file instanceof AttachmentFile) + $fileId = $file->getId(); + elseif ($F = AttachmentFile::create($file)) + $fileId = $F->getId(); + elseif (is_array($file) && isset($file['id'])) + $fileId = $file['id']; + else + return false; + + $att = Attachment::create(array( + 'type' => 'H', + 'object_id' => $this->getId(), + 'file_id' => $fileId, + 'inline' => $inline ? 1 : 0, + )); + if (!$att->save()) + return false; + return $att; } function saveAttachments($files) { - $ids=array(); + $attachments = array(); foreach ($files as $file) - if (($id=$this->saveAttachment($file))) - $ids[] = $id; + if (($A = $this->saveAttachment($file))) + $attachments[] = $A; - return $ids; + return $attachments; } function getAttachments() { - return $this->attachments->getAll(false); + return $this->attachments; } function getAttachmentUrls() { $json = array(); - foreach ($this->getAttachments() as $att) { - $json[$att['key']] = array( - 'download_url' => $att['download_url'], - 'filename' => $att['name'], + foreach ($this->attachments as $att) { + $json[$att->file->getKey()] = array( + 'download_url' => $att->file->getDownloadUrl(), + 'filename' => $att->file->name, ); } @@ -725,16 +632,19 @@ class ThreadEntry { } function getAttachmentsLinks($file='attachment.php', $target='_blank', $separator=' ') { + // TODO: Move this to the respective UI templates $str=''; - foreach ($this->getAttachments() as $att ) { - if ($att['inline']) continue; + foreach ($this->attachments as $att ) { + if ($att->inline) continue; $size = ''; - if ($att['size']) - $size=sprintf('<em>(%s)</em>', Format::file_size($att['size'])); + if ($att->file->size) + $size=sprintf('<em>(%s)</em>', Format::file_size($att->file->size)); - $str.=sprintf('<a class="Icon file no-pjax" href="%s" target="%s">%s</a>%s %s', - $att['download_url'], $target, Format::htmlchars($att['name']), $size, $separator); + $str .= sprintf( + '<a class="Icon file no-pjax" href="%s" target="%s">%s</a>%s %s', + $att->file->getDownloadUrl(), $target, + Format::htmlchars($att->file->name), $size, $separator); } return $str; @@ -744,8 +654,8 @@ class ThreadEntry { function getFiles() { $files = array(); - foreach($this->getAttachments() as $attachment) - $files[$attachment['file_id']] = $attachment['name']; + foreach($this->attachments as $attachment) + $files[$attachment->file_id] = $attachment->file->name; return $files; } @@ -774,13 +684,15 @@ class ThreadEntry { if (!$id || !$mid) return false; - $sql='INSERT INTO '.THREAD_ENTRY_EMAIL_TABLE - .' SET thread_entry_id='.db_input($id) - .', mid='.db_input($mid); + $this->email_info = ThreadEntryEmailInfo::create(array( + 'thread_entry_id' => $id, + 'mid' => $mid, + )); + if ($header) - $sql .= ', headers='.db_input(trim($header)); + $this->email_info->headers = trim($header); - return db_query($sql) ? db_insert_id() : 0; + return $this->email_info->save(); } /* variables */ @@ -810,14 +722,6 @@ class ThreadEntry { return false; } - static function lookup($id, $tid=0, $type='') { - return ($id - && is_numeric($id) - && ($e = new ThreadEntry($id, $tid, $type)) - && $e->getId()==$id - )?$e:null; - } - /** * Parameters: * mailinfo (hash<String>) email header information. Must include keys @@ -834,14 +738,12 @@ class ThreadEntry { function lookupByEmailHeaders(&$mailinfo, &$seen=false) { // Search for messages using the References header, then the // in-reply-to header - $search = 'SELECT thread_entry_id, mid FROM '.THREAD_ENTRY_EMAIL_TABLE - . ' WHERE mid=%s ' - . ' ORDER BY thread_entry_id DESC'; - - if (list($id, $mid) = db_fetch_row(db_query( - sprintf($search, db_input($mailinfo['mid']))))) { + if ($entry = ThreadEntry::objects() + ->filter(array('email_info__mid' => $mailinfo['mid'])) + ->first() + ) { $seen = true; - return ThreadEntry::lookup($id); + return $entry; } foreach (array('in-reply-to', 'references') as $header) { @@ -867,10 +769,9 @@ class ThreadEntry { list($left, $ref) = explode('+', $left); $mid = "$left@$right"; } - $res = db_query(sprintf($search, db_input($mid))); - while (list($id) = db_fetch_row($res)) { - if (!($t = ThreadEntry::lookup($id))) - continue; + $possibles = ThreadEntry::objects() + ->filter(array('email_info__mid' => $mid)); + foreach ($possibles as $t) { // Capture the first match thread item if (!$thread) $thread = $t; @@ -1028,36 +929,36 @@ class ThreadEntry { if ($poster && is_object($poster)) $poster = (string) $poster; - $sql=' INSERT INTO '.THREAD_ENTRY_TABLE.' SET `created` = NOW() ' - .' ,`type` = '.db_input($vars['type']) - .' ,`thread_id` = '.db_input($vars['threadId']) - .' ,`title` = '.db_input(Format::sanitize($vars['title'], true)) - .' ,`format` = '.db_input($vars['body']->getType()) - .' ,`staff_id` = '.db_input($vars['staffId']) - .' ,`user_id` = '.db_input($vars['userId']) - .' ,`poster` = '.db_input($poster) - .' ,`source` = '.db_input($vars['source']); + $entry = parent::create(array( + 'created' => SqlFunction::NOW(), + 'type' => $vars['type'], + 'thread_id' => $vars['threadId'], + 'title' => Format::sanitize($vars['title'], true), + 'format' => $vars['body']->getType(), + 'staff_id' => $vars['staffId'], + 'user_id' => $vars['userId'], + 'poster' => $poster, + 'source' => $vars['source'], + )); if (!isset($vars['attachments']) || !$vars['attachments']) // Otherwise, body will be configured in a block below (after // inline attachments are saved and updated in the database) - $sql.=' ,body='.db_input($body); + $entry->body = $body; if (isset($vars['pid'])) - $sql.=' ,pid='.db_input($vars['pid']); + $entry->pid = $vars['pid']; // Check if 'reply_to' is in the $vars as the previous ThreadEntry // instance. If the body of the previous message is found in the new // body, strip it out. elseif (isset($vars['reply_to']) && $vars['reply_to'] instanceof ThreadEntry) - $sql.=' ,pid='.db_input($vars['reply_to']->getId()); + $entry->pid = $vars['reply_to']->getId(); if ($vars['ip_address']) - $sql.=' ,ip_address='.db_input($vars['ip_address']); + $entry->ip_address = $vars['ip_address']; - //echo $sql; - if (!db_query($sql) - || !($entry=self::lookup(db_insert_id(), $vars['threadId']))) + if (!$entry->save()) return false; /************* ATTACHMENTS *****************/ @@ -1091,11 +992,8 @@ class ThreadEntry { } } - $sql = 'UPDATE '.THREAD_ENTRY_TABLE - .' SET body='.db_input($body) - .' WHERE `id`='.db_input($entry->getId()); - - if (!db_query($sql) || !db_affected_rows()) + $entry->body = $body; + if (!$entry->save()) return false; } @@ -1109,13 +1007,12 @@ class ThreadEntry { // Inline images (attached to the draft) $entry->saveAttachments(Draft::getAttachmentIds($body)); - Signal::send('model.created', $entry); - + Signal::send('threadentry.created', $this); return $entry; } static function add($vars) { - return ($entry=self::create($vars)) ? $entry->getId() : 0; + return self::create($vars); } // Extensible thread entry actions ------------------------ @@ -1307,6 +1204,10 @@ class TextThreadEntryBody extends ThreadEntryBody { return Format::stripEmptyLines($this->body); } + function prepend($what) { + $this->body = $what . "\n\n" . $this->body; + } + function display($output=false) { if ($this->isEmpty()) return '(empty)'; @@ -1354,6 +1255,10 @@ class HtmlThreadEntryBody extends ThreadEntryBody { return Format::searchable($body); } + function prepend($what) { + $this->body = sprintf('<div>%s<br/><br/></div>%s', $what, $this->body); + } + function display($output=false) { if ($this->isEmpty()) return '(empty)'; @@ -1375,16 +1280,12 @@ class MessageThreadEntry extends ThreadEntry { const ENTRY_TYPE = 'M'; - function MessageThreadEntry($id, $threadId=0) { - parent::ThreadEntry($id, $threadId, self::ENTRY_TYPE); - } - function getSubject() { return $this->getTitle(); } static function create($vars, &$errors) { - return self::lookup(self::add($vars, $errors)); + return static::add($vars, $errors); } static function add($vars, &$errors) { @@ -1406,16 +1307,6 @@ class MessageThreadEntry extends ThreadEntry { return parent::add($vars); } - - static function lookup($id, $tid=0) { - - return ($id - && is_numeric($id) - && ($m = new MessageThreadEntry($id, $tid)) - && $m->getId()==$id - )?$m:null; - } - } /* thread entry of type response */ @@ -1423,10 +1314,6 @@ class ResponseThreadEntry extends ThreadEntry { const ENTRY_TYPE = 'R'; - function ResponseThreadEntry($id, $threadId=0) { - parent::ThreadEntry($id, $threadId, self::ENTRY_TYPE); - } - function getSubject() { return $this->getTitle(); } @@ -1436,7 +1323,7 @@ class ResponseThreadEntry extends ThreadEntry { } static function create($vars, &$errors) { - return self::lookup(self::add($vars, $errors)); + return static::add($vars, $errors); } static function add($vars, &$errors) { @@ -1460,31 +1347,18 @@ class ResponseThreadEntry extends ThreadEntry { return parent::add($vars); } - - static function lookup($id, $tid=0) { - - return ($id - && is_numeric($id) - && ($r = new ResponseThreadEntry($id, $tid)) - && $r->getId()==$id - )?$r:null; - } } /* Thread entry of type note (Internal Note) */ class NoteThreadEntry extends ThreadEntry { const ENTRY_TYPE = 'N'; - function NoteThreadEntry($id, $threadId=0) { - parent::ThreadEntry($id, $threadId, self::ENTRY_TYPE); - } - function getMessage() { return $this->getBody(); } static function create($vars, &$errors) { - return self::lookup(self::add($vars, $errors)); + return self::add($vars, $errors); } static function add($vars, &$errors) { @@ -1503,15 +1377,6 @@ class NoteThreadEntry extends ThreadEntry { return parent::add($vars); } - - static function lookup($id, $tid=0) { - - return ($id - && is_numeric($id) - && ($n = new NoteThreadEntry($id, $tid)) - && $n->getId()==$id - )?$n:null; - } } // Object specific thread utils. @@ -1522,53 +1387,56 @@ class ObjectThread extends Thread { ObjectModel::OBJECT_TYPE_TASK => 'TaskThread', ); - function __construct($id) { + var $counts; - parent::__construct($id); + function getCounts() { + if (!isset($this->counts) && $this->getId()) { + $this->counts = array(); - if ($this->getId()) { - $sql= ' SELECT `type`, count(DISTINCT e.id) as count ' - .' FROM '.THREAD_TABLE. ' t ' - .' INNER JOIN '.THREAD_ENTRY_TABLE. ' e ON (e.thread_id = t.id) ' - .' WHERE t.id='.db_input($this->getId()) - .' GROUP BY e.`type`'; + $stuff = static::objects()->annotate(array( + 'count' => SqlAggregate::COUNT('thread_entry', true) + )) + ->values_flat('thread_entry__type', 'count'); + print $stuff; - if (($res=db_query($sql)) && db_num_rows($res)) { - while ($row=db_fetch_row($res)) - $this->_entries[$row[0]] = $row[1]; + foreach ($stuff as $row) { + list($type, $count) = $row; + $this->counts[$type] = $count; } } } function getNumMessages() { - return $this->_entries[MessageThreadEntry::ENTRY_TYPE]; + $this->getCounts(); + return $this->counts[MessageThreadEntry::ENTRY_TYPE]; } function getNumResponses() { - return $this->_entries[ResponseThreadEntry::ENTRY_TYPE]; + $this->getCounts(); + return $this->counts[ResponseThreadEntry::ENTRY_TYPE]; } function getNumNotes() { - return $this->_entries[NoteThreadEntry::ENTRY_TYPE]; + $this->getCounts(); + return $this->counts[NoteThreadEntry::ENTRY_TYPE]; } function getMessages() { - return $this->getEntries(array( - 'type' => MessageThreadEntry::ENTRY_TYPE)); + return $this->entries->filter(array( + 'type' => MessageThreadEntry::ENTRY_TYPE + )); } function getLastMessage() { - - $criteria = array( - 'type' => MessageThreadEntry::ENTRY_TYPE, - 'order' => 'DESC', - 'limit' => 1); - - return $this->getEntry($criteria); + return $this->entries->filter(array( + 'type' => MessageThreadEntry::ENTRY_TYPE + )) + ->order_by('-id') + ->first(); } function getEntry($var) { - + // XXX: PUNT if (is_numeric($var)) $id = $var; else { @@ -1582,13 +1450,15 @@ class ObjectThread extends Thread { } function getResponses() { - return $this->getEntries(array( - 'type' => ResponseThreadEntry::ENTRY_TYPE)); + return $this->entries->filter(array( + 'type' => ResponseThreadEntry::ENTRY_TYPE + )); } function getNotes() { - return $this->getEntries(array( - 'type' => NoteThreadEntry::ENTRY_TYPE)); + return $this->entries->filter(array( + 'type' => NoteThreadEntry::ENTRY_TYPE + )); } function addNote($vars, &$errors) { @@ -1617,38 +1487,36 @@ class ObjectThread extends Thread { function getVar($name) { switch ($name) { case 'original': - $entries = $this->getEntries(array( - 'type' => MessageThreadEntry::ENTRY_TYPE, - 'order' => 'ASC', - 'limit' => 1)); - if ($entries && $entries[0]) - return (string) $entries[0]['body']; + $entry = $this->entries->filter(array( + 'type' => MessageThreadEntry::ENTRY_TYPE, + )) + ->order_by('id') + ->first(); + if ($entry) + return $entry->getBody(); break; case 'last_message': case 'lastmessage': - $entries = $this->getEntries(array( - 'type' => MessageThreadEntry::ENTRY_TYPE, - 'order' => 'DESC', - 'limit' => 1)); - if ($entries && $entries[0]) - return (string) $entries[0]['body']; + $entry = $this->getLastMessage(); + if ($entry) + return $entry->getBody(); break; } } static function lookup($criteria, $type=false) { + if (!$type) + return parent::lookup($criteria); + $class = false; - if ($type && isset(self::$types[$type])) + if (isset(self::$types[$type])) $class = self::$types[$type]; if (!class_exists($class)) $class = get_called_class(); - return ($criteria - && ($t = new $class($criteria)) - && $t->getId() - ) ? $t : null; + return $class::lookup($criteria); } } @@ -1657,10 +1525,12 @@ class TicketThread extends ObjectThread { static function create($ticket) { $id = is_object($ticket) ? $ticket->getId() : $ticket; - return parent::create(array( + $thread = parent::create(array( 'object_id' => $id, 'object_type' => ObjectModel::OBJECT_TYPE_TICKET )); + if ($thread->save()) + return $thread; } } diff --git a/include/class.ticket.php b/include/class.ticket.php index 9f23ae416f7f5753f05d962820760c5fdaa06fc3..626e2265fe4ce55e7db98168cd04221f1581e573 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -75,7 +75,7 @@ class TicketModel extends VerySimpleModel { 'null' => true, ), 'thread' => array( - 'reverse' => 'ThreadModel.ticket', + 'reverse' => 'Thread.ticket', 'list' => false, 'null' => true, ), @@ -818,7 +818,7 @@ implements RestrictedAccess, Threadable { } function getThreadCount() { - return $this->getNumMessages() + $this->getNumResponses(); + return $this->getClientThread()->count(); } function getNumMessages() { @@ -834,15 +834,15 @@ implements RestrictedAccess, Threadable { } function getMessages() { - return $this->getThreadEntries('M'); + return $this->getThreadEntries(array('M')); } function getResponses() { - return $this->getThreadEntries('R'); + return $this->getThreadEntries(array('R')); } function getNotes() { - return $this->getThreadEntries('N'); + return $this->getThreadEntries(array('N')); } function getClientThread() { @@ -853,9 +853,11 @@ implements RestrictedAccess, Threadable { return $this->getThread()->getEntry($id); } - function getThreadEntries($type, $order='') { - return $this->getThread()->getEntries( - array( 'type' => $type, 'order' => $order)); + function getThreadEntries($type=false) { + $thread = $this->getThread()->getEntries(); + if ($type && is_array($type)) + $thread->filter(array('type__in' => $type)); + return $thread; } //Collaborators @@ -3129,7 +3131,7 @@ implements RestrictedAccess, Threadable { $ticket->logEvent('created'); // Fire post-create signal (for extra email sending, searching) - Signal::send('model.created', $ticket); + Signal::send('ticket.created', $ticket); /* Phew! ... time for tea (KETEPA) */ diff --git a/include/class.user.php b/include/class.user.php index 5db55087c1538f53bd2acb0311c3022165d2c7dc..0ccfaf1a7c104c1238b346076b57792be5211307 100644 --- a/include/class.user.php +++ b/include/class.user.php @@ -178,6 +178,7 @@ class User extends UserModel { catch (OrmException $e) { return null; } + Signal::send('user.created', $user); } return $user; @@ -716,9 +717,9 @@ class PersonsName { $r = explode(' ', $name); $size = count($r); - + //check if name is bad format (ex: J.Everybody), and fix them - if($size==1 && mb_strpos($r[0], '.') !== false) + if($size==1 && mb_strpos($r[0], '.') !== false) { $r = explode('.', $name); $size = count($r); diff --git a/include/client/templates/ticket-print.tmpl.php b/include/client/templates/ticket-print.tmpl.php index 67e24a1700af259f8e802017fd405675527d9dd7..a1173b217b48111ebc8857129cd9c8e4fe74a56a 100644 --- a/include/client/templates/ticket-print.tmpl.php +++ b/include/client/templates/ticket-print.tmpl.php @@ -198,30 +198,29 @@ $types = array('M', 'R'); if ($thread = $ticket->getThreadEntries($types)) { $threadTypes=array('M'=>'message','R'=>'response', 'N'=>'note'); foreach ($thread as $entry) { ?> - <div class="thread-entry <?php echo $threadTypes[$entry['thread_type']]; ?>"> + <div class="thread-entry <?php echo $threadTypes[$entry->type]; ?>"> <table class="header"><tr><td> <span><?php - echo Format::datetime($entry['created']);?></span> + echo Format::datetime($entry->created);?></span> <span style="padding:0 1em" class="faded title"><?php - echo Format::truncate($entry['title'], 100); ?></span> + echo Format::truncate($entry->title, 100); ?></span> </td> <td class="flush-right faded title" style="white-space:no-wrap"> <?php - echo Format::htmlchars($entry['name'] ?: $entry['poster']); ?></span> + echo Format::htmlchars($entry->getName()); ?></span> </td> </tr></table> <div class="thread-body"> - <div><?php echo $entry['body']->display('pdf'); ?></div> + <div><?php echo $entry->getBody()->display('pdf'); ?></div> </div> <?php - if ($entry['attachments'] - && ($tentry = $ticket->getThreadEntry($entry['id'])) - && ($files = $tentry->getAttachments())) { ?> + if ($entry->has_attachments + && ($files = $entry->attachments)) { ?> <div class="info"> -<?php foreach ($files as $F) { ?> +<?php foreach ($files as $A) { ?> <div> - <span><?php echo $F['name']; ?></span> - <span class="faded">(<?php echo Format::file_size($F['size']); ?>)</span> + <span><?php echo Format::htmlchars($A->file->name); ?></span> + <span class="faded">(<?php echo Format::file_size($A->file->size); ?>)</span> </div> <?php } ?> </div> diff --git a/include/client/view.inc.php b/include/client/view.inc.php index fe2f1b8aa481d93f4178743bfe4808ce82cda4e8..bc331dee94d8eab2efc2a64d6cbdc72ac74b3de4 100644 --- a/include/client/view.inc.php +++ b/include/client/view.inc.php @@ -114,24 +114,23 @@ if($ticket->getThreadCount() && ($thread=$ticket->getClientThread())) { foreach($thread as $entry) { //Making sure internal notes are not displayed due to backend MISTAKES! - if(!$threadType[$entry['type']]) continue; - $poster = $entry['poster']; - if($entry['type']=='R' && ($cfg->hideStaffName() || !$entry['staff_id'])) + if(!$threadType[$entry->type]) continue; + $poster = $entry->poster; + if($entry->type=='R' && ($cfg->hideStaffName() || !$entry->staff_id)) $poster = ' '; ?> - <table class="thread-entry <?php echo $threadType[$entry['type']]; ?>" cellspacing="0" cellpadding="1" width="800" border="0"> + <table class="thread-entry <?php echo $threadType[$entry->type]; ?>" cellspacing="0" cellpadding="1" width="800" border="0"> <tr><th><div> -<?php echo Format::datetime($entry['created']); ?> +<?php echo Format::datetime($entry->created); ?> <span class="textra"></span> <span><?php echo $poster; ?></span> </div> </th></tr> - <tr><td class="thread-body"><div><?php echo Format::clickableurls($entry['body']->toHtml()); ?></div></td></tr> + <tr><td class="thread-body"><div><?php echo Format::clickableurls($entry->getBody()->toHtml()); ?></div></td></tr> <?php - if($entry['attachments'] - && ($tentry=$ticket->getThreadEntry($entry['id'])) - && ($urls = $tentry->getAttachmentUrls()) - && ($links=$tentry->getAttachmentsLinks())) { ?> + if($entry->has_attachments + && ($urls = $entry->getAttachmentUrls()) + && ($links = $entry->getAttachmentsLinks())) { ?> <tr><td class="info"><?php echo $links; ?></td></tr> <?php } if ($urls) { ?> diff --git a/include/staff/templates/task-view.tmpl.php b/include/staff/templates/task-view.tmpl.php index 6020b7a3eadfbec200c2f88ef25fff82ba010340..a9a4c72170780e61cd2edd7ada25a4ab30a6ab3b 100644 --- a/include/staff/templates/task-view.tmpl.php +++ b/include/staff/templates/task-view.tmpl.php @@ -212,32 +212,31 @@ foreach (DynamicFormEntry::forObject($task->getId(), $types = array('M', 'R', 'N'); if(($thread=$task->getThreadEntries($types))) { foreach($thread as $entry) { ?> - <table class="thread-entry <?php echo $threadTypes[$entry['type']]; ?>" cellspacing="0" cellpadding="1" width="940" border="0"> + <table class="thread-entry <?php echo $threadTypes[$entry->type]; ?>" cellspacing="0" cellpadding="1" width="940" border="0"> <tr> <th colspan="4" width="100%"> <div> <span class="pull-left"> <span style="display:inline-block"><?php - echo Format::datetime($entry['created']);?></span> + echo Format::datetime($entry->created);?></span> <span style="display:inline-block;padding:0 1em" class="faded title"><?php - echo Format::truncate($entry['title'], 100); ?></span> + echo Format::truncate($entry->title, 100); ?></span> </span> <span class="pull-right" style="white-space:no-wrap;display:inline-block"> <span style="vertical-align:middle;" class="textra"></span> <span style="vertical-align:middle;" class="tmeta faded title"><?php - echo Format::htmlchars($entry['name'] ?: $entry['poster']); ?></span> + echo Format::htmlchars($entry->getName()); ?></span> </span> </div> </th> </tr> <tr><td colspan="4" class="thread-body" id="thread-id-<?php - echo $entry['id']; ?>"><div><?php - echo $entry['body']->toHtml(); ?></div></td></tr> + echo $entry->getId(); ?>"><div><?php + echo $entry->getBody()->toHtml(); ?></div></td></tr> <?php $urls = null; - if($entry['attachments'] - && ($tentry = $task->getThreadEntry($entry['id'])) + if($entry->has_attachments && ($urls = $tentry->getAttachmentUrls()) && ($links = $tentry->getAttachmentsLinks())) {?> <tr> @@ -246,7 +245,7 @@ foreach (DynamicFormEntry::forObject($task->getId(), } if ($urls) { ?> <script type="text/javascript"> - $('#thread-id-<?php echo $entry['id']; ?>') + $('#thread-id-<?php echo $entry->getId(); ?>') .data('urls', <?php echo JsonDataEncoder::encode($urls); ?>) .data('id', <?php echo $entry['id']; ?>); @@ -255,8 +254,8 @@ foreach (DynamicFormEntry::forObject($task->getId(), } ?> </table> <?php - if ($entry['type'] == 'M') - $msgId = $entry['id']; + if ($entry->type == 'M') + $msgId = $entry->getId(); } } else { echo '<p>'.__('Error fetching thread - get technical help.').'</p>'; diff --git a/include/staff/templates/ticket-print.tmpl.php b/include/staff/templates/ticket-print.tmpl.php index e283e4e8f2da193598e1096b8d1cbe0eb86d00e4..11323c4a14b60077e7e208cc41f0b1434726882e 100644 --- a/include/staff/templates/ticket-print.tmpl.php +++ b/include/staff/templates/ticket-print.tmpl.php @@ -222,29 +222,28 @@ if ($this->includenotes) if ($thread = $ticket->getThreadEntries($types)) { $threadTypes=array('M'=>'message','R'=>'response', 'N'=>'note'); foreach ($thread as $entry) { ?> - <div class="thread-entry <?php echo $threadTypes[$entry['thread_type']]; ?>"> + <div class="thread-entry <?php echo $threadTypes[$entry->type]; ?>"> <table class="header" style="width:100%"><tr><td> <span><?php - echo Format::datetime($entry['created']);?></span> + echo Format::datetime($entry->created);?></span> <span style="padding:0 1em" class="faded title"><?php - echo Format::truncate($entry['title'], 100); ?></span> + echo Format::truncate($entry->title, 100); ?></span> </td> <td class="flush-right faded title" style="white-space:no-wrap"> <?php - echo Format::htmlchars($entry['name'] ?: $entry['poster']); ?></span> + echo Format::htmlchars($entry->getName()); ?></span> </td> </tr></table> <div class="thread-body"> - <div><?php echo $entry['body']->display('pdf'); ?></div> + <div><?php echo $entry->getBody()->display('pdf'); ?></div> <?php - if ($entry['attachments'] - && ($tentry = $ticket->getThreadEntry($entry['id'])) - && ($files = $tentry->getAttachments())) { ?> + if ($entry->has_attachments + && ($files = $entry->attachments)) { ?> <div class="info"> -<?php foreach ($files as $F) { ?> +<?php foreach ($files as $A) { ?> <div> - <span><?php echo $F['name']; ?></span> - <span class="faded">(<?php echo Format::file_size($F['size']); ?>)</span> + <span><?php echo Format::htmlchars($A->file->name); ?></span> + <span class="faded">(<?php echo Format::file_size($A->file->size); ?>)</span> </div> <?php } ?> </div> diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php index eb34d70a3e82222da418dc6c025081487bf4490c..b954c49dcb78c081294149b7013a55d97931435f 100644 --- a/include/staff/ticket-view.inc.php +++ b/include/staff/ticket-view.inc.php @@ -376,8 +376,7 @@ foreach (DynamicFormEntry::forTicket($ticket->getId()) as $form) { <div class="clear"></div> <h2 style="padding:10px 0 5px 0; font-size:11pt;"><?php echo Format::htmlchars($ticket->getSubject()); ?></h2> <?php -$tcount = $ticket->getThreadCount(); -$tcount+= $ticket->getNumNotes(); +$tcount = $ticket->getThreadEntries($types)->count(); ?> <ul class="tabs threads" id="ticket_tabs" > <li class="active"><a href="#ticket_thread"><?php echo sprintf(__('Ticket Thread (%d)'), $tcount); ?></a></li> @@ -396,26 +395,25 @@ $tcount+= $ticket->getNumNotes(); /* -------- Messages & Responses & Notes (if inline)-------------*/ $types = array('M', 'R', 'N'); if(($thread=$ticket->getThreadEntries($types))) { - foreach($thread as $entry) { - $tentry = $ticket->getThreadEntry($entry['id']); ?> - <table class="thread-entry <?php echo $threadTypes[$entry['type']]; ?>" cellspacing="0" cellpadding="1" width="940" border="0"> + foreach($thread as $entry) { ?> + <table class="thread-entry <?php echo $threadTypes[$entry->type]; ?>" cellspacing="0" cellpadding="1" width="940" border="0"> <tr> <th colspan="4" width="100%"> <div> <span class="pull-left"> <span style="display:inline-block"><?php - echo Format::datetime($entry['created']);?></span> + echo Format::datetime($entry->created);?></span> <span style="display:inline-block;padding:0 1em" class="faded title"><?php - echo Format::truncate($entry['title'], 100); ?></span> + echo Format::truncate($entry->title, 100); ?></span> </span> -<?php if ($tentry->hasActions()) { - $actions = $tentry->getActions(); ?> - <div class="pull-right"> - <span class="action-button pull-right" data-dropdown="#entry-action-more-<?php echo $entry['id']; ?>"> + <div class="pull-right"> +<?php if ($entry->hasActions()) { + $actions = $entry->getActions(); ?> + <span class="action-button pull-right" data-dropdown="#entry-action-more-<?php echo $entry->getId(); ?>"> <i class="icon-caret-down"></i> <span ><i class="icon-cog"></i></span> </span> - <div id="entry-action-more-<?php echo $entry['id']; ?>" class="action-dropdown anchor-right"> + <div id="entry-action-more-<?php echo $entry->getId(); ?>" class="action-dropdown anchor-right"> <ul class="title"> <?php foreach ($actions as $group => $list) { foreach ($list as $id => $action) { ?> @@ -434,36 +432,47 @@ $tcount+= $ticket->getNumNotes(); <span style="vertical-align:middle;" class="textra"></span> <span style="vertical-align:middle;" class="tmeta faded title"><?php - echo Format::htmlchars($entry['name'] ?: $entry['poster']); ?></span> + echo Format::htmlchars($entry->getName()); ?></span> </span> </div> </th> </tr> <tr><td colspan="4" class="thread-body" id="thread-id-<?php - echo $entry['id']; ?>"><div><?php - echo Format::clickableurls($entry['body']->toHtml()); ?></div></td></tr> + echo $entry->getId(); ?>"><div><?php + echo Format::clickableurls($entry->getBody()->toHtml()); ?></div></td></tr> <?php $urls = null; - if($entry['attachments'] - && ($urls = $tentry->getAttachmentUrls()) - && ($links = $tentry->getAttachmentsLinks())) {?> + if ($entry->has_attachments + && ($urls = $entry->getAttachmentUrls())) { ?> <tr> - <td class="info" colspan="4"><?php echo $links; ?></td> + <td class="info" colspan="4"><?php + foreach ($entry->attachments as $A) { + if ($A->inline) continue; + $size = ''; + if ($A->file->size) + $size = sprintf('<em>(%s)</em>', + Format::file_size($A->file->size)); +?> + <a class="Icon file no-pjax" href="<?php echo $A->file->getDownloadUrl(); + ?>" target="_blank"><?php echo Format::htmlchars($A->file->name); + ?></a><?php echo $size;?> +<?php } ?> + </td> </tr> <?php } if ($urls) { ?> <script type="text/javascript"> - $('#thread-id-<?php echo $entry['id']; ?>') + $('#thread-id-<?php echo $entry->getId(); ?>') .data('urls', <?php echo JsonDataEncoder::encode($urls); ?>) - .data('id', <?php echo $entry['id']; ?>); + .data('id', <?php echo $entry->getId(); ?>); </script> <?php } ?> </table> <?php - if ($entry['type'] == 'M') - $msgId = $entry['id']; + if ($entry->type == 'M') + $msgId = $entry->getId(); } } else { echo '<p><em>'.__('No entries have been posted to this ticket.').'</em></p>'; diff --git a/include/upgrader/streams/core/15b30765-dd0022fb.task.php b/include/upgrader/streams/core/15b30765-dd0022fb.task.php index 4d7eac01ff94a7622a16b118b5c567d41d3a9af0..0bf1576190be9a1eb3cd3de3a60f87115fa99e00 100644 --- a/include/upgrader/streams/core/15b30765-dd0022fb.task.php +++ b/include/upgrader/streams/core/15b30765-dd0022fb.task.php @@ -188,7 +188,7 @@ class AttachmentMigrater extends MigrationTask { # TODO: Get the size and mime/type of each file. # # NOTE: If filesize() fails and file_get_contents() doesn't, - # then the AttachmentFile::save() method will automatically + # then the AttachmentFile::create() method will automatically # estimate the filesize based on the length of the string data # received in $info['data'] -- ie. no need to do that here. # @@ -228,9 +228,9 @@ class AttachmentMigrater extends MigrationTask { return $this->errorList; } - // This is the AttachmentFile::save() method from osTicket 1.7.6. It's + // This is the AttachmentFile::create() method from osTicket 1.7.6. It's // been ported here so that further changes to the %file table and the - // AttachmentFile::save() method do not affect upgrades from osTicket + // AttachmentFile::create() method do not affect upgrades from osTicket // 1.6 to osTicket 1.8 and beyond. function saveAttachment($file) { diff --git a/include/upgrader/streams/core/934954de-f1ccd3bb.task.php b/include/upgrader/streams/core/934954de-f1ccd3bb.task.php index 7fe0f141ed159be2a6d177219c1e8abed25ce2d8..041bfad9a33730283e1203d6711bbafcaa27a548 100644 --- a/include/upgrader/streams/core/934954de-f1ccd3bb.task.php +++ b/include/upgrader/streams/core/934954de-f1ccd3bb.task.php @@ -4,18 +4,16 @@ class FileImport extends MigrationTask { var $description = "Import core osTicket attachment files"; function run($runtime) { - $errors = array(); - $i18n = new Internationalization('en_US'); $files = $i18n->getTemplate('file.yaml')->getData(); foreach ($files as $f) { - if (!($id = AttachmentFile::create($f, $errors))) + if (!($file = AttachmentFile::create($f))) continue; // Ensure the new files are never deleted (attached to Disk) $sql ='INSERT INTO '.ATTACHMENT_TABLE .' SET object_id=0, `type`=\'D\', inline=1' - .', file_id='.db_input($id); + .', file_id='.db_input($file->getId()); db_query($sql); } }