diff --git a/WHATSNEW.md b/WHATSNEW.md index ce06e6a8acee174be1ff20dd5a6822deb904a771..c580634d187b26330c7fcecc42b29e55386ca6fc 100644 --- a/WHATSNEW.md +++ b/WHATSNEW.md @@ -1,3 +1,52 @@ +osTicket v1.9.5 +=============== +### Enhancements + * Add support for organization vars in templates + (`%{ticket.user.organization...}`) (#1561) + * Canned responses feature can now be disabled (#1562) + * Drop link redirection through l.php (#1640) + * Use unified file download script (#1641). Links can now be shared with + external users and accessed without authenticating. + * Ticket filters support matching and banning based on the Reply-To user + information (#1645) + +### Improvements + * Remove custom data when users are deleted (#1492) + * Fix matching of ticket number in subject (regression in v1.9.4) (#1486) + * Several minor translatable strings (#1441, #1489, #1560), thanks @Chefkeks + * Fix invalid UTF-8 chars PDF error for empty thread title (regression in + v1.9.4) (#1512) + * Consider auto response checkbox and department setting for new ticket by + staff (#1509) + * Fix PHP crash if `finfo` extension is missing (#1437) + * Fix export of choice field items (#1436) + * Properly handle alert and auto response flags from API (#1435), thanks + @stevepacker + * Fix current value of choice fields if set to boolean false (#1466) + * Do not reopen tickets for automated responses (#1529) + * Properly handle uppercase file extensions in file field configuration + (#1549) + * Fix release of ticket lock when navigating away from ticket view (#1552) + * Display FAQ article consistently on client portal (#1553) + * Avoid wrapping password reset URLs on text emails (#1558) + * Fix field requirement for clients when only required for agents (#1559) + * Fix language selection for new email template group (#1563) + * Fix incorrect status of new ticket if opened as `closed` and assigning to + an agent (#1565) + * Forbid disabling the only active administrator (#1569) + * Searching for tickets searches to midnight of the end date (#1572), thanks + @grintor + * Fix rejection of tickets by filter, even if a previous matching filter + would stop on match (#1644) + * Fix matching of `User / Email Address` in ticket filters (#1644) + * Properly HTML escape thread bodies when quoting (#1637) + * Use department email for agent alerts (#1555) + * Skip team assignment alert on new ticket if assigned to an agent (fddb3c7) + * Use custom form name as the page title when editing (#1646) + +### Performance and Security + * Fix possible XSS vulnerability in sortable table view pages (#1639) + osTicket v1.9.4 =============== ### Major New Features diff --git a/api/api.inc.php b/api/api.inc.php index fac03bccd1d848308809e78343838d5401325771..d1440c8b3a7e3c447dc6ab30cc0b6e31ade86c87 100644 --- a/api/api.inc.php +++ b/api/api.inc.php @@ -17,7 +17,8 @@ file_exists('../main.inc.php') or die('System Error'); // Disable sessions for the API. API should be considered stateless and // shouldn't chew up database records to store sessions -define('DISABLE_SESSION', true); +if (!defined('DISABLE_SESSION')) + define('DISABLE_SESSION', true); require_once('../main.inc.php'); require_once(INCLUDE_DIR.'class.http.php'); diff --git a/api/http.php b/api/http.php index 2efd1a98c271d103987ffd2eda667e738e5812cd..3f8f721ec6f7b2af531b7d577858d18ce90a0890 100644 --- a/api/http.php +++ b/api/http.php @@ -13,6 +13,10 @@ vim: expandtab sw=4 ts=4 sts=4: **********************************************************************/ +// Use sessions — it's important for SSO authentication, which uses +// /api/auth/ext +define('DISABLE_SESSION', false); + require 'api.inc.php'; # Include the main api urls diff --git a/attachment.php b/attachment.php deleted file mode 100644 index 8e45d8e155c83cae6c31697f49f5b8bfd3910a9e..0000000000000000000000000000000000000000 --- a/attachment.php +++ /dev/null @@ -1,38 +0,0 @@ -<?php -/********************************************************************* - attachment.php - - Attachments interface for clients. - Clients should never see the dir paths. - - Peter Rotich <peter@osticket.com> - Copyright (c) 2006-2013 osTicket - http://www.osticket.com - - Released under the GNU General Public License WITHOUT ANY WARRANTY. - See LICENSE.TXT for details. - - vim: expandtab sw=4 ts=4 sts=4: -**********************************************************************/ -require('secure.inc.php'); -require_once(INCLUDE_DIR.'class.attachment.php'); -// Basic checks -if (!$thisclient - || !$_GET['id'] - || !$_GET['h'] - || !($attachment=Attachment::lookup($_GET['id'])) - || !($file=$attachment->getFile()) - || strcasecmp(trim($_GET['h']), $file->getDownloadHash()) - || !($object=$attachment->getObject()) - || !$object instanceof ThreadEntry - || !($ticket=$object->getThread()->getObject()) - || !$ticket instanceof Ticket - ) - Http::response(404, __('Unknown or invalid file')); - -if (!$ticket->checkUserAccess($thisclient)) - die(__('Access Denied')); - -// Download the file.. -$file->download(); -?> diff --git a/file.php b/file.php new file mode 100644 index 0000000000000000000000000000000000000000..22cc8094be840bc25b322a6d571921f697beb8f8 --- /dev/null +++ b/file.php @@ -0,0 +1,39 @@ +<?php +/********************************************************************* + file.php + + File download facilitator for clients + + Peter Rotich <peter@osticket.com> + Jared Hancock <jared@osticket.com> + Copyright (c) 2006-2014 osTicket + http://www.osticket.com + + Released under the GNU General Public License WITHOUT ANY WARRANTY. + See LICENSE.TXT for details. + + vim: expandtab sw=4 ts=4 sts=4: +**********************************************************************/ +require('client.inc.php'); +require_once(INCLUDE_DIR.'class.file.php'); + +//Basic checks +if (!$_GET['key'] + || !$_GET['signature'] + || !$_GET['expires'] + || !($file = AttachmentFile::lookup($_GET['key'])) +) { + Http::response(404, __('Unknown or invalid file')); +} + +// Validate session access hash - we want to make sure the link is FRESH! +// and the user has access to the parent ticket!! +if ($file->verifySignature($_GET['signature'], $_GET['expires'])) { + if (($s = @$_GET['s']) && strpos($file->getType(), 'image/') === 0) + return $file->display($s); + + // Download the file.. + $file->download(@$_GET['disposition'] ?: false, $_GET['expires']); +} +// else +Http::response(404, __('Unknown or invalid file')); diff --git a/image.php b/image.php deleted file mode 100644 index 5b7f27283ba23175ff12772f4212a41859ea3e18..0000000000000000000000000000000000000000 --- a/image.php +++ /dev/null @@ -1,31 +0,0 @@ -<?php -/********************************************************************* - image.php - - Simply downloads the file...on hash validation as follows; - - * Hash must be 64 chars long. - * First 32 chars is the perm. file hash - * Next 32 chars is md5(file_id.session_id().file_hash) - - Peter Rotich <peter@osticket.com> - Copyright (c) 2006-2013 osTicket - http://www.osticket.com - - Released under the GNU General Public License WITHOUT ANY WARRANTY. - See LICENSE.TXT for details. - - vim: expandtab sw=4 ts=4 sts=4: -**********************************************************************/ - -require('client.inc.php'); -require_once(INCLUDE_DIR.'class.file.php'); -$h=trim($_GET['h']); -//basic checks -if(!$h || strlen($h)!=64 //32*2 - || !($file=AttachmentFile::lookup(substr($h,0,32))) //first 32 is the file hash. - || strcasecmp($h, $file->getDownloadHash())) //next 32 is file id + session hash. - Http::response(404, __('Unknown or invalid file')); - -$file->display(); -?> diff --git a/include/ajax.draft.php b/include/ajax.draft.php index 2687ad94b53fb28e074d856394489aeae2c435db..fa9d5c9b638f7ea81af2a8f78a94d1331591197c 100644 --- a/include/ajax.draft.php +++ b/include/ajax.draft.php @@ -113,7 +113,7 @@ class DraftAjaxAPI extends AjaxController { 'content_id' => 'cid:'.$f->getKey(), // Return draft_id to connect the auto draft creation 'draft_id' => $draft->getId(), - 'filelink' => sprintf('image.php?h=%s', $f->getDownloadHash()) + 'filelink' => $f->getDownloadUrl(false, 'inline'), )); } @@ -335,7 +335,7 @@ class DraftAjaxAPI extends AjaxController { ); while (list($id, $type) = db_fetch_row($res)) { $f = AttachmentFile::lookup($id); - $url = 'image.php?h='.$f->getDownloadHash(); + $url = $f->getDownloadUrl(); $files[] = array( 'thumb'=>$url.'&s=128', 'image'=>$url, diff --git a/include/ajax.tickets.php b/include/ajax.tickets.php index eb7ff09bc8a547acbcbff29b615ad2975ca3dd73..8d7228dea69f4c74eb6eb464be1cf6bcdf5aa766 100644 --- a/include/ajax.tickets.php +++ b/include/ajax.tickets.php @@ -502,7 +502,7 @@ class TicketsAjaxAPI extends AjaxController { Http::response(422, 'Unknown ticket variable'); // Ticket thread variables are assumed to be quotes - $response = "<br/><blockquote>$response</blockquote><br/>"; + $response = "<br/><blockquote>{$response->asVar()}</blockquote><br/>"; // Return text if html thread is not enabled if (!$cfg->isHtmlThreadEnabled()) diff --git a/include/api.tickets.php b/include/api.tickets.php index 6371daa632ff5240b0bee90cb3bed69c0ac56384..d80b1582889f7bf3f944eea9808a3f675f6008f2 100644 --- a/include/api.tickets.php +++ b/include/api.tickets.php @@ -118,9 +118,11 @@ class TicketApiController extends ApiController { function createTicket($data) { # Pull off some meta-data - $alert = $data['alert'] ? $data['alert'] : true; - $autorespond = $data['autorespond'] ? $data['autorespond'] : true; - $data['source'] = $data['source'] ? $data['source'] : 'API'; + $alert = (bool) (isset($data['alert']) ? $data['alert'] : true); + $autorespond = (bool) (isset($data['autorespond']) ? $data['autorespond'] : true); + + # Assign default value to source if not defined, or defined as NULL + $data['source'] = isset($data['source']) ? $data['source'] : 'API'; # Create the ticket with the data (attempt to anyway) $errors = array(); diff --git a/include/class.api.php b/include/class.api.php index 9fe2142044062ef86033447ddf72a009a142bf6f..a1fed6764a7d7b9816243b22fa51c79ed2f4d6a7 100644 --- a/include/class.api.php +++ b/include/class.api.php @@ -330,9 +330,9 @@ class ApiXmlDataParser extends XmlDataParser { if ($key == "phone" && is_array($value)) { $value = $value[":text"]; } else if ($key == "alert") { - $value = (bool)$value; + $value = (bool) (strtolower($value) === 'false' ? false : $value); } else if ($key == "autorespond") { - $value = (bool)$value; + $value = (bool) (strtolower($value) === 'false' ? false : $value); } else if ($key == "message") { if (!is_array($value)) { $value = array( diff --git a/include/class.attachment.php b/include/class.attachment.php index 005b8215689291a679d629cf0adf73b3bed13c08..9126cff8c0c7f9f68b6071dae3c50dc09e38c7ca 100644 --- a/include/class.attachment.php +++ b/include/class.attachment.php @@ -183,7 +183,7 @@ class GenericAttachments { function _getList($separate=false, $inlines=false, $lang=false) { if(!isset($this->attachments)) { $this->attachments = array(); - $sql='SELECT f.id, f.size, f.`key`, f.name ' + $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) ' @@ -191,8 +191,8 @@ class GenericAttachments { .' 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'] = AttachmentFile::getDownloadForIdAndKey( - $rec['id'], $rec['key']); + $rec['download_url'] = AttachmentFile::generateDownloadUrl( + $rec['id'], $rec['key'], $rec['signature']); $this->attachments[] = $rec; } } diff --git a/include/class.client.php b/include/class.client.php index 6ad2efc03b5e40ebeda8941ad56f7dd3a0aa4375..e353b977aaab06a3c3667b5d7f8e053987e120b1 100644 --- a/include/class.client.php +++ b/include/class.client.php @@ -40,9 +40,13 @@ abstract class TicketUser { $tag = substr($name, 3); switch (strtolower($tag)) { case 'ticket_link': - return sprintf('%s/view.php?auth=%s', + return sprintf('%s/view.php?%s', $cfg->getBaseUrl(), - urlencode($this->getAuthToken())); + Http::build_query( + array('auth' => $this->getAuthToken()), + false + ) + ); break; } diff --git a/include/class.config.php b/include/class.config.php index e164fa8092afa723e5af770b2bb8dc42fa3e597a..fc488ee4619d4e5fdc8edf5761f8253546e07a18 100644 --- a/include/class.config.php +++ b/include/class.config.php @@ -206,6 +206,10 @@ class OsticketConfig extends Config { return ($this->get('enable_kb') && FAQ::countPublishedFAQs()); } + function isCannedResponseEnabled() { + return $this->get('enable_premade'); + } + function getVersion() { return THIS_VERSION; } diff --git a/include/class.dept.php b/include/class.dept.php index 98d3fd80e218e1c7993b2f770e399dd95fdeacc1..e038c09f58da44bf682c699ff66474eff11d6fbc 100644 --- a/include/class.dept.php +++ b/include/class.dept.php @@ -102,6 +102,24 @@ class Dept extends VerySimpleModel { return $this->email_id; } + /** + * getAlertEmail + * + * Fetches either the department email (for replies) if configured. + * Otherwise, the system alert email address is used. + */ + function getAlertEmail() { + global $cfg; + + if (!$this->email && ($id = $this->getEmailId())) { + $this->email = Email::lookup($id); + } + if (!$this->email && $cfg) { + $this->email = $cfg->getAlertEmail(); + } + return $this->email; + } + function getEmail() { global $cfg; diff --git a/include/class.draft.php b/include/class.draft.php index 54d023a99831056b94e04f2a694f46bd919b50db..d777338d2296f39352dbe2488587a9b11f30ec1b 100644 --- a/include/class.draft.php +++ b/include/class.draft.php @@ -92,7 +92,7 @@ class Draft extends VerySimpleModel { } function setBody($body) { - // Change image.php urls back to content-id's + // Change file.php urls back to content-id's $body = Format::sanitize($body, false); $this->body = $body ?: ' '; diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php index be4f4af48565884d8d8c76775f0a57d8744ce0d4..181c1bdbd524bc8c3c2582c874b03ca127738ba9 100644 --- a/include/class.dynamic_forms.php +++ b/include/class.dynamic_forms.php @@ -459,7 +459,7 @@ class DynamicFormField extends VerySimpleModel { function getField($cache=true) { global $thisstaff; - // Create the `required` flag for the FormField instance + // Finagle the `required` flag for the FormField instance $ht = $this->ht; $ht['required'] = ($thisstaff) ? $this->isRequiredForStaff() : $this->isRequiredForUsers(); @@ -1199,16 +1199,18 @@ class SelectionField extends FormField { $value = JsonDataParser::parse($value) ?: $value; if (!is_array($value)) { - $config = $this->getConfiguration(); - if (!$config['multiselect']) { - // CDATA may be built with comma-list - list($value,) = explode(',', $value, 2); - } + $values = array(); $choices = $this->getChoices(); - if (isset($choices[$value])) - $value = array($value => $choices[$value]); - elseif ($id && isset($choices[$id])) - $value = array($id => $choices[$id]); + foreach (explode(',', $value) as $V) { + if (isset($choices[$V])) + $values[$V] = $choices[$V]; + } + if ($id && isset($choices[$id])) + $values[$id] = $choices[$id]; + + if ($values) + return $values; + // else return $value unchanged } // Don't set the ID here as multiselect prevents using exactly one // ID value. Instead, stick with the JSON value only. @@ -1328,13 +1330,6 @@ class SelectionField extends FormField { return $selection; } - function export($value) { - if ($value && is_numeric($value) - && ($item = DynamicListItem::lookup($value))) - return $item->toString(); - return $value; - } - function getFilterData() { $data = array(parent::getFilterData()); if (($v = $this->getClean()) instanceof DynamicListItem) { diff --git a/include/class.faq.php b/include/class.faq.php index 34e56f93e09a71e79e7e15dc7b2598c0cf9b0431..9b95776031909bb948bacd74127e45182cca2135 100644 --- a/include/class.faq.php +++ b/include/class.faq.php @@ -81,7 +81,7 @@ class FAQ extends VerySimpleModel { function getQuestion() { return $this->question; } function getAnswer() { return $this->answer; } function getAnswerWithImages() { - return Format::viewableImages($this->answer, ROOT_PATH.'image.php'); + return Format::viewableImages($this->answer); } function getTeaser() { return Format::truncate(Format::striptags($this->answer), 150); @@ -326,12 +326,11 @@ class FAQ extends VerySimpleModel { if ($attachments = $this->getVisibleAttachments()) { foreach($attachments as $attachment ) { /* The h key must match validation in file.php */ - $hash=$attachment['key'].md5($attachment['id'].session_id().strtolower($attachment['key'])); if($attachment['size']) $size=sprintf(' <small>(<i>%s</i>)</small>',Format::file_size($attachment['size'])); - $str.=sprintf('<a class="Icon file no-pjax" href="file.php?h=%s" target="%s">%s</a>%s %s', - $hash, $target, Format::htmlchars($attachment['name']), $size, $separator); + $str.=sprintf('<a class="Icon file no-pjax" href="%s" target="%s">%s</a>%s %s', + $attachment['download_url'], $target, Format::htmlchars($attachment['name']), $size, $separator); } } diff --git a/include/class.file.php b/include/class.file.php index 82b998a783e969b70918399f7559d0dd78d69f7c..2c2ab0fc69a37f19c3cda28e49b2cfa22d7bd925 100644 --- a/include/class.file.php +++ b/include/class.file.php @@ -110,19 +110,6 @@ class AttachmentFile { return $this->ht['created']; } - static function getDownloadForIdAndKey($id, $key) { - return strtolower($key . md5($id.session_id().strtolower($key))); - } - - - /** - * Retrieve a signature that can be sent to scp/file.php?h= in order to - * download this file - */ - function getDownloadHash() { - return self::getDownloadForIdAndKey($this->getId(), $this->getKey()); - } - function open() { return FileStorageBackend::getInstance($this); } @@ -203,11 +190,68 @@ class AttachmentFile { exit(); } - function download() { + function getDownloadUrl($minage=false, $disposition=false, $handler=false) { + // XXX: Drop this when AttachmentFile goes to ORM + return static::generateDownloadUrl($this->getId(), + strtolower($this->getKey()), $this->getSignature(), $minage, + $disposition, $handler); + } + + static function generateDownloadUrl($id, $key, $hash, $minage=false, + $disposition=false, $handler=false + ) { + // Expire at the nearest midnight, allowing at least 12 hours access + $minage = $minage ?: 43200; + $gmnow = Misc::gmtime() + $minage; + $expires = $gmnow + 86400 - ($gmnow % 86400); + + // Generate a signature based on secret content + $signature = static::_genUrlSignature($id, $key, $hash, $expires); + + $handler = $handler ?: ROOT_PATH . 'file.php'; + + // Return sanitized query string + $args = array( + 'key' => $key, + 'expires' => $expires, + 'signature' => $signature, + ); + + if ($disposition) + $args['disposition'] = $disposition; + + return $handler . '?' . http_build_query($args); + } + + function verifySignature($signature, $expires) { + $gmnow = Misc::gmtime(); + if ($expires < $gmnow) + return false; + + $check = static::_genUrlSignature($this->getId(), $this->getKey(), + $this->getSignature(), $expires); + return $signature == $check; + } + + static function _genUrlSignature($id, $key, $signature, $expires) { + $pieces = array( + 'Host='.$_SERVER['HTTP_HOST'], + 'Path='.ROOT_PATH, + 'Id='.$id, + 'Key='.strtolower($key), + 'Hash='.$signature, + 'Expires='.$expires, + ); + return hash_hmac('sha1', implode("\n", $pieces), SECRET_SALT); + } + + function download($disposition=false, $expires=false) { + $disposition = $disposition ?: 'inline'; $bk = $this->open(); - if ($bk->sendRedirectUrl('inline')) + if ($bk->sendRedirectUrl($disposition)) return; - $this->makeCacheable(); + $ttl = ($expires) ? $expires - Misc::gmtime() : false; + $this->makeCacheable($ttl); $type = $this->getType() ?: 'application/octet-stream'; if (isset($_REQUEST['overridetype'])) $type = $_REQUEST['overridetype']; @@ -336,7 +380,7 @@ class AttachmentFile { return false; } - if (!$file['type']) { + if (!$file['type'] && extension_loaded('fileinfo')) { $finfo = new finfo(FILEINFO_MIME_TYPE); if ($file['data']) $type = $finfo->buffer($file['data']); @@ -345,9 +389,9 @@ class AttachmentFile { if ($type) $file['type'] = $type; - else - $file['type'] = 'application/octet-stream'; } + if (!$file['type']) + $file['type'] = 'application/octet-stream'; $sql='INSERT INTO '.FILE_TABLE.' SET created=NOW() ' .',type='.db_input(strtolower($file['type'])) diff --git a/include/class.filter.php b/include/class.filter.php index d30a6c68b9186a9e5a7ca9b7cb56346ad286b7b2..acc3a3590a7921d1bee8599417c20fd1714e011a 100644 --- a/include/class.filter.php +++ b/include/class.filter.php @@ -137,7 +137,7 @@ class Filter { } function stopOnMatch() { - return ($this->ht['stop_on_match']); + return ($this->ht['stop_onmatch']); } function matchAllRules() { @@ -316,9 +316,13 @@ class Filter { # Override name with reply-to information from the TicketFilter # match if ($this->useReplyToEmail() && $info['reply-to']) { + $changed = $info['reply-to'] != $ticket['email'] + || ($info['reply-to-name'] && $ticket['name'] != $info['reply-to-name']); $ticket['email'] = $info['reply-to']; if ($info['reply-to-name']) $ticket['name'] = $info['reply-to-name']; + if ($changed) + throw new FilterDataChanged(); } # Use canned response. @@ -738,7 +742,7 @@ class TicketFilter { $res = $this->getAllActive(); if($res) { while (list($id) = db_fetch_row($res)) - array_push($this->filters, new Filter($id)); + $this->filters[] = new Filter($id); } return $this->filters; @@ -764,36 +768,25 @@ class TicketFilter { return $this->short_list; } - /** - * Determine if the filters that match the received vars indicate that - * the email should be rejected - * - * Returns FALSE if the email should be acceptable. If the email should - * be rejected, the first filter that matches and has reject ticket set is - * returned. - */ - function shouldReject() { - foreach ($this->getMatchingFilterList() as $filter) { - # Set reject if this filter indicates that the email should - # be blocked; however, don't unset $reject, because if it - # was set by another rule that did not set stopOnMatch(), we - # should still honor its configuration - if ($filter->rejectOnMatch()) return $filter; - } - return false; - } /** * Determine if any filters match the received email, and if so, apply * actions defined in those filters to the ticket-to-be-created. + * + * Throws: + * RejectedException if the email should not be acceptable. If the email + * should be rejected, the first filter that matches and has reject + * ticket set is returned. */ function apply(&$ticket) { foreach ($this->getMatchingFilterList() as $filter) { + if ($filter->rejectOnMatch()) + throw new RejectedException($filter, $ticket); $filter->apply($ticket, $this->vars); if ($filter->stopOnMatch()) break; } } - /* static */ function getAllActive() { + function getAllActive() { $sql='SELECT id FROM '.FILTER_TABLE .' WHERE isactive=1 ' @@ -949,6 +942,27 @@ class TicketFilter { } } +class RejectedException extends Exception { + var $filter; + var $vars; + + function __construct(Filter $filter, $vars) { + parent::__construct('Ticket rejected by a filter'); + $this->filter = $filter; + $this->vars = $vars; + } + + function getRejectingFilter() { + return $this->filter; + } + + function get($what) { + return $this->vars[$what]; + } +} + +class FilterDataChanged extends Exception {} + /** * Function: endsWith * diff --git a/include/class.format.php b/include/class.format.php index 28dc209917af48a4f449feaab1683b7fdf0b5a2d..9e6c8998538bdfbe908693ece1cf9428a151bee8 100644 --- a/include/class.format.php +++ b/include/class.format.php @@ -218,7 +218,7 @@ class Format { } unset($s); if ($styles) - $attributes['style'] = Format::htmlencode(implode(';', $styles)); + $attributes['style'] = Format::htmlchars(implode(';', $styles)); else unset($attributes['style']); } @@ -259,9 +259,10 @@ class Format { } function localizeInlineImages($text) { - // Change image.php urls back to content-id's - return preg_replace('/image\\.php\\?h=([\\w.-]{32})\\w{32}/', - 'cid:$1', $text); + // Change file.php urls back to content-id's + return preg_replace( + '/src="(?:\/[^"]+?)?\/file\\.php\\?(?:\w+=[^&]+&(?:amp;)?)*?key=([^&]+)[^"]*/', + 'src="cid:$1', $text); } function sanitize($text, $striptags=false) { @@ -275,15 +276,14 @@ class Format { return $striptags?Format::striptags($text, false):$text; } - function htmlchars($var) { - return Format::htmlencode($var); - } - - function htmlencode($var) { + function htmlchars($var, $sanitize = false) { static $phpversion = null; if (is_array($var)) - return array_map(array('Format', 'htmlencode'), $var); + return array_map(array('Format', 'htmlchars'), $var); + + if ($sanitize) + $var = Format::sanitize($var); if (!isset($phpversion)) $phpversion = phpversion(); @@ -293,7 +293,7 @@ class Format { $flags |= ENT_HTML401; try { - return htmlentities( (string) $var, $flags, 'UTF-8', false); + return htmlspecialchars( (string) $var, $flags, 'UTF-8', false); } catch(Exception $e) { return $var; } @@ -308,11 +308,11 @@ class Format { if (phpversion() >= '5.4.0') $flags |= ENT_HTML401; - return html_entity_decode($var, $flags, 'UTF-8'); + return htmlspecialchars_decode($var, $flags); } function input($var) { - return Format::htmlencode($var); + return Format::htmlchars($var); } //Format text for display.. @@ -345,19 +345,17 @@ class Format { } //make urls clickable. Mainly for display - function clickableurls($text, $trampoline=true) { + function clickableurls($text, $target='_blank') { global $ost; - $token = $ost->getLinkToken(); - // Find all text between tags $text = preg_replace_callback(':^[^<]+|>[^<]+:', - function($match) use ($token, $trampoline) { + function($match) { // Scan for things that look like URLs return preg_replace_callback( '`(?<!>)(((f|ht)tp(s?)://|(?<!//)www\.)([-+~%/.\w]+)(?:[-?#+=&;%@.\w]*)?)' .'|(\b[_\.0-9a-z-]+@([0-9a-z][0-9a-z-]+\.)+[a-z]{2,4})`', - function ($match) use ($token, $trampoline) { + function ($match) { if ($match[1]) { while (in_array(substr($match[1], -1), array('.','?','-',':',';'))) { @@ -367,13 +365,9 @@ class Format { if (strpos($match[2], '//') === false) { $match[1] = 'http://' . $match[1]; } - if ($trampoline) - return '<a href="l.php?url='.urlencode($match[1]) - .sprintf('&auth=%s" target="_blank">', $token) - .$match[1].'</a>'.$match[9]; - else - return sprintf('<a href="%s">%s</a>%s', - $match[1], $match[1], $match[9]); + + return sprintf('<a href="%s">%s</a>%s', + $match[1], $match[1], $match[9]); } elseif ($match[6]) { return sprintf('<a href="mailto:%1$s" target="_blank">%1$s</a>', $match[6]); @@ -386,35 +380,20 @@ class Format { // Now change @href and @src attributes to come back through our // system as well $config = array( - 'hook_tag' => function($e, $a=0) use ($token) { + 'hook_tag' => function($e, $a=0) use ($target) { static $eE = array('area'=>1, 'br'=>1, 'col'=>1, 'embed'=>1, 'hr'=>1, 'img'=>1, 'input'=>1, 'isindex'=>1, 'param'=>1); if ($e == 'a' && $a) { - if (isset($a['href']) - && strpos($a['href'], 'mailto:') !== 0 - && strpos($a['href'], 'l.php?') === false) - $a['href'] = 'l.php?url='.urlencode($a['href']) - .'&auth='.$token; - // ALL link targets open in a new tab - $a['target'] = '_blank'; + $a['target'] = $target; $a['class'] = 'no-pjax'; } - // Images which are external are rewritten to <div - // data-src='url...'/> - elseif ($e == 'span' && $a && isset($a['data-src'])) - $a['data-src'] = 'l.php?url='.urlencode($a['data-src']) - .'&auth='.$token; - // URLs for videos need to route too - elseif ($e == 'iframe' && $a && isset($a['src'])) - $a['src'] = 'l.php?url='.urlencode($a['src']) - .'&auth='.$token; + $at = ''; if (is_array($a)) { foreach ($a as $k=>$v) $at .= " $k=\"$v\""; return "<{$e}{$at}".(isset($eE[$e])?" /":"").">"; - } - else { + } else { return "</{$e}>"; } }, @@ -430,14 +409,14 @@ class Format { } - function viewableImages($html, $script='image.php') { + function viewableImages($html, $script=false) { return preg_replace_callback('/"cid:([\w._-]{32})"/', function($match) use ($script) { $hash = $match[1]; if (!($file = AttachmentFile::lookup($hash))) return $match[0]; - return sprintf('"%s?h=%s" data-cid="%s"', - $script, $file->getDownloadHash(), $match[1]); + return sprintf('"%s" data-cid="%s"', + $file->getDownloadUrl(false, 'inline', $script), $match[1]); }, $html); } diff --git a/include/class.forms.php b/include/class.forms.php index 4188797bddf25a7496b4e6a382224c1651b8e296..f10b8b9343a02bb67afdf144a5369b99fca519e5 100644 --- a/include/class.forms.php +++ b/include/class.forms.php @@ -1140,27 +1140,33 @@ class ChoiceField extends FormField { function to_php($value) { if (is_string($value)) - $array = JsonDataParser::parse($value) ?: $value; - else - $array = $value; - $config = $this->getConfiguration(); - if (!$config['multiselect']) { - if (is_array($array) && count($array) < 2) { - reset($array); - return key($array); - } - if (is_string($array) && strpos($array, ',') !== false) { - list($array,) = explode(',', $array, 2); + $value = JsonDataParser::parse($value) ?: $value; + + // CDATA table may be built with comma-separated key,value,key,value + if (is_string($value)) { + $values = array(); + $choices = $this->getChoices(); + foreach (explode(',', $value) as $V) { + if (isset($choices[$V])) + $values[$V] = $choices[$V]; } + if (array_filter($values)) + $value = $values; + } + $config = $this->getConfiguration(); + if (!$config['multiselect'] && is_array($value) && count($value) < 2) { + reset($value); + return key($value); } - return $array; + return $value; } function toString($value) { - $selection = $this->getChoice($value); - return is_array($selection) - ? (implode(', ', array_filter($selection)) ?: $value) - : (string) $selection; + if (!is_array($value)) + $value = $this->getChoice($value); + if (is_array($value)) + return implode(', ', $value); + return (string) $value; } function getChoice($value) { @@ -2027,6 +2033,10 @@ class FileUploadField extends FormField { else { if ($ext[0] != '.') $ext = '.' . $ext; + + // Ensure that the extension is lower-cased for comparison latr + $ext = strtolower($ext); + // Add this to the MIME types list so it can be exported to // the @accept attribute if (!isset($extensions[$ext])) @@ -2083,10 +2093,8 @@ class FileUploadField extends FormField { function display($value) { $links = array(); foreach ($this->getFiles() as $f) { - $hash = strtolower($f['key'] - . md5($f['id'].session_id().strtolower($f['key']))); - $links[] = sprintf('<a class="no-pjax" href="file.php?h=%s">%s</a>', - $hash, Format::htmlchars($f['name'])); + $links[] = sprintf('<a class="no-pjax" href="%s">%s</a>', + Format::htmlchars($f['download_url']), Format::htmlchars($f['name'])); } return implode('<br/>', $links); } @@ -2403,7 +2411,7 @@ class ChoicesWidget extends Widget { } $values = $this->value; - if (!is_array($values) && $values) { + if (!is_array($values) && isset($values)) { $values = array($values => $this->field->getChoice($values)); } @@ -2666,6 +2674,7 @@ class FileUploadWidget extends Widget { 'name' => $file->getName(), 'type' => $file->getType(), 'size' => $file->getSize(), + 'download_url' => $file->getDownloadUrl(), ); } } diff --git a/include/class.http.php b/include/class.http.php index 18eeb2e1be6db38e00abd8ac3adc4c5632cd7d41..c358355a7cd438c621e25d9eabc548709c3ad128 100644 --- a/include/class.http.php +++ b/include/class.http.php @@ -63,11 +63,13 @@ class Http { function cacheable($etag, $modified, $ttl=3600) { // Thanks, http://stackoverflow.com/a/1583753/1025836 - $last_modified = Misc::db2gmtime($modified); - header("Last-Modified: ".date('D, d M y H:i:s', $last_modified)." GMT", false); + // Timezone doesn't matter here — but the time needs to be + // consistent round trip to the browser and back. + $last_modified = strtotime($modified." GMT"); + header("Last-Modified: ".date('D, d M Y H:i:s', $last_modified)." GMT", false); header('ETag: "'.$etag.'"'); header("Cache-Control: private, max-age=$ttl"); - header('Expires: ' . gmdate(DATE_RFC822, time() + $ttl)." GMT"); + header('Expires: ' . gmdate('D, d M Y H:i:s', Misc::gmtime() + $ttl)." GMT"); header('Pragma: private'); if (@strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $last_modified || @trim($_SERVER['HTTP_IF_NONE_MATCH'], '" ') == $etag) { @@ -97,9 +99,8 @@ class Http { function download($filename, $type, $data=null, $disposition='attachment') { header('Pragma: private'); - header('Expires: 0'); header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); - header('Cache-Control: private'); + header('Cache-Control: private', false); header('Content-Type: '.$type); header(sprintf('Content-Disposition: %s; %s', $disposition, @@ -111,5 +112,10 @@ class Http { exit; } } + + static function build_query($vars, $encode=true, $separator='&') { + return http_build_query( + ($encode ? Format::htmlchars($vars) : $vars), '', $separator); + } } ?> diff --git a/include/class.nav.php b/include/class.nav.php index e8359e00d44eddd282488283ab6f1c524fda6006..d3cbb943dfe2cd0e219060dea7802482f8d23585 100644 --- a/include/class.nav.php +++ b/include/class.nav.php @@ -125,6 +125,7 @@ class StaffNav { } function getSubMenus(){ //Private. + global $cfg; $staff = $this->staff; $submenus=array(); @@ -163,7 +164,7 @@ class StaffNav { if($staff) { if ($staff->getRole()->hasPerm(FAQ::PERM_MANAGE)) $subnav[]=array('desc'=>__('Categories'),'href'=>'categories.php','iconclass'=>'faq-categories'); - if ($staff->getRole()->hasPerm(CannedModel::PERM_MANAGE)) + if ($cfg->isCannedResponseEnabled() && $staff->getRole()->hasPerm(CannedModel::PERM_MANAGE)) $subnav[]=array('desc'=>__('Canned Responses'),'href'=>'canned.php','iconclass'=>'canned'); } break; diff --git a/include/class.organization.php b/include/class.organization.php index fa0ffc9cf568bc85ee20eb8e6e2726bba8f1ac81..fbaa19df50dd8328b900e2c235421a3148862c15 100644 --- a/include/class.organization.php +++ b/include/class.organization.php @@ -261,6 +261,20 @@ class Organization extends OrganizationModel { return (string) $this->getName(); } + function asVar() { + return (string) $this->getName(); + } + + function getVar($tag) { + if($tag && is_callable(array($this, 'get'.ucfirst($tag)))) + return call_user_func(array($this, 'get'.ucfirst($tag))); + + $tag = mb_strtolower($tag); + foreach ($this->getDynamicData() as $e) + if ($a = $e->getAnswer($tag)) + return $a; + } + function update($vars, &$errors) { $valid = true; diff --git a/include/class.page.php b/include/class.page.php index 18f92d64a280a17d4bfe288be9317a34fcdef2bd..7e7d8d7898bc347e7232ca1b1b73439fba3c4188 100644 --- a/include/class.page.php +++ b/include/class.page.php @@ -65,7 +65,7 @@ class Page extends VerySimpleModel { return $this->_getLocal('body', $lang); } function getBodyWithImages() { - return Format::viewableImages($this->getLocalBody(), ROOT_PATH.'image.php'); + return Format::viewableImages($this->getLocalBody()); } function _getLocal($what, $lang=false) { diff --git a/include/class.pagenate.php b/include/class.pagenate.php index acec4fb6cacbe6fb6b69d81c4267fcfa2369c507..2d6e8b8505233b5535432372d4848c145b6e4749 100644 --- a/include/class.pagenate.php +++ b/include/class.pagenate.php @@ -38,14 +38,19 @@ class PageNate { } $this->setURL($url); } - function setURL($url='',$vars=''){ - if($url){ - if(strpos($url,'?')===false) - $url=$url.'?'; - }else{ - $url=THISPAGE.'?'; + + function setURL($url='',$vars='') { + if ($url) { + if (strpos($url, '?')===false) + $url .= '?'; + } else { + $url = THISPAGE.'?'; } - $this->url=$url.$vars; + + if ($vars && is_array($vars)) + $vars = Http::build_query($vars); + + $this->url = $url.$vars; } function getStart() { diff --git a/include/class.staff.php b/include/class.staff.php index 9ffd6bdeaf8a3ac738eb8fcb61ac5aa662aa942a..7e74fe9651562c43449014972eef08e71377c4ff 100644 --- a/include/class.staff.php +++ b/include/class.staff.php @@ -774,6 +774,20 @@ implements AuthenticatedUser { if(!$vars['group_id']) $errors['group_id']=__('Group is required'); + // Ensure we will still have an administrator with access + if ($vars['isadmin'] !== '1' || $vars['isactive'] !== '1') { + $sql = 'select count(*), max(staff_id) from '.STAFF_TABLE + .' WHERE isadmin=1 and isactive=1'; + if (($res = db_query($sql)) + && (list($count, $sid) = db_fetch_row($res))) { + if ($count == 1 && $sid = $id) { + $errors['isadmin'] = __( + 'Cowardly refusing to remove or lock out the only active administrator' + ); + } + } + } + if ($errors) return false; diff --git a/include/class.thread.php b/include/class.thread.php index 724471ed761666687721b266ab1243f9b4dfd2da..d3abcf411210dc6154f38d09a8a68a530838f71d 100644 --- a/include/class.thread.php +++ b/include/class.thread.php @@ -552,12 +552,11 @@ class ThreadEntry { return $this->attachments->getAll(false); } - function getAttachmentUrls($script='image.php') { + function getAttachmentUrls() { $json = array(); foreach ($this->getAttachments() as $att) { $json[$att['key']] = array( - 'download_url' => sprintf('attachment.php?id=%d&h=%s', - $att['attach_id'], $att['download']), + 'download_url' => $att['download_url'], 'filename' => $att['name'], ); } @@ -574,14 +573,8 @@ class ThreadEntry { if ($att['size']) $size=sprintf('<em>(%s)</em>', Format::file_size($att['size'])); - $str.=sprintf('<a class="Icon file no-pjax" href="%s?id=%d&h=%s" target="%s">%s</a>%s %s', - $file, - $att['attach_id'], - $att['download'], - $target, - Format::htmlchars($att['name']), - $size, - $separator); + $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); } return $str; @@ -867,7 +860,10 @@ class ThreadEntry { $match = array(); if ($subject && $mailinfo['email'] - && preg_match("/\b#(\S+)/u", $subject, $match) + // Required `#` followed by one or more of + // punctuation (-) then letters, numbers, and symbols + // (Try not to match closing punctuation (`]`) in [#12345]) + && preg_match("/#((\p{P}*[^\p{C}\p{Z}\p{P}]+)+)/u", $subject, $match) //Lookup by ticket number && ($ticket = Ticket::lookupByNumber($match[1])) //Lookup the user using the email address @@ -1236,6 +1232,11 @@ class ThreadEntryBody /* extends SplString */ { return $this->display('html'); } + function asVar() { + // Email template, assume HTML + return $this->display('email'); + } + function display($format=false) { throw new Exception('display: Abstract display() method not implemented'); } @@ -1271,21 +1272,15 @@ class TextThreadEntryBody extends ThreadEntryBody { switch ($output) { case 'html': - return '<div style="white-space:pre-wrap">' - .Format::clickableurls(Format::htmlchars($this->body)).'</div>'; case 'email': - return '<div style="white-space:pre-wrap">'.$this->body.'</div>'; + return '<div style="white-space:pre-wrap">' + .Format::htmlchars($this->body).'</div>'; case 'pdf': return nl2br($this->body); default: return '<pre>'.$this->body.'</pre>'; } } - - function asVar() { - // Email template, assume HTML - return $this->display('email'); - } } class HtmlThreadEntryBody extends ThreadEntryBody { function __construct($body, $options=array()) { @@ -1326,7 +1321,7 @@ class HtmlThreadEntryBody extends ThreadEntryBody { case 'email': return $this->body; case 'pdf': - return Format::clickableurls($this->body, false); + return Format::clickableurls($this->body); default: return Format::display($this->body); } diff --git a/include/class.ticket.php b/include/class.ticket.php index b1b3ea30cddb714508d9cca00235dd7c4225eda1..1dd62e97cf49c0be0a0a727260f54062f8d0c8b3 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -1072,7 +1072,8 @@ class Ticket { } //Status helper. - function setStatus($status, $comments='', &$errors=array()) { + + function setStatus($status, $comments='', &$errors=array(), $set_closing_agent=true) { global $thisstaff; if (!$thisstaff || !($role=$thisstaff->getRole($this->getDeptId()))) @@ -1116,7 +1117,7 @@ class Ticket { return false; } $sql.=', closed=NOW(), lastupdate=NOW(), duedate=NULL '; - if ($thisstaff) + if ($thisstaff && $set_closing_agent) $sql.=', staff_id='.db_input($thisstaff->getId()); $ecb = function($t) { @@ -1148,21 +1149,24 @@ class Ticket { if (!db_query($sql) || !db_affected_rows()) return false; - // Log status change b4 reload - $note = sprintf(__('Status changed from %s to %s by %s'), - $this->getStatus(), - $status, - $thisstaff ?: 'SYSTEM'); + // Log status change b4 reload — if currently has a status. (On new + // ticket, the ticket is opened and thereafter the status is set to + // the requested status). + if ($current_status = $this->getStatus()) { + $note = sprintf(__('Status changed from %s to %s by %s'), + $this->getStatus(), + $status, + $thisstaff ?: 'SYSTEM'); + + $alert = false; + if ($comments) { + $note .= sprintf('<hr>%s', $comments); + // Send out alerts if comments are included + $alert = true; + } - $alert = false; - if ($comments) { - $note .= sprintf('<hr>%s', $comments); - // Send out alerts if comments are included - $alert = true; + $this->logNote(__('Status Changed'), $note, $thisstaff, $alert); } - - $this->logNote(__('Status Changed'), $note, $thisstaff, $alert); - // Log events via callback if ($ecb) $ecb($this); @@ -1264,7 +1268,7 @@ class Ticket { //Send alert to out sleepy & idle staff. if ($alertstaff && $cfg->alertONNewTicket() - && ($email=$cfg->getAlertEmail()) + && ($email=$dept->getAlertEmail()) && ($msg=$tpl->getNewTicketAlertMsgTemplate())) { $msg = $this->replaceVars($msg->asArray(), array('message' => $message)); @@ -1427,7 +1431,10 @@ class Ticket { } // Reopen if closed AND reopenable - if ($this->isClosed() && $this->isReopenable()) + // We're also checking autorespond flag because we don't want to + // reopen closed tickets on auto-reply from end user. This is not to + // confused with autorespond on new message setting + if ($autorespond && $this->isClosed() && $this->isReopenable()) $this->reopen(); // Figure out the user @@ -1498,7 +1505,7 @@ class Ticket { $dept = $this->getDept(); if(!$dept || !($tpl = $dept->getTemplate()) - || !($email = $cfg->getAlertEmail())) + || !($email = $dept->getAlertEmail())) return true; //recipients @@ -1555,7 +1562,7 @@ class Ticket { //Get the message template if(($tpl = $dept->getTemplate()) && ($msg=$tpl->getOverdueAlertMsgTemplate()) - && ($email=$cfg->getAlertEmail())) { + && ($email = $dept->getAlertEmail())) { $msg = $this->replaceVars($msg->asArray(), array('comments' => $comments)); @@ -1740,7 +1747,7 @@ class Ticket { if(!$alert || !$cfg->alertONTransfer() || !($dept=$this->getDept())) return true; //no alerts!! - if(($email=$cfg->getAlertEmail()) + if (($email = $dept->getAlertEmail()) && ($tpl = $dept->getTemplate()) && ($msg=$tpl->getTransferAlertMsgTemplate())) { @@ -1969,7 +1976,7 @@ class Ticket { 'thread'=>$message); //If enabled...send alert to staff (New Message Alert) if($cfg->alertONNewMessage() - && ($email = $cfg->getAlertEmail()) + && ($email = $dept->getAlertEmail()) && ($tpl = $dept->getTemplate()) && ($msg = $tpl->getNewMessageAlertMsgTemplate())) { @@ -2227,7 +2234,7 @@ class Ticket { if(!$alert || !$cfg->alertONNewNote() || !($dept=$this->getDept())) return $note; - if(($email=$cfg->getAlertEmail()) + if (($email = $dept->getAlertEmail()) && ($tpl = $dept->getTemplate()) && ($msg=$tpl->getNoteAlertMsgTemplate())) { @@ -2596,12 +2603,70 @@ class Ticket { return db_fetch_array(db_query($sql)); } + protected function filterTicketData($origin, $vars, $forms, $user=false) { + global $cfg; + + // Unset all the filter data field data in case things change + // during recursive calls + foreach ($vars as $k=>$v) + if (strpos($k, 'field.') === 0) + unset($vars[$k]); + + foreach ($forms as $F) { + if ($F) { + $vars += $F->getFilterData(); + } + } + + // Add in user and organization data for filtering + if ($user) { + $vars += $user->getFilterData(); + $vars['email'] = $user->getEmail(); + $vars['name'] = $user->getName(); + if ($org = $user->getOrganization()) { + $vars += $org->getFilterData(); + } + } + // Unpack the basic user information + else { + $interesting = array('name', 'email'); + $user_form = UserForm::getUserForm()->getForm($vars); + // Add all the user-entered info for filtering + foreach ($user_form->getFields() as $f) { + $vars['field.'.$f->get('id')] = $f->toString($f->getClean()); + if (in_array($f->get('name'), $interesting)) + $vars[$f->get('name')] = $vars['field.'.$f->get('id')]; + } + // Add in organization data if one exists for this email domain + list($mailbox, $domain) = explode('@', $vars['email'], 2); + if ($org = Organization::forDomain($domain)) { + $vars += $org->getFilterData(); + } + } + + try { + // Make sure the email address is not banned + if (TicketFilter::isBanned($vars['email'])) { + throw new RejectedException(Banlist::getFilter(), $vars); + } + + // Init ticket filters... + $ticket_filter = new TicketFilter($origin, $vars); + $ticket_filter->apply($vars); + } + catch (FilterDataChanged $ex) { + // Don't pass user recursively, assume the user has changed + return self::filterTicketData($origin, $vars, $forms); + } + return $vars; + } + /* * The mother of all functions...You break it you fix it! * * $autorespond and $alertstaff overrides config settings... */ - static function create($vars, &$errors, $origin, $autorespond=true, + static function create(&$vars, &$errors, $origin, $autorespond=true, $alertstaff=true) { global $ost, $cfg, $thisclient, $_FILES; @@ -2653,81 +2718,8 @@ class Ticket { if (!$form->isValid($field_filter('ticket'))) $errors += $form->errors(); - // Unpack dynamic variables into $vars for filter application - $vars += $form->getFilterData(); - - // Unpack the basic user information - if ($vars['uid'] && ($user = User::lookup($vars['uid']))) { - $vars['email'] = $user->getEmail(); - $vars['name'] = $user->getName(); - // Add in user and organization data for filtering - $vars += $user->getFilterData(); - if ($org = $user->getOrganization()) { - $vars += $org->getFilterData(); - } - } - else { - $interesting = array('name', 'email'); - $user_form = UserForm::getUserForm()->getForm($vars); - // Add all the user-entered info for filtering - foreach ($user_form->getFields() as $f) { - $vars['field.'.$f->get('id')] = $f->toString($f->getClean()); - if (in_array($f->get('name'), $interesting)) - $vars[$f->get('name')] = $vars['field.'.$f->get('id')]; - } - // Add in organization data if one exists for this email domain - list($mailbox, $domain) = explode('@', $vars['email'], 2); - if ($org = Organization::forDomain($domain)) { - $vars += $org->getFilterData(); - } - } - - - //Check for 403 - if ($vars['email'] - && Validator::is_email($vars['email'])) { - - //Make sure the email address is not banned - if (TicketFilter::isBanned($vars['email'])) { - return $reject_ticket(sprintf(_S('Banned email - %s'), $vars['email'])); - } - - //Make sure the open ticket limit hasn't been reached. (LOOP CONTROL) - if ($cfg->getMaxOpenTickets() > 0 - && strcasecmp($origin, 'staff') - && ($_user=TicketUser::lookupByEmail($vars['email'])) - && ($openTickets=$_user->getNumOpenTickets()) - && ($openTickets>=$cfg->getMaxOpenTickets()) ) { - - $errors = array('err' => __("You've reached the maximum open tickets allowed.")); - $ost->logWarning(sprintf(_S('Ticket denied - %s'), $vars['email']), - sprintf(_S('Max open tickets (%1$d) reached for %2$s'), - $cfg->getMaxOpenTickets(), $vars['email']), - false); - - return 0; - } - } - - if ($vars['topicId']) { - if (($__topic=Topic::lookup($vars['topicId'])) - && $__form = $__topic->getForm() - ) { - $__form = $__form->instanciate(); - $__form->setSource($vars); - $vars += $__form->getFilterData(); - } - } - - //Init ticket filters... - $ticket_filter = new TicketFilter($origin, $vars); - // Make sure email contents should not be rejected - if ($ticket_filter - && ($filter=$ticket_filter->shouldReject())) { - return $reject_ticket( - sprintf(_S('Ticket rejected (%s) by filter "%s"'), - $vars['email'], $filter->getName())); - } + if ($vars['uid']) + $user = User::lookup($vars['uid']); $id=0; $fields=array(); @@ -2767,7 +2759,42 @@ class Ticket { if (!$errors) { # Perform ticket filter actions on the new ticket arguments - if ($ticket_filter) $ticket_filter->apply($vars); + $__form = null; + if ($vars['topicId']) { + if (($__topic=Topic::lookup($vars['topicId'])) + && ($__form = $__topic->getForm()) + ) { + $__form = $__form->instanciate(); + $__form->setSource($vars); + } + } + + try { + $vars = self::filterTicketData($origin, $vars, + array($form, $__form), $user); + } + catch (RejectedException $ex) { + return $reject_ticket( + sprintf(_S('Ticket rejected (%s) by filter "%s"'), + $ex->vars['email'], $ex->getRejectingFilter()->getName()) + ); + } + + //Make sure the open ticket limit hasn't been reached. (LOOP CONTROL) + if ($cfg->getMaxOpenTickets() > 0 + && strcasecmp($origin, 'staff') + && ($_user=TicketUser::lookupByEmail($vars['email'])) + && ($openTickets=$_user->getNumOpenTickets()) + && ($openTickets>=$cfg->getMaxOpenTickets()) ) { + + $errors = array('err' => __("You've reached the maximum open tickets allowed.")); + $ost->logWarning(sprintf(_S('Ticket denied - %s'), $vars['email']), + sprintf(_S('Max open tickets (%1$d) reached for %2$s'), + $cfg->getMaxOpenTickets(), $vars['email']), + false); + + return 0; + } // Allow vars to be changed in ticket filter and applied to the user // account created or detected @@ -2914,7 +2941,6 @@ class Ticket { .' ,`number`='.db_input($number) .' ,dept_id='.db_input($deptId) .' ,topic_id='.db_input($topicId) - .' ,status_id='.db_input($statusId) .' ,ip_address='.db_input($ipaddress) .' ,source='.db_input($source); @@ -2995,12 +3021,20 @@ class Ticket { if ($vars['staffId']) $ticket->assignToStaff($vars['staffId'], _S('Auto Assignment')); if ($vars['teamId']) - $ticket->assignToTeam($vars['teamId'], _S('Auto Assignment')); + // No team alert if also assigned to an individual agent + $ticket->assignToTeam($vars['teamId'], _S('Auto Assignment'), + !$vars['staffId']); } // Update the estimated due date in the database $ticket->updateEstDueDate(); + // Apply requested status — this should be done AFTER assignment, + // because if it is requested to be closed, it should not cause the + // ticket to be reopened for assignment. + if ($statusId) + $ticket->setStatus($statusId, false, $errors, false); + /********** double check auto-response ************/ //Override auto responder if the FROM email is one of the internal emails...loop control. if($autorespond && (Email::getIdByEmail($ticket->getEmail()))) @@ -3096,12 +3130,7 @@ class Ticket { $vars['response'] = $ticket->replaceVars($vars['response']); // $vars['cannedatachments'] contains the attachments placed on // the response form. - if(($response=$ticket->postReply($vars, $errors, false))) { - //Only state supported is closed on response - if(isset($vars['ticket_state']) && - $role->hasPerm(TicketModel::PERM_CLOSE)) - $ticket->setState($vars['ticket_state']); - } + $response = $ticket->postReply($vars, $errors, false); } // Not assigned...save optional note if any @@ -3115,10 +3144,16 @@ class Ticket { } $ticket->reload(); + $dept = $ticket->getDept(); + + // See if we need to skip auto-response. + $autorespond = isset($create_vars['autorespond']) + ? $create_vars['autorespond'] : true; - if(!$cfg->notifyONNewStaffTicket() + if (!$autorespond || !isset($vars['alertuser']) - || !($dept=$ticket->getDept())) + || !$dept->autoRespONNewTicket() + || !$cfg->notifyONNewStaffTicket()) return $ticket; //No alerts. //Send Notice to user --- if requested AND enabled!! diff --git a/include/class.user.php b/include/class.user.php index d12bd009e579b6343788d9a87dab3ce6a989be56..5db55087c1538f53bd2acb0311c3022165d2c7dc 100644 --- a/include/class.user.php +++ b/include/class.user.php @@ -308,7 +308,8 @@ class User extends UserModel { // Add in special `name` and `email` fields foreach (array('name', 'email') as $name) { if ($f = $entry->getForm()->getField($name)) - $vars['field.'.$f->get('id')] = $this->getName(); + $vars['field.'.$f->get('id')] = + $name == 'name' ? $this->getName() : $this->getEmail(); } } return $vars; @@ -568,6 +569,11 @@ class User extends UserModel { // Delete emails. $this->emails->expunge(); + // Drop dynamic data + foreach ($this->getDynamicData() as $cd) { + $cd->delete(); + } + // Delete user return parent::delete(); } diff --git a/include/client/tickets.inc.php b/include/client/tickets.inc.php index e39e43671662b1eff01b62e89c79a9d843f1e6d8..d88ec62d51a78227497452396abf93111a0a9a10 100644 --- a/include/client/tickets.inc.php +++ b/include/client/tickets.inc.php @@ -3,10 +3,10 @@ if(!defined('OSTCLIENTINC') || !is_object($thisclient) || !$thisclient->isValid( $tickets = TicketModel::objects(); -$qstr='&'; //Query string collector +$qs = array(); $status=null; if(isset($_REQUEST['status'])) { //Query string status has nothing to do with the real status used below. - $qstr.='status='.urlencode($_REQUEST['status']); + $qs += array('status' => $_REQUEST['status']); //Status we are actually going to use on the query...making sure it is clean! $status=strtolower($_REQUEST['status']); switch(strtolower($_REQUEST['status'])) { @@ -51,7 +51,7 @@ $tickets->filter(Q::any(array( // Perform basic search $search=($_REQUEST['a']=='search' && $_REQUEST['q']); if($search) { - $qstr.='&a='.urlencode($_REQUEST['a']).'&q='.urlencode($_REQUEST['q']); + $qs += array('a' => $_REQUEST['a'], 'q' => $_REQUEST['q']); if (is_numeric($_REQUEST['q'])) { $tickets->filter(array('number__startswith'=>$_REQUEST['q'])); } else { //Deep search! @@ -65,19 +65,11 @@ TicketForm::ensureDynamicDataView(); $total=$tickets->count(); $page=($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1; $pageNav=new Pagenate($total, $page, PAGE_LIMIT); -$pageNav->setURL('tickets.php',$qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order'])); +$qstr = '&'. Http::build_query($qs); +$qs += array('sort' => $_REQUEST['sort'], 'order' => $_REQUEST['order']); +$pageNav->setURL('tickets.php', $qs); $pageNav->paginate($tickets); -//more stuff... -$qselect.=' ,count(DISTINCT attach.id) as attachments '; -$qfrom.=' LEFT JOIN '.THREAD_ENTRY_TABLE.' entry - ON (entry.thread_id=thread.id AND entry.`type` IN ("M", "R")) '; -$qfrom.=' LEFT JOIN '.ATTACHMENT_TABLE.' attach - ON (attach.object_id=entry.id AND attach.`type` = "H") '; -$qgroup=' GROUP BY ticket.ticket_id'; - -$query="$qselect $qfrom $qwhere $qgroup ORDER BY $order_by $order LIMIT ".$pageNav->getStart().",".$pageNav->getLimit(); -//echo $query; $showing =$total ? $pageNav->showing() : ""; if(!$results_type) { diff --git a/include/client/view.inc.php b/include/client/view.inc.php index 4c93a7b3bcb5b21eeac0657c1ccaf84ebe133b53..fe2f1b8aa481d93f4178743bfe4808ce82cda4e8 100644 --- a/include/client/view.inc.php +++ b/include/client/view.inc.php @@ -126,7 +126,7 @@ if($ticket->getThreadCount() && ($thread=$ticket->getClientThread())) { <span><?php echo $poster; ?></span> </div> </th></tr> - <tr><td class="thread-body"><div><?php echo $entry['body']->toHtml(); ?></div></td></tr> + <tr><td class="thread-body"><div><?php echo Format::clickableurls($entry['body']->toHtml()); ?></div></td></tr> <?php if($entry['attachments'] && ($tentry=$ticket->getThreadEntry($entry['id'])) diff --git a/include/html2text.php b/include/html2text.php index 0c1ec08330d8a8c77acd80d1f187d6b6c0d7d4a4..1d12c733c3d3582bc3daede37cd9580b08a88314 100644 --- a/include/html2text.php +++ b/include/html2text.php @@ -448,6 +448,11 @@ class HtmlAElement extends HtmlInlineElement { $href = substr($href, 7); $output = (($href != $output) ? "$href " : '') . "<$output>"; } elseif (mb_strwidth($href) > $width / 2) { + if (mb_strwidth($output) > $width / 2) { + // Parse URL and use relative path part + if ($PU = parse_url($output)) + $output = $PU['host'] . $PU['path']; + } if ($href != $output) $id = $this->getRoot()->addFootnote($output, $href); $output = "[$output][$id]"; diff --git a/include/mpdf/mpdf.php b/include/mpdf/mpdf.php index af04fa3b483c53e838bd8f4835d2443dbe073764..9c7fe98ad44cd7a9848a0890d6583bfeefd52710 100755 --- a/include/mpdf/mpdf.php +++ b/include/mpdf/mpdf.php @@ -31136,6 +31136,7 @@ function purify_utf8($html,$lo=true) { function purify_utf8_text($txt) { // For TEXT // Make sure UTF-8 string of characters + if ($txt === null) $txt = ''; if (!$this->is_utf8($txt)) { $this->Error("Text contains invalid UTF-8 character(s)"); } $txt = preg_replace("/\r/", "", $txt ); diff --git a/include/staff/apikey.inc.php b/include/staff/apikey.inc.php index 1e6f845cf05beb9c4d65710b951346fc239a3fcc..fdf30da4bc844ed0cbdef37c3902a3916eca2716 100644 --- a/include/staff/apikey.inc.php +++ b/include/staff/apikey.inc.php @@ -1,24 +1,23 @@ <?php if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access Denied'); -$info=array(); -$qstr=''; +$info=$qs = array(); if($api && $_REQUEST['a']!='add'){ $title=__('Update API Key'); $action='update'; $submit_text=__('Save Changes'); $info=$api->getHashtable(); - $qstr.='&id='.$api->getId(); + $qs += array('id' => $api->getId()); }else { $title=__('Add New API Key'); $action='add'; $submit_text=__('Add Key'); $info['isactive']=isset($info['isactive'])?$info['isactive']:1; - $qstr.='&a='.urlencode($_REQUEST['a']); + $qs += array('a' => $_REQUEST['a']); } $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); ?> -<form action="apikeys.php?<?php echo $qstr; ?>" method="post" id="save"> +<form action="apikeys.php?<?php echo Http::build_query($qs); ?>" method="post" id="save"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> diff --git a/include/staff/apikeys.inc.php b/include/staff/apikeys.inc.php index e5430c14d1fc3e200a44a7b9116d6826c3202bf9..ddaf990966931e54b5753a4a2763d11257a239d4 100644 --- a/include/staff/apikeys.inc.php +++ b/include/staff/apikeys.inc.php @@ -1,7 +1,7 @@ <?php if(!defined('OSTADMININC') || !$thisstaff->isAdmin()) die('Access Denied'); -$qstr=''; +$qs = array(); $sql='SELECT * FROM '.API_KEY_TABLE.' WHERE 1'; $sortOptions=array('key'=>'apikey','status'=>'isactive','ip'=>'ipaddr','date'=>'created','created'=>'created','updated'=>'updated'); $orderWays=array('DESC'=>'DESC','ASC'=>'ASC'); @@ -27,9 +27,11 @@ $order_by="$order_column $order "; $total=db_count('SELECT count(*) FROM '.API_KEY_TABLE.' '); $page=($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1; $pageNav=new Pagenate($total,$page,PAGE_LIMIT); -$pageNav->setURL('apikeys.php',$qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order'])); -//Ok..lets roll...create the actual query -$qstr.='&order='.($order=='DESC'?'ASC':'DESC'); +$qstr = '&'. Http::build_query($qs); +$qs += array('sort' => $_REQUEST['sort'], 'order' => $_REQUEST['order']); +$pageNav->setURL('apikeys.php', $qs); + +$qstr.='&order='.($order=='DESC'?'ASC':'DESC'); $query="$sql ORDER BY $order_by LIMIT ".$pageNav->getStart().",".$pageNav->getLimit(); $res=db_query($query); if($res && ($num=db_num_rows($res))) diff --git a/include/staff/banlist.inc.php b/include/staff/banlist.inc.php index 7c83e9b1370c149f367cdb955a5ae7612600ae89..3454ae4803342c9b2ab52c4904a0c09dc017296f 100644 --- a/include/staff/banlist.inc.php +++ b/include/staff/banlist.inc.php @@ -1,7 +1,7 @@ <?php if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$filter) die('Access Denied'); -$qstr=''; +$qs = array(); $select='SELECT rule.* '; $from='FROM '.FILTER_RULE_TABLE.' rule '; $where='WHERE rule.filter_id='.db_input($filter->getId()); @@ -41,8 +41,10 @@ $order_by="$order_column $order "; $total=db_count('SELECT count(DISTINCT rule.id) '.$from.' '.$where); $page=($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1; $pageNav=new Pagenate($total, $page, PAGE_LIMIT); -$pageNav->setURL('banlist.php',$qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order'])); -$qstr.='&order='.($order=='DESC'?'ASC':'DESC'); +$qstr = '&'. Http::build_query($qs); +$qs += array('sort' => $_REQUEST['sort'], 'order' => $_REQUEST['order']); +$pageNav->setURL('banlist.php', $qs); +$qstr.='&order='.($order=='DESC'?'ASC':'DESC'); $query="$select $from $where ORDER BY $order_by LIMIT ".$pageNav->getStart().",".$pageNav->getLimit(); //echo $query; ?> diff --git a/include/staff/banrule.inc.php b/include/staff/banrule.inc.php index b3fe52347ccc875025eaa05fb490bd3b4a5be7a5..4f98cd148511d0787873a81f07b75e5ddc6c8275 100644 --- a/include/staff/banrule.inc.php +++ b/include/staff/banrule.inc.php @@ -1,25 +1,25 @@ <?php if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access Denied'); -$info=array(); -$qstr=''; +$info=$qs= array(); if($rule && $_REQUEST['a']!='add'){ $title=__('Update Ban Rule'); $action='update'; $submit_text=__('Update'); $info=$rule->getInfo(); $info['id']=$rule->getId(); - $qstr.='&id='.$rule->getId(); + $qs += array('id' => $rule->getId()); }else { $title=__('Add New Email Address to Ban List'); $action='add'; $submit_text=__('Add'); $info['isactive']=isset($info['isactive'])?$info['isactive']:1; - $qstr.='&a='.urlencode($_REQUEST['a']); + $qs += array('a' => $_REQUEST['a']); } + $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); ?> -<form action="banlist.php?<?php echo $qstr; ?>" method="post" id="save"> +<form action="banlist.php?<?php echo Http::build_query($qs); ?>" method="post" id="save"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> diff --git a/include/staff/cannedresponse.inc.php b/include/staff/cannedresponse.inc.php index 57276a6df94fbb504869a5213631005a515a108b..518ad1165310fbf99ee148e5d259c6146e782677 100644 --- a/include/staff/cannedresponse.inc.php +++ b/include/staff/cannedresponse.inc.php @@ -1,14 +1,13 @@ <?php if(!defined('OSTSCPINC') || !$thisstaff) die('Access Denied'); -$info=array(); -$qstr=''; +$info=$qs = array(); if($canned && $_REQUEST['a']!='add'){ $title=__('Update Canned Response'); $action='update'; $submit_text=__('Save Changes'); $info=$canned->getInfo(); $info['id']=$canned->getId(); - $qstr.='&id='.$canned->getId(); + $qs += array('id' => $canned->getId()); // Replace cid: scheme with downloadable URL for inline images $info['response'] = $canned->getResponseWithImages(); $info['notes'] = Format::viewableImages($info['notes']); @@ -17,12 +16,12 @@ if($canned && $_REQUEST['a']!='add'){ $action='create'; $submit_text=__('Add Response'); $info['isenabled']=isset($info['isenabled'])?$info['isenabled']:1; - $qstr.='&a='.$_REQUEST['a']; + $qs += array('a' => $_REQUEST['a']); } $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); ?> -<form action="canned.php?<?php echo $qstr; ?>" method="post" id="save" enctype="multipart/form-data"> +<form action="canned.php?<?php echo Http::build_query($qs); ?>" method="post" id="save" enctype="multipart/form-data"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> diff --git a/include/staff/cannedresponses.inc.php b/include/staff/cannedresponses.inc.php index 1affca425353568f2191fbb5b04bf1e074700ff6..05d098a34fc350ad13033c6e99561f9c424e5389 100644 --- a/include/staff/cannedresponses.inc.php +++ b/include/staff/cannedresponses.inc.php @@ -1,7 +1,7 @@ <?php if(!defined('OSTSCPINC') || !$thisstaff) die('Access Denied'); -$qstr=''; +$qs = array(); $sql='SELECT canned.*, count(attach.file_id) as files, dept.name as department '. ' FROM '.CANNED_TABLE.' canned '. ' LEFT JOIN '.DEPT_TABLE.' dept ON (dept.id=canned.dept_id) '. @@ -36,9 +36,11 @@ $order_by="$order_column $order "; $total=db_count('SELECT count(*) FROM '.CANNED_TABLE.' canned '); $page=($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1; $pageNav=new Pagenate($total, $page, PAGE_LIMIT); -$pageNav->setURL('canned.php',$qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order'])); +$qstr = '&'. Http::build_query($qs); +$qs += array('sort' => $_REQUEST['sort'], 'order' => $_REQUEST['order']); +$pageNav->setURL('canned.php', $qs); //Ok..lets roll...create the actual query -$qstr.='&order='.($order=='DESC'?'ASC':'DESC'); +$qstr .= '&order='.($order=='DESC'?'ASC':'DESC'); $query="$sql GROUP BY canned.canned_id ORDER BY $order_by LIMIT ".$pageNav->getStart().",".$pageNav->getLimit(); $res=db_query($query); if($res && ($num=db_num_rows($res))) diff --git a/include/staff/categories.inc.php b/include/staff/categories.inc.php index 32ad0a8e92fa4b978c3f7194b9d35de5eda53b65..05f0b5108a7de30b33506828084a83cc534f6791 100644 --- a/include/staff/categories.inc.php +++ b/include/staff/categories.inc.php @@ -1,7 +1,7 @@ <?php if(!defined('OSTSCPINC') || !$thisstaff) die('Access Denied'); -$qstr=''; +$qs = array(); $categories = Category::objects() ->annotate(array('faq_count'=>SqlAggregate::COUNT('faqs'))); $sortOptions=array('name'=>'name','type'=>'ispublic','faqs'=>'faq_count','updated'=>'updated'); @@ -25,9 +25,10 @@ $order_by="$order_column $order "; $total=$categories->count(); $page=($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1; $pageNav=new Pagenate($total, $page, PAGE_LIMIT); -$pageNav->setURL('categories.php',$qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order'])); +$qs += array('sort' => $_REQUEST['sort'], 'order' => $_REQUEST['order']); +$pageNav->setURL('categories.php', $qs); +$qstr = '&order='.($order=='DESC'?'ASC':'DESC'); $pageNav->paginate($categories); -$qstr.='&order='.($order=='DESC'?'ASC':'DESC'); if ($total) $showing=$pageNav->showing().' '.__('categories'); @@ -90,7 +91,7 @@ else <a id="selectNone" href="#ckb"><?php echo __('None');?></a> <a id="selectToggle" href="#ckb"><?php echo __('Toggle');?></a> <?php }else{ - echo __('No FAQ categories found.'); + echo __('No FAQ categories found!'); } ?> </td> </tr> diff --git a/include/staff/category.inc.php b/include/staff/category.inc.php index 55926a0aaef1cf2a716cad851466a93da2f58c11..b73a611e9845515663c587b283ce35f44580df67 100644 --- a/include/staff/category.inc.php +++ b/include/staff/category.inc.php @@ -4,7 +4,7 @@ if (!defined('OSTSCPINC') || !$thisstaff die('Access Denied'); $info=array(); -$qstr=''; +$qs = array(); if($category && $_REQUEST['a']!='add'){ $title=__('Update Category').': '.$category->getName(); $action='update'; @@ -12,7 +12,7 @@ if($category && $_REQUEST['a']!='add'){ $info=$category->getHashtable(); $info['id']=$category->getId(); $info['notes'] = Format::viewableImages($category->getNotes()); - $qstr.='&id='.$category->getId(); + $qs += array('id' => $category->getId()); $langs = $cfg->getSecondaryLanguages(); $translations = $category->getAllTranslations(); foreach ($langs as $tag) { @@ -31,12 +31,12 @@ if($category && $_REQUEST['a']!='add'){ $title=__('Add New Category'); $action='create'; $submit_text=__('Add'); - $qstr.='&a='.$_REQUEST['a']; + $qs += array('a' => $_REQUEST['a']); } $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); ?> -<form action="categories.php?<?php echo $qstr; ?>" method="post" id="save"> +<form action="categories.php?<?php echo Http::build_query($qs); ?>" method="post" id="save"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> diff --git a/include/staff/department.inc.php b/include/staff/department.inc.php index 9b6d9d4636ef0740e6f13d5464402d6152b06fae..db23768ef4cfc9b2e8020fa34241c7777647e0fd 100644 --- a/include/staff/department.inc.php +++ b/include/staff/department.inc.php @@ -1,7 +1,6 @@ <?php if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access Denied'); -$info=array(); -$qstr=''; +$info = $qs = array(); if($dept && $_REQUEST['a']!='add') { //Editing Department. $title=__('Update Department'); @@ -10,7 +9,7 @@ if($dept && $_REQUEST['a']!='add') { $info = $dept->getInfo(); $info['id'] = $dept->getId(); $info['groups'] = $dept->getAllowedGroups(); - $qstr.='&id='.$dept->getId(); + $qs += array('id' => $dept->getId()); } else { $title=__('Add New Department'); $action='create'; @@ -21,13 +20,12 @@ if($dept && $_REQUEST['a']!='add') { if (!isset($info['group_membership'])) $info['group_membership'] = 1; - $qstr.='&a='.$_REQUEST['a']; - + $qs += array('a' => $_REQUEST['a']); } $info = Format::htmlchars(($errors && $_POST) ? $_POST : $info); ?> -<form action="departments.php?<?php echo $qstr; ?>" method="post" id="save"> +<form action="departments.php?<?php echo Http::build_query($qs); ?>" method="post" id="save"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> diff --git a/include/staff/departments.inc.php b/include/staff/departments.inc.php index 4b58fea69028f9636061a7cde8a041442cd24f35..443c51c00e525c74bf82569d63d79636a4ac8b75 100644 --- a/include/staff/departments.inc.php +++ b/include/staff/departments.inc.php @@ -2,7 +2,7 @@ if (!defined('OSTADMININC') || !$thisstaff->isAdmin()) die('Access Denied'); -$qstr=''; +$qs = array(); $sortOptions=array( 'name' => 'name', 'type' => 'ispublic', @@ -33,10 +33,11 @@ $$x=' class="'.strtolower($order).'" '; $page = ($_GET['p'] && is_numeric($_GET['p'])) ? $_GET['p'] : 1; $count = Dept::objects()->count(); $pageNav = new Pagenate($count, $page, PAGE_LIMIT); -$_qstr = $qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order']); -$pageNav->setURL('departments.php', $_qstr); +$qstr = '&'. Http::build_query($qs); +$qstr .= '&order='.($order=='DESC' ? 'ASC' : 'DESC'); +$qs += array('sort' => $_REQUEST['sort'], 'order' => $_REQUEST['order']); +$pageNav->setURL('departments.php', $qs); $showing = $pageNav->showing().' '._N('department', 'departments', $count); -$qstr.='&order='.($order=='DESC'?'ASC':'DESC'); ?> <div class="pull-left" style="width:700px;padding-top:5px;"> <h2><?php echo __('Departments');?></h2> @@ -128,7 +129,7 @@ $qstr.='&order='.($order=='DESC'?'ASC':'DESC'); <a id="selectNone" href="#ckb"><?php echo __('None');?></a> <a id="selectToggle" href="#ckb"><?php echo __('Toggle');?></a> <?php }else{ - echo __('No department found'); + echo __('No departments found!'); } ?> </td> </tr> diff --git a/include/staff/directory.inc.php b/include/staff/directory.inc.php index 74269f6ce83cc97aa921b83245400f0ac15f5d66..99c65b521549321909bdf48774258e4685e90f0d 100644 --- a/include/staff/directory.inc.php +++ b/include/staff/directory.inc.php @@ -1,10 +1,6 @@ <?php if(!defined('OSTSTAFFINC') || !$thisstaff || !$thisstaff->isStaff()) die('Access Denied'); -$qstr=''; -$select='SELECT staff.*,CONCAT_WS(" ",firstname,lastname) as name,dept.name as dept '; -$from='FROM '.STAFF_TABLE.' staff '. - 'LEFT JOIN '.DEPT_TABLE.' dept ON(staff.dept_id=dept.id) '; -$where='WHERE staff.isvisible=1 '; +$qs = array(); $agents = Staff::objects() ->filter(array('isvisible'=>1)) @@ -34,7 +30,7 @@ if($_REQUEST['q']) { if($_REQUEST['did'] && is_numeric($_REQUEST['did'])) { $agents->filter(array('dept'=>$_REQUEST['did'])); - $qstr.='&did='.urlencode($_REQUEST['did']); + $qs += array('did' => $_REQUEST['did']); } $sortOptions=array('name'=>'firstname,lastname','email'=>'email','dept'=>'dept__name', @@ -61,11 +57,13 @@ foreach (explode(',', $order_column) as $C) { $total=$agents->count(); $page=($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1; $pageNav=new Pagenate($total, $page, PAGE_LIMIT); -$pageNav->setURL('directory.php',$qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order'])); +$qstr = '&'. Http::build_query($qs); +$qs += array('sort' => $_REQUEST['sort'], 'order' => $_REQUEST['order']); +$pageNav->setURL('directory.php', $qs); $pageNav->paginate($agents); //Ok..lets roll...create the actual query -$qstr.='&order='.($order=='-'?'ASC':'DESC'); +$qstr.='&order='.($order=='DESC' ? 'ASC' : 'DESC'); ?> <h2><?php echo __('Agents');?> diff --git a/include/staff/dynamic-form.inc.php b/include/staff/dynamic-form.inc.php index bf68e752f874f376c1183e4dde7971b85232565b..2d6e64f496f86f99f3743d34869ed8b328c95723 100644 --- a/include/staff/dynamic-form.inc.php +++ b/include/staff/dynamic-form.inc.php @@ -2,7 +2,7 @@ $info=array(); if($form && $_REQUEST['a']!='add') { - $title = __('Update custom form section'); + $title = __('Update form section'); $action = 'update'; $url = "?id=".urlencode($_REQUEST['id']); $submit_text=__('Save Changes'); @@ -25,14 +25,14 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo $action; ?>"> <input type="hidden" name="id" value="<?php echo $info['id']; ?>"> - <h2><?php echo __('Custom Form'); ?></h2> + <h2><?php echo $form ? Format::htmlchars($form->getTitle()) : __('Custom Form'); ?></h2> <table class="form_table" width="940" border="0" cellspacing="0" cellpadding="2"> <thead> <tr> <th colspan="2"> <h4><?php echo $title; ?></h4> <em><?php echo __( - 'Custom forms are used to allow custom data to be associated with tickets' + 'Forms are used to allow for collection of custom data' ); ?></em> </th> </tr> diff --git a/include/staff/dynamic-list.inc.php b/include/staff/dynamic-list.inc.php index c0e009bc46984267efebd989a398ebc3d7701eba..9fe06217332e4afc6e2d2b8a2bccf717a7faafba 100644 --- a/include/staff/dynamic-list.inc.php +++ b/include/staff/dynamic-list.inc.php @@ -218,7 +218,7 @@ $info=Format::htmlchars(($errors && $_POST) ? array_merge($info,$_POST) : $info) $page = ($_GET['p'] && is_numeric($_GET['p'])) ? $_GET['p'] : 1; $count = $list->getNumItems(); $pageNav = new Pagenate($count, $page, PAGE_LIMIT); - $pageNav->setURL('list.php', 'id='.urlencode($list->getId())); + $pageNav->setURL('list.php', array('id' => $list->getId())); $showing=$pageNav->showing().' '.__('list items'); ?> <?php } diff --git a/include/staff/email.inc.php b/include/staff/email.inc.php index e556d39b643f874d21a79e50be6c02fcf9fbf0e3..27e7dc449d0318da13a714748d5ced468cd4e737 100644 --- a/include/staff/email.inc.php +++ b/include/staff/email.inc.php @@ -1,7 +1,6 @@ <?php if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access Denied'); -$info=array(); -$qstr=''; +$info = $qs = array(); if($email && $_REQUEST['a']!='add'){ $title=__('Update Email'); $action='update'; @@ -17,7 +16,7 @@ if($email && $_REQUEST['a']!='add'){ if($info['userpass']) $passwdtxt=__('To change password enter new password above.'); - $qstr.='&id='.$email->getId(); + $qs += array('id' => $email->getId()); }else { $title=__('Add New Email'); $action='create'; @@ -31,12 +30,12 @@ if($email && $_REQUEST['a']!='add'){ $info['mail_fetchmax'] = 10; if (!isset($info['smtp_auth'])) $info['smtp_auth'] = 1; - $qstr.='&a='.$_REQUEST['a']; + $qs += array('a' => $_REQUEST['a']); } $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); ?> <h2><?php echo __('Email Address');?></h2> -<form action="emails.php?<?php echo $qstr; ?>" method="post" id="save"> +<form action="emails.php?<?php echo Http::build_query($qs); ?>" method="post" id="save"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> diff --git a/include/staff/emails.inc.php b/include/staff/emails.inc.php index 7b772d3e0f173802cabf564fdad4a35aedc32f6b..413cc7e74034de04b20c7d8299505df24d35b46a 100644 --- a/include/staff/emails.inc.php +++ b/include/staff/emails.inc.php @@ -1,7 +1,7 @@ <?php if(!defined('OSTADMININC') || !$thisstaff->isAdmin()) die('Access Denied'); -$qstr=''; +$qs = array(); $sortOptions = array( 'email' => 'email', 'dept' => 'dept__name', @@ -30,10 +30,10 @@ $$x=' class="'.strtolower($order).'" '; $page = ($_GET['p'] && is_numeric($_GET['p'])) ? $_GET['p'] : 1; $count = EmailModel::objects()->count(); $pageNav = new Pagenate($count, $page, PAGE_LIMIT); -$_qstr = $qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order']); -$pageNav->setURL('emails.php', $_qstr); +$qs += array('sort' => $_REQUEST['sort'], 'order' => $_REQUEST['order']); +$pageNav->setURL('emails.php', $qs); $showing = $pageNav->showing().' '._N('email', 'emails', $count); -$qstr.='&order='.($order=='DESC'?'ASC':'DESC'); +$qstr = '&order='.($order=='DESC' ? 'ASC' : 'DESC'); $def_dept_id = $cfg->getDefaultDeptId(); $def_dept_name = $cfg->getDefaultDept()->getName(); @@ -107,7 +107,7 @@ $def_priority = $cfg->getDefaultPriority()->getDesc(); <a id="selectNone" href="#ckb"><?php echo __('None');?></a> <a id="selectToggle" href="#ckb"><?php echo __('Toggle');?></a> <?php }else{ - echo __('No help emails found'); + echo __('No emails found!'); } ?> </td> </tr> diff --git a/include/staff/faq.inc.php b/include/staff/faq.inc.php index a1bdfcc7ac36558974060b1bb7cb902dd7311e6f..cb6ffed91153a90876b202ca5d9a55bfaababfb3 100644 --- a/include/staff/faq.inc.php +++ b/include/staff/faq.inc.php @@ -3,8 +3,7 @@ if (!defined('OSTSCPINC') || !$thisstaff || !$thisstaff->getRole()->hasPerm(FAQ::PERM_MANAGE)) die('Access Denied'); -$info=array(); -$qstr=''; +$info = $qs = array(); if($faq){ $title=__('Update FAQ').': '.$faq->getQuestion(); $action='update'; @@ -14,7 +13,7 @@ if($faq){ $info['topics']=$faq->getHelpTopicsIds(); $info['answer']=Format::viewableImages($faq->getAnswer()); $info['notes']=Format::viewableImages($faq->getNotes()); - $qstr='id='.$faq->getId(); + $qs += array('id' => $faq->getId()); $langs = $cfg->getSecondaryLanguages(); $translations = $faq->getAllTranslations(); foreach ($langs as $tag) { @@ -34,12 +33,13 @@ if($faq){ $action='create'; $submit_text=__('Add FAQ'); if($category) { - $qstr='cid='.$category->getId(); + $qs += array('cid' => $category->getId()); $info['category_id']=$category->getId(); } } //TODO: Add attachment support. $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); +$qstr = Http::build_query($qs); ?> <form action="faq.php?<?php echo $qstr; ?>" method="post" id="save" enctype="multipart/form-data"> <?php csrf_token(); ?> diff --git a/include/staff/filter.inc.php b/include/staff/filter.inc.php index 74435b9bde6df32da9e97d41543d837728246647..f09731af64a0cc7f5455b3677b408bb208af29aa 100644 --- a/include/staff/filter.inc.php +++ b/include/staff/filter.inc.php @@ -4,25 +4,24 @@ if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access $matches=Filter::getSupportedMatches(); $match_types=Filter::getSupportedMatchTypes(); -$info=array(); -$qstr=''; +$info = $qs = array(); if($filter && $_REQUEST['a']!='add'){ $title=__('Update Filter'); $action='update'; $submit_text=__('Save Changes'); $info=array_merge($filter->getInfo(),$filter->getFlatRules()); $info['id']=$filter->getId(); - $qstr.='&id='.$filter->getId(); + $qs += array('id' => $filter->getId()); }else { $title=__('Add New Filter'); $action='add'; $submit_text=__('Add Filter'); $info['isactive']=isset($info['isactive'])?$info['isactive']:0; - $qstr.='&a='.urlencode($_REQUEST['a']); + $qs += array('a' => $_REQUEST['a']); } $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); ?> -<form action="filters.php?<?php echo $qstr; ?>" method="post" id="save"> +<form action="filters.php?<?php echo Http::build_query($qs); ?>" method="post" id="save"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> diff --git a/include/staff/filters.inc.php b/include/staff/filters.inc.php index 6b4c98647a20910f1a3f5688a58674ef1e5f55e4..ce55919ca6274bd0e039cb641d5da886616731a6 100644 --- a/include/staff/filters.inc.php +++ b/include/staff/filters.inc.php @@ -1,7 +1,7 @@ <?php if(!defined('OSTADMININC') || !$thisstaff->isAdmin()) die('Access Denied'); $targets = Filter::getTargets(); -$qstr=''; +$qs = array(); $sql='SELECT filter.*,count(rule.id) as rules '. 'FROM '.FILTER_TABLE.' filter '. 'LEFT JOIN '.FILTER_RULE_TABLE.' rule ON(rule.filter_id=filter.id) '. @@ -32,9 +32,10 @@ $order_by="$order_column $order "; $total=db_count('SELECT count(*) FROM '.FILTER_TABLE.' filter '); $page=($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1; $pageNav=new Pagenate($total, $page, PAGE_LIMIT); -$pageNav->setURL('filters.php',$qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order'])); -//Ok..lets roll...create the actual query -$qstr.='&order='.($order=='DESC'?'ASC':'DESC'); +$qstr = '&'. Http::build_query($qs); +$qs += array('sort' => $_REQUEST['sort'], 'order' => $_REQUEST['order']); +$pageNav->setURL('filters.php', $qs); +$qstr.='&order='.($order=='DESC' ? 'ASC' : 'DESC'); $query="$sql ORDER BY $order_by LIMIT ".$pageNav->getStart().",".$pageNav->getLimit(); $res=db_query($query); if($res && ($num=db_num_rows($res))) diff --git a/include/staff/group.inc.php b/include/staff/group.inc.php index 2a3a63807fc54c6c3e56edad8bc031af9fc9e0d5..e8006cade91f1a065cbd54b3a0c1ec9d5849b18e 100644 --- a/include/staff/group.inc.php +++ b/include/staff/group.inc.php @@ -1,6 +1,7 @@ <?php -$info=array(); +if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access Denied'); +$info = $qs = array(); if ($group) { $title = __('Update Group'); $action = 'update'; @@ -9,18 +10,20 @@ if ($group) { $info['id'] = $group->getId(); $info['depts'] = $group->getDepartments(); $trans['name'] = $group->getTranslateTag('name'); + $qs += array('id' => $group->getId()); } else { $title = __('Add New Group'); $action = 'add'; $submit_text = __('Create Group'); $info['isactive'] = isset($info['isactive']) ? $info['isactive'] : 1; + $qs += array('a' => $_REQUEST['a']); } $info = Format::htmlchars(($errors && $_POST) ? array_merge($info, $_POST) : $info); $roles = Role::getActiveRoles(); ?> -<form action="" method="post" id="save"> +<form action="groups.php?<?php echo Http::build_query($qs); ?>" method="post" id="save" name="group"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> diff --git a/include/staff/groups.inc.php b/include/staff/groups.inc.php index 69e5581448700e2bdf7a70dc14147677598b8ca2..6c7b794984a0aee199e01dc769626297c1bb38b8 100644 --- a/include/staff/groups.inc.php +++ b/include/staff/groups.inc.php @@ -2,7 +2,7 @@ if (!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access Denied'); -$qstr = ''; +$qs = array(); $sortOptions = array( 'name' => 'name', 'users' => 'members_count', @@ -35,10 +35,11 @@ $$x=' class="'.strtolower($order).'" '; $page = ($_GET['p'] && is_numeric($_GET['p'])) ? $_GET['p'] : 1; $count = Group::objects()->count(); $pageNav = new Pagenate($count, $page, PAGE_LIMIT); -$_qstr = $qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order']); -$pageNav->setURL('groups.php', $_qstr); +$qstr = '&'. Http::build_query($qs); +$qstr .= '&order='.($order=='DESC' ? 'ASC' : 'DESC'); +$qs += array('sort' => $_REQUEST['sort'], 'order' => $_REQUEST['order']); +$pageNav->setURL('pages.php', $qs); $showing = $pageNav->showing().' '._N('group', 'groups', $count); -$qstr.='&order='.($order=='DESC'?'ASC':'DESC'); ?> <div class="pull-left" style="width:700px;padding-top:5px;"> <h2><?php echo __('Agent Groups');?> diff --git a/include/staff/helptopic.inc.php b/include/staff/helptopic.inc.php index ee8616919d13d61a0b3f38d6db876f93011115f0..6fb6371bc77cd7a61e03ee1830c950cf2ea72504 100644 --- a/include/staff/helptopic.inc.php +++ b/include/staff/helptopic.inc.php @@ -1,7 +1,6 @@ <?php if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access Denied'); -$info=array(); -$qstr=''; +$info = $qs = array(); if($topic && $_REQUEST['a']!='add') { $title=__('Update Help Topic'); $action='update'; @@ -10,7 +9,7 @@ if($topic && $_REQUEST['a']!='add') { $info['id']=$topic->getId(); $info['pid']=$topic->getPid(); $trans['name'] = $topic->getTranslateTag('name'); - $qstr.='&id='.$topic->getId(); + $qs += array('id' => $topic->getId()); } else { $title=__('Add New Help Topic'); $action='create'; @@ -18,11 +17,11 @@ if($topic && $_REQUEST['a']!='add') { $info['isactive']=isset($info['isactive'])?$info['isactive']:1; $info['ispublic']=isset($info['ispublic'])?$info['ispublic']:1; $info['form_id'] = Topic::FORM_USE_PARENT; - $qstr.='&a='.$_REQUEST['a']; + $qs += array('a' => $_REQUEST['a']); } $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); ?> -<form action="helptopics.php?<?php echo $qstr; ?>" method="post" id="save"> +<form action="helptopics.php?<?php echo Http::build_query($qs); ?>" method="post" id="save"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> diff --git a/include/staff/orgs.inc.php b/include/staff/orgs.inc.php index d85d42d1d087dcd16268336c79e3f2165a3f7892..f75f67f556d9e6fbf715ed374a1771e5b06c44d0 100644 --- a/include/staff/orgs.inc.php +++ b/include/staff/orgs.inc.php @@ -1,7 +1,7 @@ <?php if(!defined('OSTSCPINC') || !$thisstaff) die('Access Denied'); -$qstr=''; +$qs = array(); $select = 'SELECT org.* ,COALESCE(team.name, @@ -27,7 +27,7 @@ if ($_REQUEST['query']) { org.name LIKE \'%'.$search.'%\' OR value.value LIKE \'%'.$search.'%\' )'; - $qstr.='&query='.urlencode($_REQUEST['query']); + $qs += array('query' => $_REQUEST['query']); } $sortOptions = array('name' => 'org.name', @@ -56,9 +56,10 @@ $order_by="$order_column $order "; $total=db_count('SELECT count(DISTINCT org.id) '.$from.' '.$where); $page=($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1; $pageNav=new Pagenate($total,$page,PAGE_LIMIT); -$pageNav->setURL('orgs.php',$qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order'])); -//Ok..lets roll...create the actual query -$qstr.='&order='.($order=='DESC'?'ASC':'DESC'); +$qstr = '&'. Http::build_query($qs); +$qs += array('sort' => $_REQUEST['sort'], 'order' => $_REQUEST['order']); +$pageNav->setURL('orgs.php', $qs); +$qstr.='&order='.($order=='DESC' ? 'ASC' : 'DESC'); $select .= ', count(DISTINCT user.id) as users '; diff --git a/include/staff/page.inc.php b/include/staff/page.inc.php index 028e71b9366729a7019611b64cf55c262947ae2e..cbc2d2526f1ed37e65952a3efb4e309208825c63 100644 --- a/include/staff/page.inc.php +++ b/include/staff/page.inc.php @@ -6,8 +6,7 @@ $pageTypes = array( 'thank-you' => __('Thank you page'), 'other' => __('Other'), ); -$info=array(); -$qstr=''; +$info = $qs = array(); if($page && $_REQUEST['a']!='add'){ $title=__('Update Page'); $action='update'; @@ -17,7 +16,7 @@ if($page && $_REQUEST['a']!='add'){ $info['notes'] = Format::viewableImages($info['notes']); $trans['name'] = $page->getTranslateTag('name'); $slug = Format::slugify($info['name']); - $qstr.='&id='.$page->getId(); + $qs += array('id' => $page->getId()); $translations = CustomDataTranslation::allTranslations( $page->getTranslateTag('name:body'), 'article'); foreach ($cfg->getSecondaryLanguages() as $tag) { @@ -34,11 +33,11 @@ if($page && $_REQUEST['a']!='add'){ $action='add'; $submit_text=__('Add Page'); $info['isactive']=isset($info['isactive'])?$info['isactive']:0; - $qstr.='&a='.urlencode($_REQUEST['a']); + $qs += array('a' => $_REQUEST['a']); } $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); ?> -<form action="pages.php?<?php echo $qstr; ?>" method="post" id="save"> +<form action="pages.php?<?php echo Http::build_query($qs); ?>" method="post" id="save"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> diff --git a/include/staff/pages.inc.php b/include/staff/pages.inc.php index 5f364a768592f2c1fb5cf3b61bac752eb019392e..11a3f48b66146023b76b0d0c38176bf55b99a822 100644 --- a/include/staff/pages.inc.php +++ b/include/staff/pages.inc.php @@ -4,7 +4,7 @@ if(!defined('OSTADMININC') || !$thisstaff->isAdmin()) die('Access Denied'); $pages = Page::objects() ->filter(array('type__in'=>array('other','landing','thank-you','offline'))) ->annotate(array('topics'=>SqlAggregate::COUNT('topics'))); -$qstr=''; +$qs = array(); $sortOptions=array( 'name'=>'name', 'status'=>'isactive', 'created'=>'created', 'updated'=>'updated', @@ -25,10 +25,11 @@ $$x=' class="'.strtolower($order).'" '; $total = $pages->count(); $page=($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1; $pageNav=new Pagenate($total, $page, PAGE_LIMIT); -$pageNav->setURL('pages.php',$qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order'])); +$qstr = '&'. Http::build_query($qs); +$qstr .= '&order='.($order=='DESC' ? 'ASC' : 'DESC'); +$qs += array('sort' => $_REQUEST['sort'], 'order' => $_REQUEST['order']); +$pageNav->setURL('pages.php', $qs); //Ok..lets roll...create the actual query -$qstr.='&order='.($order=='DESC'?'ASC':'DESC'); -$pages = $pages->limit($pageNav->getLimit())->offset($pageNav->getStart()); if ($total) $showing=$pageNav->showing()._N('site page','site pages', $num); else diff --git a/include/staff/plugin.inc.php b/include/staff/plugin.inc.php index f76b0f27c749e889b0a8170875798a3d2f932292..f27e21333aabc78535863ccb2e9c403851ac8a01 100644 --- a/include/staff/plugin.inc.php +++ b/include/staff/plugin.inc.php @@ -14,10 +14,11 @@ if($plugin && $_REQUEST['a']!='add') { $submit_text = __('Save Changes'); $info = $plugin->ht; } -$info=Format::htmlchars(($errors && $_POST)?$_POST:$info); + +$info = Format::htmlchars(($errors && $_POST) ? $_POST : $info); ?> -<form action="?id=<?php echo urlencode($_REQUEST['id']); ?>" method="post" id="save"> +<form action="?<?php echo Http::build_query(array('id' => $_REQUEST['id'])); ?>" method="post" id="save"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="id" value="<?php echo $info['id']; ?>"> diff --git a/include/staff/settings-pages.inc.php b/include/staff/settings-pages.inc.php index 9727bde4060beeaacce6169a375b87b0b5382aa9..3bf1c78776adc2bc90b9604175591bd3ff969202 100644 --- a/include/staff/settings-pages.inc.php +++ b/include/staff/settings-pages.inc.php @@ -135,7 +135,7 @@ $pages = Page::getPages(); echo $logo->getId(); ?>" <?php if ($logo->getId() == $current) echo 'checked="checked"'; ?>/> - <img src="image.php?h=<?php echo $logo->getDownloadHash(); ?>" + <img src="<?php echo $logo->getDownloadUrl(); ?>" alt="Custom Logo" valign="middle" style="box-shadow: 0 0 0.5em rgba(0,0,0,0.5); margin: 0.5em; height: 5em; diff --git a/include/staff/settings-tickets.inc.php b/include/staff/settings-tickets.inc.php index 0aef056efa472af65087bf8126a7301bad4fc645..021d24c4397e1ac632e4c974e8f9fe0cd718b607 100644 --- a/include/staff/settings-tickets.inc.php +++ b/include/staff/settings-tickets.inc.php @@ -207,7 +207,7 @@ if(!($maxfileuploads=ini_get('max_file_uploads'))) </th> </tr> <tr> - <td width="180"><?php echo __('Ticket Attachment Settings');?>:</td> + <td width="180"><?php echo __('EndUser Attachment Settings');?>:</td> <td> <?php $tform = TicketForm::objects()->one()->getForm(); @@ -224,7 +224,7 @@ if(!($maxfileuploads=ini_get('max_file_uploads'))) </td> </tr> <tr> - <td width="180"><?php echo __('Maximum File Size');?>:</td> + <td width="180"><?php echo __('Agent Maximum File Size');?>:</td> <td> <select name="max_file_size"> <option value="262144">— <?php echo __('Small'); ?> —</option> diff --git a/include/staff/slaplan.inc.php b/include/staff/slaplan.inc.php index 91dba3a2b735c40e43cf6c2b8f155176283d0326..3ca424c3044aea1d524f6ef8032b9671cfe04aca 100644 --- a/include/staff/slaplan.inc.php +++ b/include/staff/slaplan.inc.php @@ -1,7 +1,6 @@ <?php if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access Denied'); -$info=array(); -$qstr=''; +$info = $qs = array(); if($sla && $_REQUEST['a']!='add'){ $title=__('Update SLA Plan' /* SLA is abbreviation for Service Level Agreement */); $action='update'; @@ -9,7 +8,7 @@ if($sla && $_REQUEST['a']!='add'){ $info=$sla->getInfo(); $info['id']=$sla->getId(); $trans['name'] = $sla->getTranslateTag('name'); - $qstr.='&id='.$sla->getId(); + $qs += array('id' => $sla->getId()); }else { $title=__('Add New SLA Plan' /* SLA is abbreviation for Service Level Agreement */); $action='add'; @@ -17,11 +16,11 @@ if($sla && $_REQUEST['a']!='add'){ $info['isactive']=isset($info['isactive'])?$info['isactive']:1; $info['enable_priority_escalation']=isset($info['enable_priority_escalation'])?$info['enable_priority_escalation']:1; $info['disable_overdue_alerts']=isset($info['disable_overdue_alerts'])?$info['disable_overdue_alerts']:0; - $qstr.='&a='.urlencode($_REQUEST['a']); + $qs += array('a' => $_REQUEST['a']); } $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); ?> -<form action="slas.php?<?php echo $qstr; ?>" method="post" id="save"> +<form action="slas.php?<?php echo Http::build_query($qs); ?>" method="post" id="save"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> diff --git a/include/staff/slaplans.inc.php b/include/staff/slaplans.inc.php index 3565ea7605554c3e828071386e68e60458d5ac60..46264ec98dfa061e98f45a787da722c72d3833e5 100644 --- a/include/staff/slaplans.inc.php +++ b/include/staff/slaplans.inc.php @@ -1,7 +1,7 @@ <?php if(!defined('OSTADMININC') || !$thisstaff->isAdmin()) die('Access Denied'); -$qstr=''; +$qs = array(); $sortOptions=array( 'name' => 'name', 'status' => 'isactive', @@ -31,11 +31,12 @@ $x=$sort.'_sort'; $$x=' class="'.strtolower($order).'" '; $page = ($_GET['p'] && is_numeric($_GET['p'])) ? $_GET['p'] : 1; $count = SLA::objects()->count(); -$pageNav = new Pagenate($count, $page, PAGE_LIMIT); -$_qstr = $qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order']); -$pageNav->setURL('slas.php', $_qstr); +$qstr = '&'. Http::build_query($qs); +$qs += array('sort' => $_REQUEST['sort'], 'order' => $_REQUEST['order']); + +$pageNav->setURL('slas.php', $qs); $showing = $pageNav->showing().' '._N('SLA plan', 'SLA plans', $count); -$qstr.='&order='.($order=='DESC'?'ASC':'DESC'); +$qstr .= '&order='.($order=='DESC' ? 'ASC' : 'DESC'); ?> <div class="pull-left" style="width:700px;padding-top:5px;"> <h2><?php echo __('Service Level Agreements');?></h2> diff --git a/include/staff/staff.inc.php b/include/staff/staff.inc.php index f1ac113c711a521c93119409896686676a9396f1..1da74cc1aa0648324cc1003ad6003b9ce82454e3 100644 --- a/include/staff/staff.inc.php +++ b/include/staff/staff.inc.php @@ -1,8 +1,7 @@ <?php if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access Denied'); -$info=array(); -$qstr=''; +$info = $qs = array(); if($staff && $_REQUEST['a']!='add'){ //Editing Department. $title=__('Update Agent'); @@ -13,7 +12,7 @@ if($staff && $_REQUEST['a']!='add'){ $info['id']=$staff->getId(); $info['teams'] = $staff->getTeams(); $info['signature'] = Format::viewableImages($info['signature']); - $qstr.='&id='.$staff->getId(); + $qs += array('id' => $staff->getId()); }else { $title=__('Add New Agent'); $action='create'; @@ -25,11 +24,11 @@ if($staff && $_REQUEST['a']!='add'){ $info['isactive']=1; $info['isvisible']=1; $info['isadmin']=0; - $qstr.='&a=add'; + $qs += array('a' => 'add'); } $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); ?> -<form action="staff.php?<?php echo $qstr; ?>" method="post" id="save" autocomplete="off"> +<form action="staff.php?<?php echo Http::build_query($qs); ?>" method="post" id="save" autocomplete="off"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> diff --git a/include/staff/staffmembers.inc.php b/include/staff/staffmembers.inc.php index e1872334ec1cc67c413a2202789d5920af6b3190..dc40e747c26e06a8ef7b734bd29ec9561eb19f50 100644 --- a/include/staff/staffmembers.inc.php +++ b/include/staff/staffmembers.inc.php @@ -3,6 +3,7 @@ if (!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access Denied'); $qstr=''; +$qs = array(); $sortOptions = array( 'name' => 'lastname', 'username' => 'username', @@ -38,17 +39,17 @@ $$x=' class="'.strtolower($order).'" '; $filters = array(); if ($_REQUEST['did'] && is_numeric($_REQUEST['did'])) { $filters += array('dept_id' => $_REQUEST['did']); - $qstr.='&did='.urlencode($_REQUEST['did']); + $qs += array('did' => $_REQUEST['did']); } if ($_REQUEST['gid'] && is_numeric($_REQUEST['gid'])) { $filters += array('group_id' => $_REQUEST['gid']); - $qstr.='&gid='.urlencode($_REQUEST['gid']); + $qs += array('gid' => $_REQUEST['gid']); } if ($_REQUEST['tid'] && is_numeric($_REQUEST['tid'])) { $filters += array('teams__team_id' => $_REQUEST['tid']); - $qstr.='&tid='.urlencode($_REQUEST['tid']); + $qs += array('tid' => $_REQUEST['tid']); } //agents objects @@ -68,10 +69,11 @@ if ($filters) $page = ($_GET['p'] && is_numeric($_GET['p'])) ? $_GET['p'] : 1; $count = $agents->count(); $pageNav = new Pagenate($count, $page, PAGE_LIMIT); -$_qstr = $qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order']); -$pageNav->setURL('staff.php', $_qstr); +$qs += array('sort' => $_REQUEST['sort'], 'order' => $_REQUEST['order']); +$pageNav->setURL('staff.php', $qs); $showing = $pageNav->showing().' '._N('agent', 'agents', $count); -$qstr.='&order='.($order=='DESC'?'ASC':'DESC'); +$qstr = '&'. Http::build_query($qs); +$qstr .= '&order='.($order=='DESC' ? 'ASC' : 'DESC'); // add limits. $agents->limit($pageNav->getLimit())->offset($pageNav->getStart()); diff --git a/include/staff/syslogs.inc.php b/include/staff/syslogs.inc.php index 8b610d0f60dd4d87ec61351ce4313753a6c47acd..68648f99feb37872fcb2949ef6dc386e6c03687d 100644 --- a/include/staff/syslogs.inc.php +++ b/include/staff/syslogs.inc.php @@ -1,9 +1,9 @@ <?php if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access Denied'); -$qstr=''; +$qs = array(); if($_REQUEST['type']) { - $qstr.='&type='.urlencode($_REQUEST['type']); + $qs += array('type' => $_REQUEST['type']); } $type=null; switch(strtolower($_REQUEST['type'])){ @@ -38,11 +38,11 @@ if( ($startTime && $startTime>time()) or ($startTime>$endTime && $endTime>0)){ }else{ if($startTime){ $qwhere.=' AND created>=FROM_UNIXTIME('.$startTime.')'; - $qstr.='&startDate='.urlencode($_REQUEST['startDate']); + $qs += array('startDate' => $_REQUEST['startDate']); } if($endTime){ $qwhere.=' AND created<=FROM_UNIXTIME('.$endTime.')'; - $qstr.='&endDate='.urlencode($_REQUEST['endDate']); + $qs += array('endDate' => $_REQUEST['endDate']); } } $sortOptions=array('id'=>'log.log_id', 'title'=>'log.title','type'=>'log_type','ip'=>'log.ip_address' @@ -73,8 +73,9 @@ $total=db_count("SELECT count(*) $qfrom $qwhere"); $page = ($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1; //pagenate $pageNav=new Pagenate($total, $page, PAGE_LIMIT); -$pageNav->setURL('logs.php',$qstr); -$qstr.='&order='.($order=='DESC'?'ASC':'DESC'); +$pageNav->setURL('logs.php',$qs); +$qs += array('order' => ($order=='DESC' ? 'ASC' : 'DESC')); +$qstr = '&'. Http::build_query($qs); $query="$qselect $qfrom $qwhere ORDER BY $order_by LIMIT ".$pageNav->getStart().",".$pageNav->getLimit(); $res=db_query($query); if($res && ($num=db_num_rows($res))) diff --git a/include/staff/system.inc.php b/include/staff/system.inc.php index 2e21c21a67527331b228f673fc2b80974bc11e73..a3a337f12a56b274093b97d3b6ab5025fd6e6472 100644 --- a/include/staff/system.inc.php +++ b/include/staff/system.inc.php @@ -37,6 +37,10 @@ $extensions = array( 'name' => 'intl', 'desc' => __('Highly recommended for non western european language content') ), + 'fileinfo' => array( + 'name' => 'fileinfo', + 'desc' => __('Used to detect file types for uploads') + ), ); ?> diff --git a/include/staff/team.inc.php b/include/staff/team.inc.php index ec651205c6a9bf6e4529de325ba2c921e125c47d..f851fdc3a9c789277eece37f62b60436530bd984 100644 --- a/include/staff/team.inc.php +++ b/include/staff/team.inc.php @@ -1,7 +1,6 @@ <?php if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access Denied'); -$info=$members=array(); -$qstr=''; +$info = $members = $qs = array(); if ($team && $_REQUEST['a']!='add') { //Editing Team $title=__('Update Team'); @@ -10,20 +9,20 @@ if ($team && $_REQUEST['a']!='add') { $info=$team->getInfo(); $info['id']=$team->getId(); $trans['name'] = $team->getTranslateTag('name'); - $qstr.='&id='.$team->getId(); $members = $team->getMembers(); + $qs += array('id' => $team->getId()); } else { $title=__('Add New Team'); $action='create'; $submit_text=__('Create Team'); $info['isenabled']=1; $info['noalerts']=0; - $qstr.='&a='.$_REQUEST['a']; + $qs += array('a' => $_REQUEST['a']); } $info = Format::htmlchars(($errors && $_POST) ? $_POST : $info); ?> -<form action="teams.php?<?php echo $qstr; ?>" method="post" id="save"> +<form action="teams.php?<?php echo Http::build_query($qs); ?>" method="post" id="save"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> diff --git a/include/staff/teams.inc.php b/include/staff/teams.inc.php index 22c08c2073dbf428d406df8320620d6cc7383615..5982f7c208e1e05c020db9a97dd0409c43203293 100644 --- a/include/staff/teams.inc.php +++ b/include/staff/teams.inc.php @@ -1,7 +1,7 @@ <?php if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access Denied'); -$qstr=''; +$qs = array(); $sortOptions=array( 'name' => 'name', 'status' => 'isenabled', @@ -35,10 +35,11 @@ $$x=' class="'.strtolower($order).'" '; $page = ($_GET['p'] && is_numeric($_GET['p'])) ? $_GET['p'] : 1; $count = Team::objects()->count(); $pageNav = new Pagenate($count, $page, PAGE_LIMIT); -$_qstr = $qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order']); -$pageNav->setURL('teams.php', $_qstr); +$qstr = '&'. Http::build_query($qs); +$qs += array('sort' => $_REQUEST['sort'], 'order' => $_REQUEST['order']); +$pageNav->setURL('teams.php', $qs); $showing = $pageNav->showing().' '._N('team', 'teams', $count); -$qstr.='&order='.($order=='DESC'?'ASC':'DESC'); +$qstr .= '&order='.urlencode($order=='DESC' ? 'ASC' : 'DESC'); ?> diff --git a/include/staff/template.inc.php b/include/staff/template.inc.php index e0e3727474bed4873cd7fdfb16945083973438f0..073908166ee3a3ebb5388de462cb701d72d508d9 100644 --- a/include/staff/template.inc.php +++ b/include/staff/template.inc.php @@ -1,26 +1,25 @@ <?php if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access Denied'); -$info=array(); -$qstr=''; +$info = $qs = array(); if($template && $_REQUEST['a']!='add'){ $title=__('Update Template'); $action='update'; $submit_text=__('Save Changes'); $info=$template->getInfo(); $info['tpl_id']=$template->getId(); - $qstr.='&tpl_id='.$template->getId(); + $qs += array('tpl_id' => $template->getId()); }else { $title=__('Add New Template'); $action='add'; $submit_text=__('Add Template'); $info['isactive']=isset($info['isactive'])?$info['isactive']:0; $info['lang_id'] = $cfg->getPrimaryLanguage(); - $qstr.='&a='.urlencode($_REQUEST['a']); + $qs += array('a' => $_REQUEST['a']); } $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); ?> -<form action="templates.php?<?php echo $qstr; ?>" method="post" id="save"> +<form action="templates.php?<?php echo Http::build_query($qs); ?>" method="post" id="save"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> @@ -141,7 +140,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <?php foreach($langs as $l) { $selected = ($info['lang_id'] == $l['code']) ? 'selected="selected"' : ''; ?> <option value="<?php echo $l['code']; ?>" <?php echo $selected; - ?>><?php echo $l['desc']; ?></option> + ?>><?php echo Internationalization::getLanguageDescription($l['code']); ?></option> <?php } ?> </select> <span class="error">* <?php echo $errors['lang_id']; ?></span> diff --git a/include/staff/templates.inc.php b/include/staff/templates.inc.php index c7bcafafb76e9567817432ce0cfb0af0f039626b..2d7a972cb04176a1f538407fa93c946cfe787f62 100644 --- a/include/staff/templates.inc.php +++ b/include/staff/templates.inc.php @@ -1,7 +1,7 @@ <?php if(!defined('OSTADMININC') || !$thisstaff->isAdmin()) die('Access Denied'); -$qstr=''; +$qs = array(); $sql='SELECT tpl.*,count(dept.tpl_id) as depts '. 'FROM '.EMAIL_TEMPLATE_GRP_TABLE.' tpl '. 'LEFT JOIN '.DEPT_TABLE.' dept USING(tpl_id) '. @@ -30,9 +30,10 @@ $order_by="$order_column $order "; $total=db_count('SELECT count(*) FROM '.EMAIL_TEMPLATE_GRP_TABLE.' tpl '); $page=($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1; $pageNav=new Pagenate($total, $page, PAGE_LIMIT); -$pageNav->setURL('templates.php',$qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order'])); -//Ok..lets roll...create the actual query -$qstr.='&order='.($order=='DESC'?'ASC':'DESC'); +$qstr = '&'. Http::build_query($qs); +$qs += array('sort' => $_REQUEST['sort'], 'order' => $_REQUEST['order']); +$pageNav->setURL('templates.php', $qs); +$qstr .= '&order='.($order=='DESC' ? 'ASC' : 'DESC'); $query="$sql GROUP BY tpl.tpl_id ORDER BY $order_by LIMIT ".$pageNav->getStart().",".$pageNav->getLimit(); $res=db_query($query); if($res && ($num=db_num_rows($res))) diff --git a/include/staff/templates/users.tmpl.php b/include/staff/templates/users.tmpl.php index 51f8f7e3d6b84fa57c8174aae8a4e2547fa7ea9e..b95974d214cf17d57ef2062b1d26de4bd7106307 100644 --- a/include/staff/templates/users.tmpl.php +++ b/include/staff/templates/users.tmpl.php @@ -1,5 +1,5 @@ <?php -$qstr=''; +$qs = array(); $select = 'SELECT user.*, email.address as email '; $from = 'FROM '.USER_TABLE.' user ' @@ -33,9 +33,12 @@ $order_by="$order_column $order "; $total=db_count('SELECT count(DISTINCT user.id) '.$from.' '.$where); $page=($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1; $pageNav=new Pagenate($total,$page,PAGE_LIMIT); -$pageNav->setURL('users.php',$qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order'])); +$qstr = '&'. Http::build_query($qs); +$qs += array('sort' => $_REQUEST['sort'], 'order' => $_REQUEST['order']); + +$pageNav->setURL('users.php', $qs); //Ok..lets roll...create the actual query -$qstr.='&order='.($order=='DESC'?'ASC':'DESC'); +$qstr .= '&order='.($order=='DESC' ? 'ASC' : 'DESC'); $select .= ', count(DISTINCT ticket.ticket_id) as tickets '; diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php index ce64ae1156c060a3a5bb0fb113149dc376c67281..de9ccf80153ce21876688d343459a1cb08dc6b17 100644 --- a/include/staff/ticket-view.inc.php +++ b/include/staff/ticket-view.inc.php @@ -441,7 +441,7 @@ $tcount+= $ticket->getNumNotes(); </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 Format::clickableurls($entry['body']->toHtml()); ?></div></td></tr> <?php $urls = null; if($entry['attachments'] @@ -570,6 +570,7 @@ $tcount+= $ticket->getNumNotes(); <label><strong><?php echo __('Response');?>:</strong></label> </td> <td> +<?php if ($cfg->isCannedResponseEnabled()) { ?> <select id="cannedResp" name="cannedResp"> <option value="0" selected="selected"><?php echo __('Select a canned response');?></option> <option value='original'><?php echo __('Original Message'); ?></option> @@ -584,7 +585,7 @@ $tcount+= $ticket->getNumNotes(); ?> </select> <br> - <?php +<?php } # endif (canned-resonse-enabled) $signature = ''; switch ($thisstaff->getDefaultSignatureType()) { case 'dept': @@ -1043,5 +1044,19 @@ $(function() { } }); }); +<?php + // Set the lock if one exists + if ($lock) { ?> +!function() { + var setLock = setInterval(function() { + if (typeof(window.autoLock) === 'undefined') + return; + clearInterval(setLock); + autoLock.setLock({ + id:<?php echo $lock->getId(); ?>, + time: <?php echo $cfg->getLockTime(); ?>}, 'acquire'); + }, 50); +}(); +<?php } ?> }); </script> diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php index ad4e0864595ba4f76f4c175675292fa8fb699190..d12c5b0858a251799d0af0afdc8a631fc1fc20be 100644 --- a/include/staff/tickets.inc.php +++ b/include/staff/tickets.inc.php @@ -16,8 +16,8 @@ unset($args['a']); $refresh_url = $path . '?' . http_build_query($args); - -switch(strtolower($_REQUEST['status'])){ //Status is overloaded +$queue_name = strtolower($_GET['status'] ?: $_GET['a']); //Status is overloaded +switch ($queue_name) { case 'closed': $status='closed'; $results_type=__('Closed Tickets'); @@ -60,6 +60,10 @@ case 'search': } break; } + elseif (isset($_GET['uid'])) { + // Apply user filter + $tickets->filter(array('user__id'=>$_GET['uid'])); + } elseif (isset($_SESSION['advsearch'])) { // XXX: De-duplicate and simplify this code $form = $search->getFormFromSession('advsearch'); @@ -67,7 +71,6 @@ case 'search': $tickets = $search->mangleQuerySet($tickets, $form); $results_type=__('Advanced Search') . '<a class="action-button" href="?clear_filter"><i class="icon-ban-circle"></i> <em>' . __('clear') . '</em></a>'; - unset($_REQUEST['sort']); break; } // Fall-through and show open tickets @@ -115,7 +118,11 @@ $tickets->values('lock__staff_id', 'staff_id', 'isoverdue', 'team_id', 'ticket_i // Apply requested quick filter // Apply requested sorting -switch ($_REQUEST['sort']) { +$queue_sort_key = sprintf(':Q:%s:sort', $queue_name); + +if (isset($_GET['sort'])) + $_SESSION[$queue_sort_key] = $_GET['sort']; +switch ($_SESSION[$queue_sort_key]) { case 'number': $tickets->extra(array( 'order_by'=>array(SqlExpression::times(new SqlField('number'), 1)) @@ -143,9 +150,9 @@ case 'updated': } // Apply requested pagination -$pagelimit=($_GET['limit'] && is_numeric($_GET['limit']))?$_GET['limit']:PAGE_LIMIT; $page=($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1; -$pageNav=new Pagenate($tickets->count(), $page,$pagelimit); +$pageNav=new Pagenate($tickets->count(), $page, PAGE_LIMIT); +$pageNav->setUrl('tickets.php', $args); $tickets = $pageNav->paginate($tickets); TicketForm::ensureDynamicDataView(); @@ -158,10 +165,12 @@ $_SESSION[':Q:tickets'] = $tickets; <!-- SEARCH FORM START --> <div id='basic_search'> <form action="tickets.php" method="get"> - <input type="hidden" name="status" value="search"> + <input type="hidden" name="a" value="search"> <table> <tr> - <td><input type="text" id="basic-ticket-search" name="query" size=30 value="<?php echo Format::htmlchars($_REQUEST['query']); ?>" + <td><input type="text" id="basic-ticket-search" name="query" + size=30 value="<?php echo Format::htmlchars($_REQUEST['query'], + true); ?>" autocomplete="off" autocorrect="off" autocapitalize="off"></td> <td><input type="submit" class="button" value="<?php echo __('Search'); ?>"></td> <td> <a href="#" onclick="javascript: @@ -191,7 +200,7 @@ $_SESSION[':Q:tickets'] = $tickets; 'priority,due' => __('Priority + Due Soon'), 'number' => __('Ticket Number'), ) as $mode => $desc) { ?> - <option value="<?php echo $mode; ?>" <?php if ($mode == $_REQUEST['sort']) echo 'selected="selected"'; ?>><?php echo $desc; ?></option> + <option value="<?php echo $mode; ?>" <?php if ($mode == $_SESSION[$queue_sort_key]) echo 'selected="selected"'; ?>><?php echo $desc; ?></option> <?php } ?> </select> </span> @@ -212,7 +221,8 @@ $_SESSION[':Q:tickets'] = $tickets; <?php csrf_token(); ?> <input type="hidden" name="a" value="mass_process" > <input type="hidden" name="do" id="action" value="" > - <input type="hidden" name="status" value="<?php echo Format::htmlchars($_REQUEST['status']); ?>" > + <input type="hidden" name="status" value="<?php echo + Format::htmlchars($_REQUEST['status'], true); ?>" > <table class="list" border="0" cellspacing="1" cellpadding="2" width="940"> <thead> <tr> @@ -364,10 +374,14 @@ $_SESSION[':Q:tickets'] = $tickets; </tfoot> </table> <?php - if($total>0){ //if we actually had any tickets returned. + if ($total>0) { //if we actually had any tickets returned. echo '<div> '.__('Page').':'.$pageNav->getPageLinks().' '; - echo '<a class="export-csv no-pjax" href="?a=export&status=' - .$_REQUEST['status'] .'">'.__('Export').'</a> <i class="help-tip icon-question-sign" href="#export"></i></div>'; + echo sprintf('<a class="export-csv no-pjax" href="?%s">%s</a>', + Http::build_query(array( + 'a' => 'export', 'h' => $hash, + 'status' => $_REQUEST['status'])), + __('Export')); + echo ' <i class="help-tip icon-question-sign" href="#export"></i></div>'; } ?> </form> </div> diff --git a/include/staff/users.inc.php b/include/staff/users.inc.php index 073c33a03431a9c39fd3007c95649d3733e77e92..9ff87132d2b1311b2ad620d17cd896129e656327 100644 --- a/include/staff/users.inc.php +++ b/include/staff/users.inc.php @@ -1,7 +1,7 @@ <?php if(!defined('OSTSCPINC') || !$thisstaff) die('Access Denied'); -$qstr=''; +$qs = array(); $users = User::objects() ->annotate(array('ticket_count'=>SqlAggregate::COUNT('tickets'))); @@ -14,6 +14,7 @@ if ($_REQUEST['query']) { 'org__name__contains' => $search, // TODO: Add search for cdata ))); + $qs += array('query' => $_REQUEST['query']); } $sortOptions = array('name' => 'name', @@ -42,11 +43,12 @@ $order_by="$order_column $order "; $total = $users->count(); $page=($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1; $pageNav=new Pagenate($total,$page,PAGE_LIMIT); -$pageNav->setURL('users.php',$qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order'])); $pageNav->paginate($users); -//Ok..lets roll...create the actual query -$qstr.='&order='.($order=='DESC'?'ASC':'DESC'); +$qstr = '&'. Http::build_query($qs); +$qs += array('sort' => $_REQUEST['sort'], 'order' => $_REQUEST['order']); +$pageNav->setURL('users.php', $qs); +$qstr.='&order='.($order=='DESC' ? 'ASC' : 'DESC'); //echo $query; $_SESSION[':Q:users'] = $users; diff --git a/js/filedrop.field.js b/js/filedrop.field.js index 7d332b575943224a0b6b9884b2a252bdbdb98b70..e451028a03fef02fa385f09497178778c125134c 100644 --- a/js/filedrop.field.js +++ b/js/filedrop.field.js @@ -179,11 +179,12 @@ } if (file.id) filenode.data('fileId', file.id); - if (file.download) + if (file.download_url) { filenode.find('.filename').prepend( $('<a class="no-pjax" target="_blank"></a>').text(file.name) - .attr('href', 'file.php?h='+escape(file.download)) + .attr('href', file.download_url) ); + } else filenode.find('.filename').prepend($('<span>').text(file.name)); this.$element.parent().find('.files').append(filenode); diff --git a/kb/file.php b/kb/file.php deleted file mode 100644 index 99664708a567a08d80eaca73ece27a7f9973f87a..0000000000000000000000000000000000000000 --- a/kb/file.php +++ /dev/null @@ -1,30 +0,0 @@ -<?php -/********************************************************************* - file.php - - Simply downloads the file...on hash validation as follows; - - * Hash must be 64 chars long. - * First 32 chars is the perm. file hash - * Next 32 chars is md5(file_id.session_id().file_hash) - - Peter Rotich <peter@osticket.com> - Copyright (c) 2006-2013 osTicket - http://www.osticket.com - - Released under the GNU General Public License WITHOUT ANY WARRANTY. - See LICENSE.TXT for details. - - vim: expandtab sw=4 ts=4 sts=4: -**********************************************************************/ -require('kb.inc.php'); -require_once(INCLUDE_DIR.'class.file.php'); -$h=trim($_GET['h']); -//basic checks -if(!$h || strlen($h)!=64 //32*2 - || !($file=AttachmentFile::lookup(substr($h,0,32))) //first 32 is the file hash. - || strcasecmp($h, $file->getDownloadHash())) //next 32 is file id + session hash. - Http::response(404, __('Unknown or invalid file')); - -$file->download(); -?> diff --git a/l.php b/l.php deleted file mode 100644 index a0520a8cbba31e26cd743e3e9546aebd329e0131..0000000000000000000000000000000000000000 --- a/l.php +++ /dev/null @@ -1,36 +0,0 @@ -<?php -/********************************************************************* - l.php - - Link redirection - - Jared Hancock <jared@osticket.com> - Copyright (c) 2006-2013 osTicket - http://www.osticket.com - - Released under the GNU General Public License WITHOUT ANY WARRANTY. - See LICENSE.TXT for details. - - vim: expandtab sw=4 ts=4 sts=4: -**********************************************************************/ -require 'secure.inc.php'; -//Basic url validation + token check. - -# PHP < 5.4.7 will not handle a URL like //host.tld/path correctly -if (!($url=trim($_GET['url']))) - Http::response(422, __('Invalid URL')); - -$check = (strpos($url, '//') === 0) ? 'http:' . $url : $url; -if (!Validator::is_url($check) || !$ost->validateLinkToken($_GET['auth'])) - Http::response(403, __('URL link not authorized')); -elseif (strpos($_SERVER['HTTP_ACCEPT'], 'text/html') === false) - Http::redirect($url); -?> -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> -<html> -<head> - <meta http-equiv="content-type" content="text/html; charset=utf-8"/> - <meta http-equiv="refresh" content="0;URL=<?php echo $url; ?>"/> -</head> -<body/> -</html> diff --git a/scp/attachment.php b/scp/attachment.php deleted file mode 100644 index d3283b877bd1300ad12d93d77bd7362a12943a41..0000000000000000000000000000000000000000 --- a/scp/attachment.php +++ /dev/null @@ -1,38 +0,0 @@ -<?php -/********************************************************************* - attachment.php - - Handles attachment downloads & access validation. - - Peter Rotich <peter@osticket.com> - Copyright (c) 2006-2013 osTicket - http://www.osticket.com - - Released under the GNU General Public License WITHOUT ANY WARRANTY. - See LICENSE.TXT for details. - - vim: expandtab sw=4 ts=4 sts=4: -**********************************************************************/ -require('staff.inc.php'); -require_once(INCLUDE_DIR.'class.attachment.php'); - -// Basic checks -if (!$thisstaff - || !$_GET['id'] - || !$_GET['h'] - || !($attachment=Attachment::lookup($_GET['id'])) - || !($file=$attachment->getFile()) - || strcasecmp(trim($_GET['h']), $file->getDownloadHash()) - || !($object=$attachment->getObject()) - || !$object instanceof ThreadEntry - || !($ticket=$object->getThread()->getObject()) - || !$ticket instanceof Ticket - ) - Http::response(404, __('Unknown or invalid file')); - -if (!$ticket->checkStaffPerm($thisstaff)) - die(__('Access Denied')); - -//Download the file.. -$file->download(); -?> diff --git a/scp/canned.php b/scp/canned.php index c8290c39f58b9f87494bb11f6ad436f329d598b6..d35ec00c6b58aa207b6cb68d1f6240f64d79764a 100644 --- a/scp/canned.php +++ b/scp/canned.php @@ -18,8 +18,8 @@ include_once(INCLUDE_DIR.'class.canned.php'); /* check permission */ if(!$thisstaff - || - !$thisstaff->getRole()->hasPerm(CannedModel::PERM_MANAGE)) { + || !$thisstaff->getRole()->hasPerm(CannedModel::PERM_MANAGE) + || !$cfg->isCannedResponseEnabled()) { header('Location: kb.php'); exit; } diff --git a/scp/file.php b/scp/file.php deleted file mode 100644 index 97687de0f0bb0a341ce895298f587891a9c179c0..0000000000000000000000000000000000000000 --- a/scp/file.php +++ /dev/null @@ -1,30 +0,0 @@ -<?php -/********************************************************************* - file.php - - Simply downloads the file...on hash validation as follows; - - * Hash must be 64 chars long. - * First 32 chars is the perm. file hash - * Next 32 chars is md5(file_id.session_id().file_hash) - - Peter Rotich <peter@osticket.com> - Copyright (c) 2006-2013 osTicket - http://www.osticket.com - - Released under the GNU General Public License WITHOUT ANY WARRANTY. - See LICENSE.TXT for details. - - vim: expandtab sw=4 ts=4 sts=4: -**********************************************************************/ -require('staff.inc.php'); -require_once(INCLUDE_DIR.'class.file.php'); -$h=trim($_GET['h']); -//basic checks -if(!$h || strlen($h)!=64 //32*2 - || !($file=AttachmentFile::lookup(substr($h,0,32))) //first 32 is the file hash. - || strcasecmp($file->getDownloadHash(), $h)) //next 32 is file id + session hash. - Http::response(404, __('Unknown or invalid file')); - -$file->download(); -?> diff --git a/scp/image.php b/scp/image.php deleted file mode 100644 index 4c4ddbfe6da36e21588c0359b96924c365fba64b..0000000000000000000000000000000000000000 --- a/scp/image.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -/********************************************************************* - image.php - - Simply downloads the file...on hash validation as follows; - - * Hash must be 64 chars long. - * First 32 chars is the perm. file hash - * Next 32 chars is md5(file_id.session_id().file_hash) - - Peter Rotich <peter@osticket.com> - Copyright (c) 2006-2013 osTicket - http://www.osticket.com - - Released under the GNU General Public License WITHOUT ANY WARRANTY. - See LICENSE.TXT for details. - - vim: expandtab sw=4 ts=4 sts=4: -**********************************************************************/ - -require('staff.inc.php'); -require_once(INCLUDE_DIR.'class.file.php'); -$h=trim($_GET['h']); -//basic checks -if(!$h || strlen($h)!=64 //32*2 - || !($file=AttachmentFile::lookup(substr($h,0,32))) //first 32 is the file hash. - || strcasecmp($h, $file->getDownloadHash())) //next 32 is file id + session hash. - Http::response(404, __('Unknown or invalid file')); - -if ($_GET['s'] && is_numeric($_GET['s'])) - $file->display($_GET['s']); -else - $file->display(); -?> diff --git a/scp/js/ticket.js b/scp/js/ticket.js index 600d8186bf7b0e63824daf67b9fdbe174807aacf..37b828a172974f4b0bcb9265c9cd358bbd4361ad 100644 --- a/scp/js/ticket.js +++ b/scp/js/ticket.js @@ -15,6 +15,20 @@ **********************************************************************/ var autoLock = { + // Defaults + lockId: 0, + timerId: 0, + lasteventTime: 0, + lastattemptTime: 0, + acquireTime: 0, + renewTime: 0, + renewFreq: 0, //renewal frequency in seconds...based on returned lock time. + time: 0, + lockAttempts: 0, //Consecutive lock attempt errors + maxattempts: 2, //Maximum failed lock attempts before giving up. + warn: true, + retry: true, + addEvent: function(elm, evType, fn, useCapture) { if(elm.addEventListener) { elm.addEventListener(evType, fn, useCapture); @@ -111,18 +125,6 @@ var autoLock = { void(autoLock.tid=parseInt($(':input[name=id]',fObj).val())); void(autoLock.lockTime=parseInt($(':input[name=locktime]',fObj).val())); - autoLock.lockId=0; - autoLock.timerId=0; - autoLock.lasteventTime=0; - autoLock.lastattemptTime=0; - autoLock.acquireTime=0; - autoLock.renewTime=0; - autoLock.renewFreq=0; //renewal frequency in seconds...based on returned lock time. - autoLock.time=0; - autoLock.lockAttempts=0; //Consecutive lock attempt errors - autoLock.maxattempts=2; //Maximum failed lock attempts before giving up. - autoLock.warn=true; - autoLock.retry=true; autoLock.watchDocument(); autoLock.resetTimer(); autoLock.addEvent(window,'unload',autoLock.releaseLock,true); //Release lock regardless of any activity. diff --git a/scp/l.php b/scp/l.php deleted file mode 100644 index ec4705a0f2763101c2ac2b1b96c3af174793316a..0000000000000000000000000000000000000000 --- a/scp/l.php +++ /dev/null @@ -1,36 +0,0 @@ -<?php -/********************************************************************* - l.php - - Link redirection - - Jared Hancock <jared@osticket.com> - Copyright (c) 2006-2013 osTicket - http://www.osticket.com - - Released under the GNU General Public License WITHOUT ANY WARRANTY. - See LICENSE.TXT for details. - - vim: expandtab sw=4 ts=4 sts=4: -**********************************************************************/ -require_once 'staff.inc.php'; -//Basic url validation + token check. - -# PHP < 5.4.7 will not handle a URL like //host.tld/path correctly -if (!($url=trim($_GET['url']))) - Http::response(422, __('Invalid URL')); - -$check = (strpos($url, '//') === 0) ? 'http:' . $url : $url; -if (!Validator::is_url($check) || !$ost->validateLinkToken($_GET['auth'])) - Http::response(403, __('URL link not authorized')); -elseif (strpos($_SERVER['HTTP_ACCEPT'], 'text/html') === false) - Http::redirect($url); -?> -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> -<html> -<head> - <meta http-equiv="content-type" content="text/html; charset=utf-8"/> - <meta http-equiv="refresh" content="0;URL=<?php echo $url; ?>"/> -</head> -<body/> -</html> diff --git a/setup/css/wizard.css b/setup/css/wizard.css index 838f2de0f8c3f87a153549c54dbcb4c2cc1ea93b..a742c75cbbbf0cc2c91b72ffbabf6a99abef16a0 100644 --- a/setup/css/wizard.css +++ b/setup/css/wizard.css @@ -3,7 +3,7 @@ body { background: url('../images/background.jpg?1312906017') top left repeat-x #wizard { background: #fff; width: 800px; margin: 30px auto; padding: 10px; border: 1px solid #2a67ac; border-right: 2px solid #2a67ac; border-bottom: 3px solid #2a67ac; overflow: hidden; margin-bottom:5px;} -a { color: #2a67ac; } +a { color: #2a67ac; display: inline-block; } /* Helpers */ .centered {text-align: center;} diff --git a/setup/inc/install-prereq.inc.php b/setup/inc/install-prereq.inc.php index 4c3167e6b71e07a85a32eed73f7ef0ac4bcb57f5..12c97730a035cadf8aec02d677280f38b81c8e8a 100644 --- a/setup/inc/install-prereq.inc.php +++ b/setup/inc/install-prereq.inc.php @@ -9,7 +9,7 @@ if(!defined('SETUPINC')) die('Kwaheri!'); <p><?php echo __('We are delighted you have chosen osTicket for your customer support ticketing system!');?></p> <p><?php echo __("The installer will guide you every step of the way in the installation process. You're minutes away from your awesome customer support system!");?></p> </div> - <h2><?php echo __('Prerequisites');?>.</h3> + <h2><?php echo __('Prerequisites');?></h3> <p><?php echo __("Before we begin, we'll check your server configuration to make sure you meet the minimum requirements to install and run osTicket.");?></p> <h3><?php echo __('Required');?>: <font color="red"><?php echo $errors['prereq']; ?></font></h3> <?php echo __('These items are necessary in order to install and use osTicket.');?>