diff --git a/css/rtl.css b/css/rtl.css index 64c4d086bb36824e3247659718138e4e78cebc01..aa5dfec8c6361d8eab32cbf109f208ec47885b16 100644 --- a/css/rtl.css +++ b/css/rtl.css @@ -27,10 +27,12 @@ right: auto; left: 1em; } -.rtl #nav .inactive li { +.rtl #nav .inactive li, +.rtl #sub_nav li { text-align: right; } -.rtl #nav .inactive li a { +.rtl #nav .inactive li a, +.rtl #sub_nav li a { background-position: 100% 50%; padding-left: 0; padding-right: 24px; @@ -39,6 +41,10 @@ left: auto; right: -1px; } +.rtl #sub_nav li + li > a { + margin-left: 0; + margin-right: 10px; +} .rtl .tip_close { right: auto; left: 0.5em; diff --git a/include/ajax.users.php b/include/ajax.users.php index f3b6c247a00fd57dc8379c077c6e07c1c0e084da..c7259983f4b0a698da99c0e81023c34043ddbbd1 100644 --- a/include/ajax.users.php +++ b/include/ajax.users.php @@ -35,7 +35,7 @@ class UsersAjaxAPI extends AjaxController { if (!$type || !strcasecmp($type, 'remote')) { foreach (AuthenticationBackend::searchUsers($_REQUEST['q']) as $u) { - $name = "{$u['first']} {$u['last']}"; + $name = new PersonsName(array('first' => $u['first'], 'last' => $u['last'])); $users[] = array('email' => $u['email'], 'name'=>$name, 'info' => "{$u['email']} - $name (remote)", 'id' => "auth:".$u['id'], "/bin/true" => $_REQUEST['q']); @@ -48,7 +48,8 @@ class UsersAjaxAPI extends AjaxController { ? ' OR email.address IN ('.implode(',',db_input($emails)).') ' : ''; - $escaped = db_input(strtolower($_REQUEST['q']), false); + $q = str_replace(' ', '%', $_REQUEST['q']); + $escaped = db_input($q, false); $sql='SELECT DISTINCT user.id, email.address, name ' .' FROM '.USER_TABLE.' user ' .' JOIN '.USER_EMAIL_TABLE.' email ON user.id = email.user_id ' @@ -57,7 +58,6 @@ class UsersAjaxAPI extends AjaxController { .' WHERE email.address LIKE \'%'.$escaped.'%\' OR user.name LIKE \'%'.$escaped.'%\' OR value.value LIKE \'%'.$escaped.'%\''.$remote_emails - .' ORDER BY user.created ' .' LIMIT '.$limit; if(($res=db_query($sql)) && db_num_rows($res)){ @@ -68,11 +68,12 @@ class UsersAjaxAPI extends AjaxController { break; } } - $name = Format::htmlchars($name); + $name = Format::htmlchars(new PersonsName($name)); $users[] = array('email'=>$email, 'name'=>$name, 'info'=>"$email - $name", "id" => $id, "/bin/true" => $_REQUEST['q']); } } + usort($users, function($a, $b) { return strcmp($a['name'], $b['name']); }); } return $this->json_encode(array_values($users)); diff --git a/include/class.charset.php b/include/class.charset.php index aa26cb2ccb36cb4232c144408d2405430dde3fe6..d33c3abd105e067c169cff6a348f774b2d3bf079 100644 --- a/include/class.charset.php +++ b/include/class.charset.php @@ -31,6 +31,9 @@ class Charset { return 'cp949'; case preg_match('`^iso-?(\S+)$`i', $charset, $match): return "ISO-".$match[1]; + // GBK superceded gb2312 and is backward compatible + case preg_match('`^gb2312`i', $charset): + return 'GBK'; // Incorrect, bogus, ambiguous or empty charsets // ISO-8859-1 is assumed case preg_match('`^(default|x-user-defined|iso|us-ascii)$`i', $charset): diff --git a/include/class.dept.php b/include/class.dept.php index 588a71c127f553c10010a3f6dbf9eda3d506163c..39f914f9f401233909742e991d528c7b6551c1c3 100644 --- a/include/class.dept.php +++ b/include/class.dept.php @@ -134,6 +134,8 @@ class Dept extends VerySimpleModel { } function getMembers($criteria=null) { + global $cfg; + if (!$this->_members || $criteria) { $members = Staff::objects() ->filter(Q::any(array( @@ -152,7 +154,16 @@ class Dept extends VerySimpleModel { 'onvacation' => 0, )); - $members->order_by('lastname', 'firstname'); + switch ($cfg->getDefaultNameFormat()) { + case 'last': + case 'lastfirst': + case 'legal': + $members->order_by('lastname', 'firstname'); + break; + + default: + $members->order_by('firstname', 'lastname'); + } if ($criteria) return $members->all(); diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php index 9295eec836f5cbd1345094b0f80332ff003fe5da..10419a2099c651e33265d443ccc4dc5ca788e292 100644 --- a/include/class.dynamic_forms.php +++ b/include/class.dynamic_forms.php @@ -1101,6 +1101,13 @@ class DynamicFormEntry extends VerySimpleModel { } } + /** + * Save the form entry and all associated answers. + * + * Returns: + * (mixed) FALSE if updated failed, otherwise the number of dirty answers + * which were save is returned (which may be ZERO). + */ function save($refetch=false) { if (count($this->dirty)) $this->set('updated', new SqlFunction('NOW')); @@ -1108,6 +1115,7 @@ class DynamicFormEntry extends VerySimpleModel { if (!parent::save($refetch || count($this->dirty))) return false; + $dirty = 0; foreach ($this->getAnswers() as $a) { $field = $a->getField(); @@ -1129,8 +1137,11 @@ class DynamicFormEntry extends VerySimpleModel { else { $a->set('value', $val); } + if ($a->dirty) + $dirty++; $a->save(); } + return $dirty; } function delete() { diff --git a/include/class.format.php b/include/class.format.php index 61613331e0346da6a16f07f44f6c648e986bce92..83eb012168a119a405549be8e9872240f4808f17 100644 --- a/include/class.format.php +++ b/include/class.format.php @@ -219,7 +219,7 @@ class Format { 'schemes' => 'href: aim, feed, file, ftp, gopher, http, https, irc, mailto, news, nntp, sftp, ssh, telnet; *:file, http, https; src: cid, http, https, data', 'hook_tag' => function($e, $a=0) { return Format::__html_cleanup($e, $a); }, 'elements' => '*+iframe', - 'spec' => 'iframe=-*,height,width,type,src(match="`^(https?:)?//(www\.)?(youtube|dailymotion|vimeo)\.com/`i"),frameborder; div=data-mid'.($spec ? '; '.$spec : ''), + 'spec' => 'iframe=-*,height,width,type,src(match="`^(https?:)?//(www\.)?(youtube|dailymotion|vimeo)\.com/`i"),frameborder'.($spec ? '; '.$spec : ''), ); return Format::html($html, $config); @@ -228,7 +228,7 @@ class Format { function localizeInlineImages($text) { // Change file.php urls back to content-id's return preg_replace( - '/src="(?:\/[^"]+?)?\/file\\.php\\?(?:\w+=[^&]+&(?:amp;)?)*?key=([^&]+)[^"]*/', + '`src="(?:https?:/)?(?:/[^/"]+)*?/file\\.php\\?(?:\w+=[^&]+&(?:amp;)?)*?key=([^&]+)[^"]*`', 'src="cid:$1', $text); } diff --git a/include/class.mailer.php b/include/class.mailer.php index a58e37c0581b2f246932ebdb74e8d6bd3f739e7d..c398df9f5ccf26f8bca4a8d16a21009fbc52cdf6 100644 --- a/include/class.mailer.php +++ b/include/class.mailer.php @@ -395,7 +395,7 @@ class Mailer { // in a response if ($reply_tag || $mid_token) { $message = "<div style=\"display:none\" - data-mid=\"$mid_token\">$reply_tag</div>$message"; + class=\"mid-$mid_token\">$reply_tag</div>$message"; } $txtbody = rtrim(Format::html2text($message, 90, false)) . ($messageId ? "\nRef-Mid: $messageId\n" : ''); diff --git a/include/class.mailfetch.php b/include/class.mailfetch.php index 0fa55e099358a53c56a0e6e29a99e21b910bdd7b..8a8839a9eaa7e51919066d901b50c098bbab5e08 100644 --- a/include/class.mailfetch.php +++ b/include/class.mailfetch.php @@ -361,7 +361,7 @@ class MailFetcher { } //search for specific mime type parts....encoding is the desired encoding. - function getPart($mid, $mimeType, $encoding=false, $struct=null, $partNumber=false, $recurse=-1) { + function getPart($mid, $mimeType, $encoding=false, $struct=null, $partNumber=false, $recurse=-1, $recurseIntoRfc822=true) { if(!$struct && $mid) $struct=@imap_fetchstructure($this->mbox, $mid); @@ -397,15 +397,21 @@ class MailFetcher { && ($content = $this->tnef->getBody('text/html', $encoding))) return $content; - //Do recursive search - $text=''; - if($struct && $struct->parts && $recurse) { + // Do recursive search + $text = ''; + $ctype = $this->getMimeType($struct); + if ($struct && $struct->parts && $recurse + // Do not recurse into email (rfc822) attachments unless requested + && (strtolower($ctype) !== 'message/rfc822' || $recurseIntoRfc822) + ) { while(list($i, $substruct) = each($struct->parts)) { - if($partNumber) + if ($partNumber) $prefix = $partNumber . '.'; - if (($result=$this->getPart($mid, $mimeType, $encoding, - $substruct, $prefix.($i+1), $recurse-1))) - $text.=$result; + if ($result = $this->getPart($mid, $mimeType, $encoding, + $substruct, $prefix.($i+1), $recurse-1, $recurseIntoRfc822) + ) { + $text .= $result; + } } } @@ -520,9 +526,13 @@ class MailFetcher { if (strtolower($ctype) == 'multipart/report') { foreach ($struct->parameters as $p) { if (strtolower($p->attribute) == 'report-type' - && $p->value == 'delivery-status') { - return new TextThreadEntryBody( $this->getPart( - $mid, 'text/plain', $this->charset, $struct, false, 1)); + && $p->value == 'delivery-status' + ) { + if ($body = $this->getPart( + $mid, 'text/plain', $this->charset, $struct, false, 3, false + )) { + return new TextThreadBody($body); + } } } } diff --git a/include/class.mailparse.php b/include/class.mailparse.php index 855afe9a0ae16bad3c94b03c7c3b017097a1850b..6564a4a22ac9f3190543c18aa4971ac06f1826ab 100644 --- a/include/class.mailparse.php +++ b/include/class.mailparse.php @@ -278,9 +278,8 @@ class Mail_Parse { && isset($this->struct->ctype_parameters['report-type']) && $this->struct->ctype_parameters['report-type'] == 'delivery-status' ) { - return new TextThreadBody( - $this->getPart($this->struct, 'text/plain', 1) - ); + if ($body = $this->getPart($this->struct, 'text/plain', 3, false)) + return new TextThreadBody($body); } return false; } @@ -326,10 +325,27 @@ class Mail_Parse { return $body; } - function getPart($struct, $ctypepart, $recurse=-1) { + /** + * Fetch all the parts of the message for a specific MIME type. The + * parts are automatically transcoded to UTF-8 and concatenated together + * in the event more than one body of the requested type exists. + * + * Parameters: + * $struct - (<Mail_mime>) decoded message + * $ctypepart - (string) 'text/plain' or 'text/html', message body + * format to retrieve from the mail + * $recurse - (int:-1) levels acceptable to recurse into. Default is to + * recurse as needed. + * $recurseIntoRfc822 - (bool:true) proceed to recurse into + * message/rfc822 bodies to look for the message body format + * requested. For something like a bounce notice, where another + * email might be attached to the email, set this to false to avoid + * finding the wrong body. + */ + function getPart($struct, $ctypepart, $recurse=-1, $recurseIntoRfc822=true) { - if($struct && !@$struct->parts) { - $ctype = @strtolower($struct->ctype_primary.'/'.$struct->ctype_secondary); + $ctype = @strtolower($struct->ctype_primary.'/'.$struct->ctype_secondary); + if ($struct && !@$struct->parts) { if (@$struct->disposition && (strcasecmp($struct->disposition, 'inline') !== 0)) return ''; @@ -349,10 +365,16 @@ class Mail_Parse { return $content; $data=''; - if($struct && @$struct->parts && $recurse) { - foreach($struct->parts as $i=>$part) { - if($part && ($text=$this->getPart($part,$ctypepart,$recurse - 1))) - $data.=$text; + if ($struct && @$struct->parts && $recurse + // Do not recurse into email (rfc822) attachments unless requested + && ($ctype !== 'message/rfc822' || $recurseIntoRfc822) + ) { + foreach ($struct->parts as $i=>$part) { + if ($part && ($text=$this->getPart($part, $ctypepart, + $recurse-1, $recurseIntoRfc822)) + ) { + $data .= $text; + } } } return $data; diff --git a/include/class.search.php b/include/class.search.php index 6bbc7a3657a53e61399812d0ff5b182e44c1c30f..7225f652e7ab3397ca79fcd6c687013c0b313733 100644 --- a/include/class.search.php +++ b/include/class.search.php @@ -848,11 +848,16 @@ class DepartmentChoiceField extends ChoiceField { class AssigneeChoiceField extends ChoiceField { function getChoices() { + global $thisstaff; + $items = array( 'M' => __('Me'), 'T' => __('One of my teams'), ); foreach (Staff::getStaffMembers() as $id=>$name) { + // Don't include $thisstaff (since that's 'Me') + if ($thisstaff && $thisstaff->getId() == $id) + continue; $items['s' . $id] = $name; } foreach (Team::getTeams() as $id=>$name) { diff --git a/include/class.staff.php b/include/class.staff.php index 2654387c9896a379fb58afd50e2e1d2c838dd490..4a13e1b382104d893ad1aae63376667ca53ce48c 100644 --- a/include/class.staff.php +++ b/include/class.staff.php @@ -103,7 +103,7 @@ implements AuthenticatedUser, EmailContact { if (!$bk && $this->backend) $bk = $this->backend; - return StaffAuthenticationBackend::getBackend($authkey); + return StaffAuthenticationBackend::getBackend($bk); } function setAuthKey($key) { @@ -230,7 +230,7 @@ implements AuthenticatedUser, EmailContact { } function getName() { - return new PersonsName($this->firstname.' '.$this->lastname); + return new PersonsName(array('first' => $this->ht['firstname'], 'last' => $this->ht['lastname'])); } function getFirstName() { @@ -658,8 +658,8 @@ implements AuthenticatedUser, EmailContact { } static function getStaffMembers($availableonly=false) { - - $members = static::objects()->order_by('lastname', 'firstname'); + global $cfg; + $members = static::objects(); if ($availableonly) { $members = $members->filter(array( @@ -669,6 +669,17 @@ implements AuthenticatedUser, EmailContact { )); } + switch ($cfg->getDefaultNameFormat()) { + case 'last': + case 'lastfirst': + case 'legal': + $members->order_by('lastname', 'firstname'); + break; + + default: + $members->order_by('firstname', 'lastname'); + } + $users=array(); foreach ($members as $M) { $users[$M->getId()] = $M->getName(); diff --git a/include/class.thread.php b/include/class.thread.php index 7e97f9fe299c421508b173abb621bc7e4027b7b9..3cf9308025ffed40b4ca3744dd538c16d9e4715f 100644 --- a/include/class.thread.php +++ b/include/class.thread.php @@ -188,7 +188,6 @@ class Thread extends VerySimpleModel { )) { $vars['message'] = $body; $vars['userId'] = $mailinfo['userId'] ?: $object->getUserId(); - $vars['origin'] = 'Email'; if ($object instanceof Threadable) return $object->postThreadEntry('M', $vars); @@ -197,7 +196,26 @@ class Thread extends VerySimpleModel { else throw new Exception('Cannot continue discussion with abstract thread'); } - // XXX: Consider collaborator role + // Consider collaborator role (disambiguate staff members as + // collaborators). Normally, the block above should match based + // on the Referenced message-id header + elseif ($object instanceof Ticket + && ($E = UserEmail::lookup($mailinfo['email'])) + && ($C = Collaborator::lookup(array( + 'ticketId' => $object->getId(), 'userId' => $E->user_id + ))) + ) { + $vars['userId'] = $mailinfo['userId'] ?: $C->getUserId(); + $vars['message'] = $body; + + if ($object instanceof Threadable) + return $object->postThreadEntry('M', $vars); + elseif ($this instanceof ObjectThread) + $this->addMessage($vars, $errors); + else + throw new Exception('Cannot continue discussion with abstract thread'); + } + // Accept internal note from staff members' replies elseif ($mailinfo['staffId'] || ($mailinfo['staffId'] = Staff::getIdByEmail($mailinfo['email']))) { $vars['staffId'] = $mailinfo['staffId']; @@ -983,11 +1001,20 @@ class ThreadEntry extends VerySimpleModel { // Search for the message-id token in the body // *DEPRECATED* the current algo on outgoing mail will use // Mailer::getMessageId as the message id tagged here - if (preg_match('`(?:data-mid="|Ref-Mid: )([^"\s]*)(?:$|")`', - $mailinfo['message'], $match)) + if (preg_match('`(?:class="mid-|Ref-Mid: )([^"\s]*)(?:$|")`', + $mailinfo['message'], $match)) { + // Support new Message-Id format + if (($info = Mailer::decodeMessageId($match[1])) + && $info['loopback'] + && $info['entryId'] + ) { + return ThreadEntry::lookup($info['entryId']); + } + // Support old (deprecated) reference format if ($thread = ThreadEntry::lookupByRefMessageId($match[1], $mailinfo['email'])) return $thread; + } return null; } diff --git a/include/class.ticket.php b/include/class.ticket.php index b9f740d67210e2b5deb6c1cb768693f76cb32742..30a5df7fd556d85d55074691e093289992d5aeab 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -1246,7 +1246,7 @@ implements RestrictedAccess, Threadable { // 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'), + $note = sprintf(__('Status changed from %1$s to %2$s by %3$s'), $this->getStatus(), $status, $thisstaff ?: 'SYSTEM'); @@ -3389,6 +3389,9 @@ implements RestrictedAccess, Threadable { // Not assigned...save optional note if any if (!$vars['assignId'] && $vars['note']) { + if (!$cfg->isHtmlThreadEnabled()) { + $vars['note'] = new TextThreadBody($vars['note']); + } $ticket->logNote(_S('New Ticket'), $vars['note'], $thisstaff, false); } else { diff --git a/include/class.user.php b/include/class.user.php index 71fa471918f79cba6e16aff797eba074cfddd2e2..7cb3cf2dfb9ef1855e56c6269a1f125831c3ae4b 100644 --- a/include/class.user.php +++ b/include/class.user.php @@ -193,7 +193,7 @@ class User extends UserModel { var $_entries; var $_forms; - static function fromVars($vars, $create=true) { + static function fromVars($vars, $create=true, $update=false) { // Try and lookup by email address $user = static::lookupByEmail($vars['email']); if (!$user && $create) { @@ -227,6 +227,10 @@ class User extends UserModel { } Signal::send('user.created', $user); } + elseif ($update) { + $errors = array(); + $user->updateInfo($vars, $errors, true); + } return $user; } @@ -506,7 +510,7 @@ class User extends UserModel { $error = false; foreach ($users as $u) { $vars = array_combine($keys, $u); - if (!static::fromVars($vars)) { + if (!static::fromVars($vars, true, true)) { $error = sprintf(__('Unable to import user: %s'), print_r($vars, true)); break; @@ -566,7 +570,6 @@ class User extends UserModel { if (($f=$entry->getDynamicForm()) && $f->get('type') == 'U') { if (($name = $f->getField('name'))) { $this->name = $name->getClean(); - $this->save(); } if (($email = $f->getField('email'))) { @@ -574,10 +577,12 @@ class User extends UserModel { $this->default_email->save(); } } - $entry->save(); + // DynamicFormEntry::save returns the number of answers updated + if ($entry->save()) { + $this->updated = SqlFunction::NOW(); + } } - - return true; + return $this->save(); } function save($refetch=false) { diff --git a/include/i18n/en_US/help/tips/staff.agent.yaml b/include/i18n/en_US/help/tips/staff.agent.yaml index 37fbe68478d27ac6bdd1ad380f16317404f2e346..298faa1bbe19ab208561d6949618eb88129987d2 100644 --- a/include/i18n/en_US/help/tips/staff.agent.yaml +++ b/include/i18n/en_US/help/tips/staff.agent.yaml @@ -13,14 +13,6 @@ # must match the HTML #ids put into the page template. # --- -add_new_agent: - title: Add New Agent - content: > - -agent_staff_information: - title: Agent (Staff) Information - content: > - username: title: Username content: > diff --git a/include/i18n/en_US/help/tips/staff.agents.yaml b/include/i18n/en_US/help/tips/staff.agents.yaml index e0dbf0e8c02fb632c40cfe3e64660ac7f68be446..f358b0532a048173b6fb37b3579c5b3857d33749 100644 --- a/include/i18n/en_US/help/tips/staff.agents.yaml +++ b/include/i18n/en_US/help/tips/staff.agents.yaml @@ -13,31 +13,3 @@ # must match the HTML #ids put into the page template. # --- -add_new_agent: - title: Add New Agent - content: > - -agents: - title: Agents - content: > - -name: - title: Name - content: > - -username: - title: Username - content: > - -status: - title: Status - content: > - -created: - title: Created - content: > - -last_login: - title: Last Login - content: > - diff --git a/include/i18n/en_US/help/tips/staff.departments.yaml b/include/i18n/en_US/help/tips/staff.departments.yaml index 782aa58ec5a0c44d6054c37b4c7b5f16109744d5..63858a53e2c7ce710784fdeec85cab77b2342191 100644 --- a/include/i18n/en_US/help/tips/staff.departments.yaml +++ b/include/i18n/en_US/help/tips/staff.departments.yaml @@ -13,27 +13,11 @@ # must match the HTML #ids put into the page template. # --- -department: - title: Department - content: > - -name: - title: Name - content: > - type: title: Type content: > If the Department’s Type is Private, then the Department Signature will not be available on response nor will the department assignment show from the Client Portal. -users: - title: Users - content: > - -email_address: - title: Email Address - content: > - dept_manager: title: Department Manager content: > diff --git a/include/staff/apikeys.inc.php b/include/staff/apikeys.inc.php index ddaf990966931e54b5753a4a2763d11257a239d4..d244ea256064c42701d4aa28fe167728f14c6191 100644 --- a/include/staff/apikeys.inc.php +++ b/include/staff/apikeys.inc.php @@ -119,11 +119,11 @@ endif; <a class="close" href=""><i class="icon-remove-circle"></i></a> <hr/> <p class="confirm-action" style="display:none;" id="enable-confirm"> - <?php echo sprintf(__('Are you sure want to <b>enable</b> %s?'), + <?php echo sprintf(__('Are you sure you want to <b>enable</b> %s?'), _N('selected API key', 'selected API keys', 2));?> </p> <p class="confirm-action" style="display:none;" id="disable-confirm"> - <?php echo sprintf(__('Are you sure want to <b>disable</b> %s?'), + <?php echo sprintf(__('Are you sure you want to <b>disable</b> %s?'), _N('selected API key', 'selected API keys', 2)); ?> </p> <p class="confirm-action" style="display:none;" id="delete-confirm"> diff --git a/include/staff/banlist.inc.php b/include/staff/banlist.inc.php index 3454ae4803342c9b2ab52c4904a0c09dc017296f..3fd33ed5a98840bf6136ed1b10b9c06f96ffa6b3 100644 --- a/include/staff/banlist.inc.php +++ b/include/staff/banlist.inc.php @@ -145,11 +145,11 @@ endif; <a class="close" href=""><i class="icon-remove-circle"></i></a> <hr/> <p class="confirm-action" style="display:none;" id="enable-confirm"> - <?php echo sprintf(__('Are you sure want to <b>enable</b> %s?'), + <?php echo sprintf(__('Are you sure you want to <b>enable</b> %s?'), _N('selected ban rule', 'selected ban rules', 2));?> </p> <p class="confirm-action" style="display:none;" id="disable-confirm"> - <?php echo sprintf(__('Are you sure want to <b>disable</b> %s?'), + <?php echo sprintf(__('Are you sure you want to <b>disable</b> %s?'), _N('selected ban rule', 'selected ban rules', 2));?> </p> <p class="confirm-action" style="display:none;" id="delete-confirm"> diff --git a/include/staff/cannedresponses.inc.php b/include/staff/cannedresponses.inc.php index 05d098a34fc350ad13033c6e99561f9c424e5389..bca9ee17c5e0347db832d50b532f628e839286d6 100644 --- a/include/staff/cannedresponses.inc.php +++ b/include/staff/cannedresponses.inc.php @@ -130,11 +130,11 @@ endif; <a class="close" href=""><i class="icon-remove-circle"></i></a> <hr/> <p class="confirm-action" style="display:none;" id="enable-confirm"> - <?php echo sprintf(__('Are you sure want to <b>enable</b> %s?'), + <?php echo sprintf(__('Are you sure you want to <b>enable</b> %s?'), _N('selected canned response', 'selected canned responses', 2));?> </p> <p class="confirm-action" style="display:none;" id="disable-confirm"> - <?php echo sprintf(__('Are you sure want to <b>disable</b> %s?'), + <?php echo sprintf(__('Are you sure you want to <b>disable</b> %s?'), _N('selected canned response', 'selected canned responses', 2));?> </p> <p class="confirm-action" style="display:none;" id="delete-confirm"> diff --git a/include/staff/categories.inc.php b/include/staff/categories.inc.php index 05f0b5108a7de30b33506828084a83cc534f6791..d7b39d8817c3a6c34f58d9b7fe40cf0556db08b2 100644 --- a/include/staff/categories.inc.php +++ b/include/staff/categories.inc.php @@ -115,11 +115,11 @@ endif; <a class="close" href=""><i class="icon-remove-circle"></i></a> <hr/> <p class="confirm-action" style="display:none;" id="make_public-confirm"> - <?php echo sprintf(__('Are you sure want to make %s <b>public</b>?'), + <?php echo sprintf(__('Are you sure you want to make %s <b>public</b>?'), _N('selected category', 'selected categories', 2));?> </p> <p class="confirm-action" style="display:none;" id="make_private-confirm"> - <?php echo sprintf(__('Are you sure want to make %s <b>private</b> (internal)?'), + <?php echo sprintf(__('Are you sure you want to make %s <b>private</b> (internal)?'), _N('selected category', 'selected categories', 2));?> </p> <p class="confirm-action" style="display:none;" id="delete-confirm"> diff --git a/include/staff/departments.inc.php b/include/staff/departments.inc.php index 7941d6fa4631a866d6aafdc3b23f0e557a7b1f96..c07f2d32b60f31a2fe5d0fad5520c4a88c7637b6 100644 --- a/include/staff/departments.inc.php +++ b/include/staff/departments.inc.php @@ -153,11 +153,11 @@ endif; <a class="close" href=""><i class="icon-remove-circle"></i></a> <hr/> <p class="confirm-action" style="display:none;" id="make_public-confirm"> - <?php echo sprintf(__('Are you sure want to make %s <b>public</b>?'), + <?php echo sprintf(__('Are you sure you want to make %s <b>public</b>?'), _N('selected department', 'selected departments', 2));?> </p> <p class="confirm-action" style="display:none;" id="make_private-confirm"> - <?php echo sprintf(__('Are you sure want to make %s <b>private</b> (internal)?'), + <?php echo sprintf(__('Are you sure you want to make %s <b>private</b> (internal)?'), _N('selected department', 'selected departments', 2));?> </p> <p class="confirm-action" style="display:none;" id="delete-confirm"> diff --git a/include/staff/directory.inc.php b/include/staff/directory.inc.php index 99c65b521549321909bdf48774258e4685e90f0d..4e53d3597bb75cf2075b9045f81aa5b4783f92df 100644 --- a/include/staff/directory.inc.php +++ b/include/staff/directory.inc.php @@ -9,7 +9,6 @@ $agents = Staff::objects() if($_REQUEST['q']) { $searchTerm=$_REQUEST['q']; if($searchTerm){ - $query=db_real_escape($searchTerm,false); //escape the term ONLY...no quotes. if(is_numeric($searchTerm)){ $agents->filter(Q::any(array( 'phone__contains'=>$searchTerm, @@ -33,10 +32,20 @@ if($_REQUEST['did'] && is_numeric($_REQUEST['did'])) { $qs += array('did' => $_REQUEST['did']); } -$sortOptions=array('name'=>'firstname,lastname','email'=>'email','dept'=>'dept__name', +$sortOptions=array('name'=>array('firstname','lastname'),'email'=>'email','dept'=>'dept__name', 'phone'=>'phone','mobile'=>'mobile','ext'=>'phone_ext', 'created'=>'created','login'=>'lastlogin'); $orderWays=array('DESC'=>'-','ASC'=>''); + +switch ($cfg->getDefaultNameFormat()) { +case 'last': +case 'lastfirst': +case 'legal': + $sortOptions['name'] = 'staff.lastname, staff.firstname'; + break; +// Otherwise leave unchanged +} + $sort=($_REQUEST['sort'] && $sortOptions[strtolower($_REQUEST['sort'])])?strtolower($_REQUEST['sort']):'name'; //Sorting options... if($sort && $sortOptions[$sort]) { @@ -50,7 +59,7 @@ if($_REQUEST['order'] && $orderWays[strtoupper($_REQUEST['order'])]) { $x=$sort.'_sort'; $$x=' class="'.strtolower($_REQUEST['order'] ?: 'desc').'" '; -foreach (explode(',', $order_column) as $C) { +foreach ((array) $order_column as $C) { $agents->order_by($order.$C); } diff --git a/include/staff/dynamic-list.inc.php b/include/staff/dynamic-list.inc.php index 6a09009906e1d54c33a3e3a187e4a9bb35e87a7b..ed7030ff611136393d2e5b5889d5aba1c63c8788 100644 --- a/include/staff/dynamic-list.inc.php +++ b/include/staff/dynamic-list.inc.php @@ -25,7 +25,7 @@ $info=Format::htmlchars(($errors && $_POST) ? array_merge($info,$_POST) : $info) <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> <input type="hidden" name="id" value="<?php echo $info['id']; ?>"> <h2><?php echo __('Custom List'); ?> - <?php echo $list ? $list->getName() : 'Add new list'; ?></h2> + <?php echo $list ? $list->getName() : __('Add new list'); ?></h2> <ul class="tabs" id="list-tabs"> <li class="active"><a href="#definition"> diff --git a/include/staff/filter.inc.php b/include/staff/filter.inc.php index 5b4cfddcf95762c4ec963fea9283fa574dcccd1c..3c0f8f49ada7acf811697b500ee1fd104b447b1f 100644 --- a/include/staff/filter.inc.php +++ b/include/staff/filter.inc.php @@ -142,7 +142,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <?php } ?> </select> <select name="rule_h<?php echo $i; ?>"> - <option value="0">— <?php echo __('Select One');?> ‐</option> + <option value="0">— <?php echo __('Select One');?> —</option> <?php foreach($match_types as $k=>$v){ $sel=($info["rule_h$i"]==$k)?'selected="selected"':''; diff --git a/include/staff/filters.inc.php b/include/staff/filters.inc.php index 8e1c4946b60d30f41907aebf9ea78fcca6dd32d9..ef04bf366c44bd7830830c8e499c08e12d964b8f 100644 --- a/include/staff/filters.inc.php +++ b/include/staff/filters.inc.php @@ -129,11 +129,11 @@ endif; <a class="close" href=""><i class="icon-remove-circle"></i></a> <hr/> <p class="confirm-action" style="display:none;" id="enable-confirm"> - <?php echo sprintf(__('Are you sure want to <b>enable</b> %s?'), + <?php echo sprintf(__('Are you sure you want to <b>enable</b> %s?'), _N('selected filter', 'selected filters', 2));?> </p> <p class="confirm-action" style="display:none;" id="disable-confirm"> - <?php echo sprintf(__('Are you sure want to <b>disable</b> %s?'), + <?php echo sprintf(__('Are you sure you want to <b>disable</b> %s?'), _N('selected filter', 'selected filters', 2));?> </p> <p class="confirm-action" style="display:none;" id="delete-confirm"> diff --git a/include/staff/groups.inc.php b/include/staff/groups.inc.php index 6c7b794984a0aee199e01dc769626297c1bb38b8..c1ec406f9510422ae1d5bfac84e9f4c3d55cdeae 100644 --- a/include/staff/groups.inc.php +++ b/include/staff/groups.inc.php @@ -148,11 +148,11 @@ endif; <a class="close" href=""><i class="icon-remove-circle"></i></a> <hr/> <p class="confirm-action" style="display:none;" id="enable-confirm"> - <?php echo sprintf(__('Are you sure want to <b>enable</b> %s?'), + <?php echo sprintf(__('Are you sure you want to <b>enable</b> %s?'), _N('selected group', 'selected groups', 2));?> </p> <p class="confirm-action" style="display:none;" id="disable-confirm"> - <?php echo sprintf(__('Are you sure want to <b>disable</b> %s?'), + <?php echo sprintf(__('Are you sure you want to <b>disable</b> %s?'), _N('selected group', 'selected groups', 2));?> </p> <p class="confirm-action" style="display:none;" id="delete-confirm"> diff --git a/include/staff/helptopics.inc.php b/include/staff/helptopics.inc.php index 681efbbe0acbc34ba12ff908e862a8242b5a5032..1c91d64cbefa1f7abd2d8ad24fe05a3e7afec365 100644 --- a/include/staff/helptopics.inc.php +++ b/include/staff/helptopics.inc.php @@ -144,11 +144,11 @@ endif; <a class="close" href=""><i class="icon-remove-circle"></i></a> <hr/> <p class="confirm-action" style="display:none;" id="enable-confirm"> - <?php echo sprintf(__('Are you sure want to <b>enable</b> %s?'), + <?php echo sprintf(__('Are you sure you want to <b>enable</b> %s?'), _N('selected help topic', 'selected help topics', 2));?> </p> <p class="confirm-action" style="display:none;" id="disable-confirm"> - <?php echo sprintf(__('Are you sure want to <b>disable</b> %s?'), + <?php echo sprintf(__('Are you sure you want to <b>disable</b> %s?'), _N('selected help topic', 'selected help topics', 2));?> </p> <p class="confirm-action" style="display:none;" id="delete-confirm"> diff --git a/include/staff/pages.inc.php b/include/staff/pages.inc.php index 11a3f48b66146023b76b0d0c38176bf55b99a822..2398c5e573dbadf3228421a0705df5ddf1b0473e 100644 --- a/include/staff/pages.inc.php +++ b/include/staff/pages.inc.php @@ -122,11 +122,11 @@ endif; <a class="close" href=""><i class="icon-remove-circle"></i></a> <hr/> <p class="confirm-action" style="display:none;" id="enable-confirm"> - <?php echo sprintf(__('Are you sure want to <b>enable</b> %s?'), + <?php echo sprintf(__('Are you sure you want to <b>enable</b> %s?'), _N('selected site page', 'selected site pages', 2));?> </p> <p class="confirm-action" style="display:none;" id="disable-confirm"> - <?php echo sprintf(__('Are you sure want to <b>disable</b> %s?'), + <?php echo sprintf(__('Are you sure you want to <b>disable</b> %s?'), _N('selected site page', 'selected site pages', 2));?> </p> <p class="confirm-action" style="display:none;" id="delete-confirm"> diff --git a/include/staff/plugins.inc.php b/include/staff/plugins.inc.php index 9ec8ff7bd01c9185e8540ff6f48cd3a1e6c117fd..7e05195abc4765ddad3a9e3b14ad2fe4882b9590 100644 --- a/include/staff/plugins.inc.php +++ b/include/staff/plugins.inc.php @@ -83,12 +83,12 @@ if ($count) //Show options.. </p> <p class="confirm-action" style="display:none;" id="enable-confirm"> <font color="green"><?php echo sprintf( - __('Are you sure want to <b>enable</b> %s?'), + __('Are you sure you want to <b>enable</b> %s?'), _N('selected plugin', 'selected plugins', 2)); ?></font> </p> <p class="confirm-action" style="display:none;" id="disable-confirm"> <font color="red"><?php echo sprintf( - __('Are you sure want to <b>disable</b> %s?'), + __('Are you sure you want to <b>disable</b> %s?'), _N('selected plugin', 'selected plugins', 2)); ?></font> </p> <div><?php echo __('Please confirm to continue.'); ?></div> diff --git a/include/staff/profile.inc.php b/include/staff/profile.inc.php index 55e13a4752109ffff38952d04db3ddd10997fcf0..5f6288db738b1cd3cd11fcbf7a1e389e70b2bcd6 100644 --- a/include/staff/profile.inc.php +++ b/include/staff/profile.inc.php @@ -262,7 +262,7 @@ $info['id']=$staff->getId(); <td colspan=2> <textarea class="richtext no-bar" name="signature" cols="21" rows="5" style="width: 60%;"><?php echo $info['signature']; ?></textarea> - <br><em><?php __('Signature is made available as a choice, on ticket reply.');?></em> + <br><em><?php echo __('Signature is made available as a choice, on ticket reply.');?></em> </td> </tr> </tbody> diff --git a/include/staff/settings-system.inc.php b/include/staff/settings-system.inc.php index c1b573bd766c6f249a3efa949bd694b57440b913..9d6e325c41ff0f3e7a1725ecb749ef0191177448 100644 --- a/include/staff/settings-system.inc.php +++ b/include/staff/settings-system.inc.php @@ -98,7 +98,8 @@ $gmtime = Misc::gmtime(); for ($i = 1; $i <=12; $i++) { ?> <option <?php echo $config['log_graceperiod']==$i?'selected="selected"':''; ?> value="<?php echo $i; ?>"> - <?php echo __('After');?> <?php echo $i; ?> <?php echo ($i>1)?__('Months'):__('Month'); ?></option> + <?php echo sprintf(_N('After %d month', 'After %d months', $i), $i);?> + </option> <?php } ?> </select> diff --git a/include/staff/settings-tickets.inc.php b/include/staff/settings-tickets.inc.php index dc94ca4d272424e19562a40ecbb907ed19de11cc..8e1c23723176dc71d50d3bccfcb6f23262741b8a 100644 --- a/include/staff/settings-tickets.inc.php +++ b/include/staff/settings-tickets.inc.php @@ -224,7 +224,9 @@ if(!($maxfileuploads=ini_get('max_file_uploads'))) </td> </tr> <tr> - <td width="180"><?php echo __('Agent Maximum File Size');?>:</td> + <td width="180"><?php echo __( + // Maximum size for agent-uploaded files (via SCP) + 'Agent Maximum File Size');?>:</td> <td> <select name="max_file_size"> <option value="262144">— <?php echo __('Small'); ?> —</option> diff --git a/include/staff/slaplans.inc.php b/include/staff/slaplans.inc.php index dbe5cabb84478acb1d50b3b7601e69d3e25e6c67..2f1b3904efcfefb2773e447f0e28701a14de7c45 100644 --- a/include/staff/slaplans.inc.php +++ b/include/staff/slaplans.inc.php @@ -134,11 +134,11 @@ endif; <a class="close" href=""><i class="icon-remove-circle"></i></a> <hr/> <p class="confirm-action" style="display:none;" id="enable-confirm"> - <?php echo sprintf(__('Are you sure want to <b>enable</b> %s?'), + <?php echo sprintf(__('Are you sure you want to <b>enable</b> %s?'), _N('selected SLA plan', 'selected SLA plans', 2));?> </p> <p class="confirm-action" style="display:none;" id="disable-confirm"> - <?php echo sprintf(__('Are you sure want to <b>disable</b> %s?'), + <?php echo sprintf(__('Are you sure you want to <b>disable</b> %s?'), _N('selected SLA plan', 'selected SLA plans', 2));?> </p> <p class="confirm-action" style="display:none;" id="delete-confirm"> diff --git a/include/staff/staffmembers.inc.php b/include/staff/staffmembers.inc.php index dc40e747c26e06a8ef7b734bd29ec9561eb19f50..8bd1c395d694b3197dd723f979cedb5c60a6deec 100644 --- a/include/staff/staffmembers.inc.php +++ b/include/staff/staffmembers.inc.php @@ -5,7 +5,7 @@ if (!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin()) $qstr=''; $qs = array(); $sortOptions = array( - 'name' => 'lastname', + 'name' => array('firstname', 'lastname'), 'username' => 'username', 'status' => 'isactive', 'group' => 'group__name', @@ -21,7 +21,16 @@ if ($sort && $sortOptions[$sort]) { $order_column = $sortOptions[$sort]; } -$order_column = $order_column ? $order_column : 'lastname'; +$order_column = $order_column ? $order_column : array('firstname', 'lastname'); + +switch ($cfg->getDefaultNameFormat()) { +case 'last': +case 'lastfirst': +case 'legal': + $sortOptions['name'] = array('lastname', 'firstname'); + break; +// Otherwise leave unchanged +} if ($_REQUEST['order'] && isset($orderWays[strtoupper($_REQUEST['order'])])) { $order = $orderWays[strtoupper($_REQUEST['order'])]; @@ -57,10 +66,12 @@ $agents = Staff::objects() ->annotate(array( 'teams_count'=>SqlAggregate::COUNT('teams', true), )) - ->select_related('dept', 'group') - ->order_by(sprintf('%s%s', - strcasecmp($order, 'DESC') ? '' : '-', - $order_column)); + ->select_related('dept', 'group'); + +$order = strcasecmp($order, 'DESC') ? '' : '-'; +foreach ((array) $order_column as $C) { + $agents->order_by($order.$C); +} if ($filters) $agents->filter($filters); @@ -155,7 +166,7 @@ $agents->limit($pageNav->getLimit())->offset($pageNav->getStart()); <input type="checkbox" class="ckb" name="ids[]" value="<?php echo $id; ?>" <?php echo $sel ? 'checked="checked"' : ''; ?> > <td><a href="staff.php?id=<?php echo $id; ?>"><?php echo - Format::htmlchars($agent->getName()); ?></a> </td> + Format::htmlchars((string) $agent->getName()); ?></a> </td> <td><?php echo $agent->getUserName(); ?></td> <td><?php echo $agent->isActive() ? __('Active') :'<b>'.__('Locked').'</b>'; ?> <?php echo $agent->onvacation ? '<small>(<i>'.__('vacation').'</i>)</small>' : ''; ?></td> @@ -206,11 +217,11 @@ endif; <a class="close" href=""><i class="icon-remove-circle"></i></a> <hr/> <p class="confirm-action" style="display:none;" id="enable-confirm"> - <?php echo sprintf(__('Are you sure want to <b>enable</b> (unlock) %s?'), + <?php echo sprintf(__('Are you sure you want to <b>enable</b> (unlock) %s?'), _N('selected agent', 'selected agents', 2));?> </p> <p class="confirm-action" style="display:none;" id="disable-confirm"> - <?php echo sprintf(__('Are you sure want to <b>disable</b> (lock) %s?'), + <?php echo sprintf(__('Are you sure you want to <b>disable</b> (lock) %s?'), _N('selected agent', 'selected agents', 2));?> <br><br><?php echo __("Locked staff won't be able to login to Staff Control Panel.");?> </p> diff --git a/include/staff/teams.inc.php b/include/staff/teams.inc.php index 5982f7c208e1e05c020db9a97dd0409c43203293..b2c0947cd91b5a0ed2e73e429de295f970b937b4 100644 --- a/include/staff/teams.inc.php +++ b/include/staff/teams.inc.php @@ -145,11 +145,11 @@ endif; <a class="close" href=""><i class="icon-remove-circle"></i></a> <hr/> <p class="confirm-action" style="display:none;" id="enable-confirm"> - <?php echo sprintf(__('Are you sure want to <b>enable</b> %s?'), + <?php echo sprintf(__('Are you sure you want to <b>enable</b> %s?'), _N('selected team', 'selected teams', 2));?> </p> <p class="confirm-action" style="display:none;" id="disable-confirm"> - <?php echo sprintf(__('Are you sure want to <b>disable</b> %s?'), + <?php echo sprintf(__('Are you sure you want to <b>disable</b> %s?'), _N('selected team', 'selected teams', 2));?> </p> <p class="confirm-action" style="display:none;" id="delete-confirm"> diff --git a/include/staff/templates.inc.php b/include/staff/templates.inc.php index 2d7a972cb04176a1f538407fa93c946cfe787f62..e83f2a526374212a5dc9aeed12030c236cccbd5e 100644 --- a/include/staff/templates.inc.php +++ b/include/staff/templates.inc.php @@ -128,11 +128,11 @@ endif; <a class="close" href=""><i class="icon-remove-circle"></i></a> <hr/> <p class="confirm-action" style="display:none;" id="enable-confirm"> - <?php echo sprintf(__('Are you sure want to <b>enable</b> %s?'), + <?php echo sprintf(__('Are you sure you want to <b>enable</b> %s?'), _N('selected template set', 'selected template sets', 2));?> </p> <p class="confirm-action" style="display:none;" id="disable-confirm"> - <?php echo sprintf(__('Are you sure want to <b>disable</b> %s?'), + <?php echo sprintf(__('Are you sure you want to <b>disable</b> %s?'), _N('selected template set', 'selected template sets', 2));?> </p> <p class="confirm-action" style="display:none;" id="delete-confirm"> diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php index a054b841b6f71eced09e27e52eb00161ed6d71a0..e2cc27c5f3441b18d42b9c6b227b6ae0583158dc 100644 --- a/include/staff/ticket-view.inc.php +++ b/include/staff/ticket-view.inc.php @@ -1016,7 +1016,7 @@ $tcount = $ticket->getThreadEntries($types)->count(); <?php echo sprintf(Format::htmlchars(__('%s <%s> will longer have access to the ticket')), '<b>'.Format::htmlchars($ticket->getName()).'</b>', Format::htmlchars($ticket->getEmail())); ?> </span> - <?php echo sprintf(__('Are you sure want to <b>change</b> ticket owner to %s?'), + <?php echo sprintf(__('Are you sure you want to <b>change</b> ticket owner to %s?'), '<b><span id="newuser">this guy</span></b>'); ?> </p> <p class="confirm-action" style="display:none;" id="delete-confirm"> diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php index edb325b288a66eab97a80b29283d15ddff8fca98..bdab6cd1db6cdbe7d3d7aad223c878c87504b520 100644 --- a/include/staff/tickets.inc.php +++ b/include/staff/tickets.inc.php @@ -506,7 +506,7 @@ $_SESSION[':Q:tickets'] = $orig_tickets; <a class="close" href=""><i class="icon-remove-circle"></i></a> <hr/> <p class="confirm-action" style="display:none;" id="mark_overdue-confirm"> - <?php echo __('Are you sure want to flag the selected tickets as <font color="red"><b>overdue</b></font>?');?> + <?php echo __('Are you sure you want to flag the selected tickets as <font color="red"><b>overdue</b></font>?');?> </p> <div><?php echo __('Please confirm to continue.');?></div> <hr style="margin-top:1em"/> diff --git a/include/staff/user-view.inc.php b/include/staff/user-view.inc.php index 0497b74b8ce27d2795d700c235d4b4368c985b79..07085d4b9a44fe0440217bce5d68915d9579d792 100644 --- a/include/staff/user-view.inc.php +++ b/include/staff/user-view.inc.php @@ -169,18 +169,18 @@ if ($thisstaff->getRole()->hasPerm(User::PERM_EDIT)) { ?> <a class="close" href=""><i class="icon-remove-circle"></i></a> <hr/> <p class="confirm-action" style="display:none;" id="banemail-confirm"> - <?php echo sprintf(__('Are you sure want to <b>ban</b> %s?'), $user->getEmail()); ?> + <?php echo sprintf(__('Are you sure you want to <b>ban</b> %s?'), $user->getEmail()); ?> <br><br> <?php echo __('New tickets from the email address will be auto-rejected.'); ?> </p> <p class="confirm-action" style="display:none;" id="confirmlink-confirm"> <?php echo sprintf(__( - 'Are you sure want to send an <b>Account Activation Link</b> to <em> %s </em>?'), + 'Are you sure you want to send an <b>Account Activation Link</b> to <em> %s </em>?'), $user->getEmail()); ?> </p> <p class="confirm-action" style="display:none;" id="pwreset-confirm"> <?php echo sprintf(__( - 'Are you sure want to send a <b>Password Reset Link</b> to <em> %s </em>?'), + 'Are you sure you want to send a <b>Password Reset Link</b> to <em> %s </em>?'), $user->getEmail()); ?> </p> <div><?php echo __('Please confirm to continue.'); ?></div> diff --git a/include/tnef_decoder.php b/include/tnef_decoder.php index 0459998d48109087da43cdb135e2260ba38ae75a..08bf3755c906e20fd44233a864881634fe510f1f 100644 --- a/include/tnef_decoder.php +++ b/include/tnef_decoder.php @@ -63,8 +63,8 @@ class TnefStreamReader implements Iterator { $this->push($stream); // Read header - if (self::SIGNATURE != $this->_geti(32)) - throw new TnefException("Invalid signature"); + if (self::SIGNATURE != ($S = $this->_geti(32))) + throw new TnefException(sprintf("%08x: Invalid signature magic", $S)); $this->_geti(16); // Attach key @@ -109,6 +109,10 @@ class TnefStreamReader implements Iterator { return $value; } + protected function skip($bytes) { + $this->pos += $bytes; + } + function check($block) { $sum = 0; $bytes = strlen($block['data']); $bs = 1024; for ($i=0; $i < $bytes; $i+=$bs) { @@ -117,7 +121,8 @@ class TnefStreamReader implements Iterator { $sum = $sum % 65536; } if ($block['checksum'] != $sum) - throw new TnefException('Corrupted block. Invalid checksum'); + throw new TnefException(sprintf('Corrupted block. %04x: Invalid checksum, expected %04x', + $sum, $block['checksum'])); } function next() { @@ -288,6 +293,16 @@ class TnefAttributeStreamReader extends TnefStreamReader { $this->pos = 4; } + /** + * Read a single typed value from the current input stream. The type is + * a 16-bit number receved as an argument which should be one of the + * Type* constants defined in this class. + * + * According to the TNEF spec, all types regardless of their actual + * size, must be rounded up in size to the next multiple of four (4) + * bytes. Therefore 16-bit values and a strings will need to have extra + * padding consumed to keep the stream on track. + */ protected function readPhpValue($type) { switch ($type) { case self::TypeUnspecified: @@ -296,27 +311,53 @@ class TnefAttributeStreamReader extends TnefStreamReader { return null; case self::TypeInt16: - return $this->_geti(16); + // Signed 16-bit value = INT16. + $int16 = unpack('v', $this->_getx(4)); + $sign = $int16 & 0x8000; + if ($sign) + // Use two's compliment + $int16 = - ((~$int16 & 0xFFFF) + 1); + return $int16; case self::TypeInt32: + // Signed 32-bit value = INT32. + // FIXME: Convert to signed value return $this->_geti(32); case self::TypeBoolean: - return (bool) $this->_geti(32); + // 16-bit Boolean (non-zero = TRUE) + list($bool) = unpack('v', $this->_getx(4)); + return 0 != $bool; case self::TypeFlt32: - list($f) = unpack('f', $this->_getx(8)); + // Signed 32-bit floating point= FLOAT. + list($f) = unpack('f', $this->_getx(4)); return $f; case self::TypeFlt64: + // 64-bit floating point= DOUBLE. list($d) = unpack('d', $this->_getx(8)); return $d; - case self::TypeAppTime: case self::TypeCurency: - case self::TypeInt64: + // Signed 64-bit int = OLE CURRENCY type. + // FIXME: Convert to PHP double return $this->_getx(8); + case self::TypeInt64: + // 8-byte signed integer= INT64. + $x = $this->_getx(8); + if (phpversion() >= '5.6.3') + list($x) = unpack('P', $x); + return $x; + + case self::TypeAppTime: + list($d) = unpack('d', $this->_getx(8)); + // Application time= OLE DATE type. + // Thanks, http://stackoverflow.com/a/10443946/1025836 + // Convert to UNIX timestamp, UTC timezone is assumed + return ($d - 25569) * 86400; + case self::TypeSystime: $a = unpack('Vl/Vh', $this->_getx(8)); // return FileTimeToU64(f) / 10000000 - 11644473600 @@ -326,7 +367,6 @@ class TnefAttributeStreamReader extends TnefStreamReader { case self::TypeString8: case self::TypeUnicode: case self::TypeBinary: - case self::TypeObject: $length = $this->_geti(32); /* Pad to next 4 byte boundary. */ @@ -341,18 +381,28 @@ class TnefAttributeStreamReader extends TnefStreamReader { /* Read and truncate to length. */ $text = substr($this->_getx($datalen), 0, $length); if ($type == self::TypeUnicode) { - $text = Charset::utf8($text, 'ucs2'); + // TNEF spec says encoding is UTF-16LE + $text = Charset::utf8($text, 'UTF-16LE'); } return $text; + case self::TypeObject: + $length = $this->_geti(32); + $oid = $this->_getx(16); + if (bin2hex($text) == "0703020000000000c000000000000046") { + // TODO: Create stream parser for embedded TNEF stream + } + $this->skip($length - 16); + $this->skip((4 - ($length % 4)) % 4); + return null; + case self::TypeCLSID: return $this->_getx(16); default: throw new TnefException(sprintf('0x%04x: Bad data type', $type)); } - } function next() { @@ -476,6 +526,9 @@ class TnefStreamParser { case self::idMessageID: $msg->_set('MessageId', $info['data']); break; + case self::idSubject: + $msg->_set('Subject', $info['data']); + break; case self::attMsgProps: // Message properties (includig body) diff --git a/scp/css/scp.css b/scp/css/scp.css index 744fbf9d1e62d4a0041478a312a8735d9dc06061..64735d3de27b7959e24fe40d1179aa2aff0764e1 100644 --- a/scp/css/scp.css +++ b/scp/css/scp.css @@ -161,6 +161,7 @@ div#header a { line-height:26px; border-left:1px solid #aaa; border-right:1px solid #aaa; + white-space: nowrap; } #nav .active, #sub_nav li { diff --git a/setup/test/tests/stubs.php b/setup/test/tests/stubs.php index 392873e1957f9b29bb5daa9d2db2f7070fba3050..83a6cdd297a4688f96528aeee46a9360fa0ae9f1 100644 --- a/setup/test/tests/stubs.php +++ b/setup/test/tests/stubs.php @@ -156,6 +156,10 @@ class ResourceBundle { function getLocales() {} } +class NumberFormatter { + function getSymbol() {} +} + class Aws_Route53_Client { function changeResourceRecordSets() {} }