diff --git a/bootstrap.php b/bootstrap.php index 4e2259cc3c86660bca8b49c888af6f73c6264a01..b8971116fecb896cebe915240ce4c047fd5e983e 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -208,6 +208,9 @@ class Bootstrap { } function i18n_prep() { + ini_set('default_charset', 'utf-8'); + ini_set('output_encoding', 'utf-8'); + // MPDF requires mbstring functions if (!extension_loaded('mbstring')) { if (function_exists('iconv')) { diff --git a/include/ajax.orgs.php b/include/ajax.orgs.php index c582b578060b8ba09151db6739cc49c6431809ad..b6a4002feac751e5a2bbf1a3a6ac34f38ee2db45 100644 --- a/include/ajax.orgs.php +++ b/include/ajax.orgs.php @@ -227,11 +227,11 @@ class OrgsAjaxAPI extends AjaxController { $info['title'] = __('Add New Organization'); $info['search'] = false; - return self::_lookupform($form, $info); + return $this->_lookupform($form, $info); } function lookup() { - return self::_lookupform(); + return $this->_lookupform(); } function selectOrg($id) { @@ -257,11 +257,15 @@ class OrgsAjaxAPI extends AjaxController { return $ajax->createNote('O'.$id); } - static function _lookupform($form=null, $info=array()) { + function _lookupform($form=null, $info=array()) { if (!$info or !$info['title']) $info += array('title' => __('Organization Lookup')); + if ($_POST && ($org = Organization::lookup($_POST['orgid']))) { + Http::response(201, $org->to_json()); + } + ob_start(); include(STAFFINC_DIR . 'templates/org-lookup.tmpl.php'); $resp = ob_get_contents(); diff --git a/include/class.api.php b/include/class.api.php index c998cf244a6ddc314dfaa983a99cc03cfa7d81ce..ce8b28ad04501ade75b4096703e39d22401ace1e 100644 --- a/include/class.api.php +++ b/include/class.api.php @@ -297,7 +297,12 @@ class ApiController { $msg.="\n*[".$_SERVER['HTTP_X_API_KEY']."]*\n"; $ost->logWarning(__('API Error')." ($code)", $msg, false); - $this->response($code, $error); //Responder should exit... + if (PHP_SAPI == 'cli') { + fwrite(STDERR, "({$code}) $error\n"); + } + else { + $this->response($code, $error); //Responder should exit... + } return false; } diff --git a/include/class.config.php b/include/class.config.php index b7eae5da0e851d57f8766f1dc3ceaf44451b68a2..a2cad713eb487ab4542f4186fb19cc4c5f308fe1 100644 --- a/include/class.config.php +++ b/include/class.config.php @@ -741,19 +741,20 @@ class OsticketConfig extends Config { return ($this->get('message_alert_acct_manager')); } - function alertONNewNote() { + //TODO: change note_alert to activity_alert + function alertONNewActivity() { return ($this->get('note_alert_active')); } - function alertLastRespondentONNewNote() { + function alertLastRespondentONNewActivity() { return ($this->get('note_alert_laststaff')); } - function alertAssignedONNewNote() { + function alertAssignedONNewActivity() { return ($this->get('note_alert_assigned')); } - function alertDeptManagerONNewNote() { + function alertDeptManagerONNewActivity() { return ($this->get('note_alert_dept_manager')); } diff --git a/include/class.dept.php b/include/class.dept.php index e038c09f58da44bf682c699ff66474eff11d6fbc..588a71c127f553c10010a3f6dbf9eda3d506163c 100644 --- a/include/class.dept.php +++ b/include/class.dept.php @@ -239,6 +239,16 @@ class Dept extends VerySimpleModel { return ($this->getManagerId() && $this->getManagerId()==$staff); } + function isMember($staff) { + + if (is_object($staff)) + $staff = $staff->getId(); + + // Members are indexed by ID + $members = $this->getMembers(); + + return ($members && isset($members[$staff])); + } function isPublic() { return $this->ispublic; diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php index 2efcbec3c2fca60c77f19d49c60a30c7557ee415..23829e8a471b3b63b72cb9c071ed763888e7c187 100644 --- a/include/class.dynamic_forms.php +++ b/include/class.dynamic_forms.php @@ -838,6 +838,9 @@ class DynamicFormEntry extends VerySimpleModel { 'null' => true, 'constraint' => array('form_id' => 'DynamicForm.id'), ), + 'answers' => array( + 'reverse' => 'DynamicFormEntryAnswer.entry' + ), ), ); diff --git a/include/class.email.php b/include/class.email.php index 2943192ef5c6734698829e874275c7574e590ed7..2d05f47800a1462b83dd111a9cb6cc4794bc9b79 100644 --- a/include/class.email.php +++ b/include/class.email.php @@ -358,8 +358,12 @@ class Email { if(!isset($vars['postfetch'])) $errors['postfetch']=__('Indicate what to do with fetched emails'); - elseif(!strcasecmp($vars['postfetch'],'archive') && !$vars['mail_archivefolder'] ) - $errors['postfetch']=__('Valid folder required'); + elseif(!strcasecmp($vars['postfetch'],'archive')) { + if ($vars['mail_protocol'] == 'POP') + $errors['postfetch'] = __('POP mail servers do not support folders'); + elseif (!$vars['mail_archivefolder']) + $errors['postfetch'] = __('Valid folder required'); + } } if($vars['smtp_active']) { diff --git a/include/class.mailer.php b/include/class.mailer.php index cc7341b7747c2c76704bbd5b983b795a035b30e5..ff1a463c3cfc9a444fdcb75020efbe4e2234b9ca 100644 --- a/include/class.mailer.php +++ b/include/class.mailer.php @@ -376,25 +376,13 @@ class Mailer { $reply_tag = $cfg->getReplySeparator() . '<br/><br/>'; } - // Use Mail_mime default initially - $eol = null; + // Use general failsafe default initially + $eol = "\n"; // MAIL_EOL setting can be defined in `ost-config.php` if (defined('MAIL_EOL') && is_string(MAIL_EOL)) { $eol = MAIL_EOL; } - // The Suhosin patch will muck up the line endings in some - // cases - // - // References: - // https://github.com/osTicket/osTicket-1.8/issues/202 - // http://pear.php.net/bugs/bug.php?id=12032 - // http://us2.php.net/manual/en/function.mail.php#97680 - elseif ((extension_loaded('suhosin') || defined("SUHOSIN_PATCH")) - && !$this->getSMTPInfo() - ) { - $eol = "\n"; - } $mime = new Mail_mime($eol); // If the message is not explicitly declared to be a text message, diff --git a/include/class.mailfetch.php b/include/class.mailfetch.php index db9026228caa6f024c57c43f906fce5cae683eab..3b0c7cecddcaa349ddd589415df13c50aa8908b0 100644 --- a/include/class.mailfetch.php +++ b/include/class.mailfetch.php @@ -347,10 +347,15 @@ class MailFetcher { // Ensure we have a message-id. If unable to read it out of the // email, use the hash of the entire email headers - if (!$header['mid'] && $header['header']) - if (!($header['mid'] = Mail_Parse::findHeaderEntry($header['header'], - 'message-id'))) + if (!$header['mid'] && $header['header']) { + $header['mid'] = Mail_Parse::findHeaderEntry($header['header'], + 'message-id'); + + if (is_array($header['mid'])) + $header['mid'] = array_pop(array_filter($header['mid'])); + if (!$header['mid']) $header['mid'] = '<' . md5($header['header']) . '@local>'; + } return $header; } @@ -544,11 +549,21 @@ class MailFetcher { } function getPriority($mid) { - if ($this->tnef && isset($this->tnef->Importance)) - // PidTagImportance is 0, 1, or 2 + if ($this->tnef && isset($this->tnef->Importance)) { + // PidTagImportance is 0, 1, or 2, 2 is high // http://msdn.microsoft.com/en-us/library/ee237166(v=exchg.80).aspx - return $this->tnef->Importance + 1; - return Mail_Parse::parsePriority($this->getHeader($mid)); + $urgency = 4 - $this->tnef->Importance; + } + elseif ($priority = Mail_Parse::parsePriority($this->getHeader($mid))) { + $urgency = $priority + 1; + } + if ($urgency) { + $sql = 'SELECT `priority_id` FROM '.PRIORITY_TABLE + .' WHERE `priority_urgency`='.db_input($urgency) + .' LIMIT 1'; + $id = db_result(db_query($sql)); + return $id; + } } function getBody($mid) { @@ -643,7 +658,7 @@ class MailFetcher { $vars['in-reply-to'] = @$headers['in-reply-to'] ?: null; } // Fetch deliver status report - $vars['message'] = $this->getDeliveryStatusMessage($mid); + $data['message'] = $this->getDeliveryStatusMessage($mid) ?: $this->getBody($mid); $vars['thread-type'] = 'N'; $vars['flags']['bounce'] = true; } diff --git a/include/class.mailparse.php b/include/class.mailparse.php index 5c9cdf4d636fd04b4cdb4d894ca06b036eae8b37..855afe9a0ae16bad3c94b03c7c3b017097a1850b 100644 --- a/include/class.mailparse.php +++ b/include/class.mailparse.php @@ -242,8 +242,11 @@ class Mail_Parse { } function getMessageId(){ - if (!($mid = $this->struct->headers['message-id'])) + if (($mid = $this->struct->headers['message-id']) && is_array($mid)) + $mid = array_pop(array_filter($mid)); + if (!$mid) $mid = sprintf('<%s@local>', md5($this->getHeader())); + return $mid; } @@ -275,10 +278,9 @@ class Mail_Parse { && isset($this->struct->ctype_parameters['report-type']) && $this->struct->ctype_parameters['report-type'] == 'delivery-status' ) { - return sprintf('<pre>%s</pre>', - Format::htmlchars( - $this->getPart($this->struct, 'text/plain', 1) - )); + return new TextThreadBody( + $this->getPart($this->struct, 'text/plain', 1) + ); } return false; } @@ -462,43 +464,59 @@ class Mail_Parse { return $files; } - function getPriority(){ - if ($this->tnef && isset($this->tnef->Importance)) - // PidTagImportance is 0, 1, or 2 + function getPriority() { + if ($this->tnef && isset($this->tnef->Importance)) { + // PidTagImportance is 0, 1, or 2, 2 is high // http://msdn.microsoft.com/en-us/library/ee237166(v=exchg.80).aspx - return $this->tnef->Importance + 1; - - return Mail_Parse::parsePriority($this->getHeader()); + $urgency = 4 - $this->tnef->Importance; + } + elseif ($priority = Mail_Parse::parsePriority($this->getHeader())) { + $urgency = $priority + 1; + } + if ($urgency) { + $sql = 'SELECT `priority_id` FROM '.PRIORITY_TABLE + .' WHERE `priority_urgency`='.db_input($urgency) + .' LIMIT 1'; + $id = db_result(db_query($sql)); + return $id; + } } + /** + * Return a normalized priority urgency from the email headers received. + * + * Returns: + * (int) priority urgency, {1,2,3}, where 1 is high. Returns 0 if no + * priority could be inferred from the headers. + * + * References: + * https://github.com/osTicket/osTicket-1.8/issues/1674 + * http://stackoverflow.com/questions/15568583/php-mail-priority-types + */ static function parsePriority($header=null){ - if (! $header) - return 0; - // Test for normal "X-Priority: INT" style header & stringy version. - // Allows for Importance possibility. - $matching_char = ''; - if (preg_match ( '/priority: (\d|\w)/i', $header, $matching_char ) - || preg_match ( '/importance: (\d|\w)/i', $header, $matching_char )) { - switch ($matching_char[1]) { - case 'h' : - case 'H' :// high - case 'u': - case 'U': //Urgent - case 6 : - case 5 : - return 1; - case 'n' : // normal - case 'N' : - case 4 : - case 3 : - return 2; - case 'l' : // low - case 'L' : - case 2 : - case 1 : - return 3; - } + if (!$header) + return 0; + + // Test for normal "X-Priority: INT" style header & stringy version. + // Allows for Importance possibility. + $matching_char = ''; + if (preg_match('/(?:priority|importance): (\w)/i', $header, $matching_char)) { + switch (strtoupper($matching_char[1])) { + case 'H' :// high + case 'U': //Urgent + case '2' : + case '1' : + return 1; + case 'N' : + case '4' : + case '3' : + return 2; + case 'L' : + case '6' : + case '5' : + return 3; + } } return 0; } @@ -657,7 +675,7 @@ class EmailDataParser { $data['in-reply-to'] = @$headers['in-reply-to'] ?: null; } // Fetch deliver status report - $data['message'] = $parser->getDeliveryStatusMessage(); + $data['message'] = $parser->getDeliveryStatusMessage() ?: $parser->getBody(); $data['thread-type'] = 'N'; $data['flags']['bounce'] = true; } diff --git a/include/class.thread.php b/include/class.thread.php index b537436d58a2c6bc6fc727f7321f1996e3cd5861..03fe49fb523b9ddb5405f07dd4dbd0db3bde3c74 100644 --- a/include/class.thread.php +++ b/include/class.thread.php @@ -380,7 +380,7 @@ class ThreadEntry extends VerySimpleModel { static $meta = array( 'table' => THREAD_ENTRY_TABLE, 'pk' => array('id'), - 'select_related' => array('staff', 'user', 'email_info'), + 'select_related' => array('staff', 'user'), 'joins' => array( 'thread' => array( 'constraint' => array('thread_id' => 'Thread.id'), @@ -478,6 +478,10 @@ class ThreadEntry extends VerySimpleModel { return $this->save(); } + function getMessage() { + return $this->getBody(); + } + function getCreateDate() { return $this->created; } @@ -787,7 +791,8 @@ class ThreadEntry extends VerySimpleModel { function saveEmailInfo($vars) { - if(!$vars || !$vars['mid']) + // Don't save empty message ID + if (!$vars || !$vars['mid']) return 0; $this->ht['email_mid'] = $vars['mid']; @@ -1019,7 +1024,7 @@ class ThreadEntry extends VerySimpleModel { return false; $check = sprintf('%s@%s', - substr(md5($to . $ticket->getNumber() . $ticket->getId()), -10), + substr(md5($from . $ticket->getNumber() . $ticket->getId()), -10), substr($domain, -10) ); diff --git a/include/class.ticket.php b/include/class.ticket.php index 12327054ca0f8c442c19a09baeb27c50a213f934..30b7ed09bc8eac8c1613fc42e230eb34c631aace 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -1454,9 +1454,15 @@ implements RestrictedAccess, Threadable { return true; } - function onResponse() { + function onResponse($response, $options=array()) { db_query('UPDATE '.TICKET_TABLE.' SET isanswered=1, lastresponse=NOW(), updated=NOW() WHERE ticket_id='.db_input($this->getId())); $this->reload(); + $vars = array_merge($options, + array( + 'activity' => _S('New Response'), + 'threadentry' => $response)); + + $this->onActivity($vars); } /* @@ -1594,6 +1600,86 @@ implements RestrictedAccess, Threadable { } } + function onActivity($vars, $alert=true) { + global $cfg, $thisstaff; + + //TODO: do some shit + + if (!$alert // Check if alert is enabled + || !$cfg->alertONNewActivity() + || !($dept=$this->getDept()) + || !($email=$cfg->getAlertEmail()) + || !($tpl = $dept->getTemplate()) + || !($msg=$tpl->getNoteAlertMsgTemplate())) + return; + + // Alert recipients + $recipients=array(); + + //Last respondent. + if ($cfg->alertLastRespondentONNewActivity()) + $recipients[] = $this->getLastRespondent(); + + // Assigned staff / team + if ($cfg->alertAssignedONNewActivity()) { + + if (isset($vars['assignee']) + && $vars['assignee'] instanceof Staff) + $recipients[] = $vars['assignee']; + elseif ($this->isOpen() && ($assignee = $this->getStaff())) + $recipients[] = $assignee; + + if ($team = $this->getTeam()) + $recipients = array_merge($recipients, $team->getMembers()); + } + + // Dept manager + if ($cfg->alertDeptManagerONNewActivity() && $dept && $dept->getManagerId()) + $recipients[] = $dept->getManager(); + + $options = array(); + $staffId = $thisstaff ? $thisstaff->getId() : 0; + if ($vars['threadentry'] && $vars['threadentry'] instanceof ThreadEntry) { + $options = array( + 'inreplyto' => $vars['threadentry']->getEmailMessageId(), + 'references' => $vars['threadentry']->getEmailReferences(), + 'thread' => $vars['threadentry']); + + // Activity details + if (!$vars['comments']) + $vars['comments'] = $vars['threadentry']; + + // Staff doing the activity + $staffId = $vars['threadentry']->getStaffId() ?: $staffId; + } + + $msg = $this->replaceVars($msg->asArray(), + array( + 'note' => $vars['threadentry'], // For compatibility + 'activity' => $vars['activity'], + 'comments' => $vars['comments'])); + + $isClosed = $this->isClosed(); + $sentlist=array(); + foreach ($recipients as $k=>$staff) { + if (!is_object($staff) + // Don't bother vacationing staff. + || !$staff->isAvailable() + // No need to alert the poster! + || $staffId == $staff->getId() + // No duplicates. + || isset($sentlist[$staff->getEmail()]) + // Make sure staff has access to ticket + || ($isClosed && !$this->checkStaffPerm($staff)) + ) + continue; + $alert = $this->replaceVars($msg, array('recipient' => $staff)); + $email->sendAlert($staff, $alert['subj'], $alert['body'], null, $options); + $sentlist[$staff->getEmail()] = 1; + } + + } + function onAssign($assignee, $comments, $alert=true) { global $cfg, $thisstaff; @@ -1843,11 +1929,21 @@ implements RestrictedAccess, Threadable { if($this->isClosed()) $this->reopen(); $this->reload(); + $dept = $this->getDept(); // Set SLA of the new department if(!$this->getSLAId() || $this->getSLA()->isTransient()) $this->selectSLAId(); + // Make sure the new department allows assignment to the + // currently assigned agent (if any) + if ($this->isAssigned() + && ($staff=$this->getStaff()) + && $dept->assignMembersOnly() + && !$dept->isMember($staff)) { + $this->setStaffId(0); + } + /*** log the transfer comments as internal note - with alerts disabled - ***/ $title=sprintf(_S('Ticket transferred from %1$s to %2$s'), $currentDept, $this->getDeptName()); @@ -1857,7 +1953,7 @@ implements RestrictedAccess, Threadable { $this->logEvent('transferred'); //Send out alerts if enabled AND requested - if(!$alert || !$cfg->alertONTransfer() || !($dept=$this->getDept())) + if (!$alert || !$cfg->alertONTransfer()) return true; //no alerts!! if (($email = $dept->getAlertEmail()) @@ -1900,6 +1996,21 @@ implements RestrictedAccess, Threadable { return true; } + function claim() { + global $thisstaff; + + if (!$thisstaff || !$this->isOpen() || $this->isAssigned()) + return false; + + $dept = $this->getDept(); + if ($dept->assignMembersOnly() && !$dept->isMember($thisstaff)) + return false; + + $comments = sprintf(_S('Ticket claimed by %s'), $thisstaff->getName()); + + return $this->assignToStaff($thisstaff->getId(), $comments, false); + } + function assignToStaff($staff, $note, $alert=true) { if(!is_object($staff) && !($staff=Staff::lookup($staff))) @@ -2214,20 +2325,23 @@ implements RestrictedAccess, Threadable { if(!($response = $this->getThread()->addResponse($vars, $errors))) return null; + $assignee = $this->getStaff(); // Set status - if checked. if ($vars['reply_status_id'] && $vars['reply_status_id'] != $this->getStatusId()) $this->setStatus($vars['reply_status_id']); + // Claim on response bypasses the department assignment restrictions if($thisstaff && $this->isOpen() && !$this->getStaffId() && $cfg->autoClaimTickets()) $this->setStaffId($thisstaff->getId()); //direct assignment; $this->lastrespondent = null; - $this->onResponse(); //do house cleaning.. + + $this->onResponse($response, array('assignee' => $assignee)); //do house cleaning.. /* email the user?? - if disabled - then bail out */ - if(!$alert) return $response; + if (!$alert) return $response; $dept = $this->getDept(); @@ -2356,62 +2470,12 @@ implements RestrictedAccess, Threadable { $this->reload(); } - // If alerts are not enabled then return a success. - if(!$alert || !$cfg->alertONNewNote() || !($dept=$this->getDept())) - return $note; - - if (($email = $dept->getAlertEmail()) - && ($tpl = $dept->getTemplate()) - && ($msg=$tpl->getNoteAlertMsgTemplate())) { - - $msg = $this->replaceVars($msg->asArray(), - array('note' => $note)); - - // Alert recipients - $recipients=array(); - - //Last respondent. - if ($cfg->alertLastRespondentONNewNote()) - $recipients[] = $this->getLastRespondent(); - - // Assigned staff / team - if ($cfg->alertAssignedONNewNote()) { - - if ($assignee && $assignee instanceof Staff) - $recipients[] = $assignee; - - if ($team = $this->getTeam()) - $recipients = array_merge($recipients, $team->getMembers()); - } - - // Dept manager - if ($cfg->alertDeptManagerONNewNote() && $dept && $dept->getManagerId()) - $recipients[] = $dept->getManager(); - - $options = array( - 'inreplyto'=>$note->getEmailMessageId(), - 'references'=>$note->getEmailReferences(), - 'thread'=>$note); - - $isClosed = $this->isClosed(); - $sentlist=array(); - foreach( $recipients as $k=>$staff) { - if(!is_object($staff) - // Don't bother vacationing staff. - || !$staff->isAvailable() - // No duplicates. - || isset($sentlist[$staff->getEmail()]) - // No need to alert the poster! - || $note->getStaffId() == $staff->getId() - // Make sure staff has access to ticket - || ($isClosed && !$this->checkStaffPerm($staff)) - ) - continue; - $alert = $this->replaceVars($msg, array('recipient' => $staff)); - $email->sendAlert($staff, $alert['subj'], $alert['body'], null, $options); - $sentlist[$staff->getEmail()] = 1; - } - } + $activity = $vars['activity'] ?: _S('New Internal Note'); + $this->onActivity(array( + 'activity' => $activity, + 'threadentry' => $note, + 'assignee' => $assignee + ), $alert); return $note; } diff --git a/include/class.user.php b/include/class.user.php index 194b659a34777039b1484928c745f415310ce1bb..08e2ce3b4503a80f657d66ecaac54ae5b7d14e53 100644 --- a/include/class.user.php +++ b/include/class.user.php @@ -64,6 +64,13 @@ class UserModel extends VerySimpleModel { 'constraint' => array('id' => 'UserCdata.user_id'), 'null' => true, ), + 'cdata_entry' => array( + 'constraint' => array( + 'id' => 'DynamicFormEntry.object_id', + "'U'" => 'DynamicFormEntry.object_type', + ), + 'null' => true, + ), ) ); @@ -884,7 +891,12 @@ class UserAccountModel extends VerySimpleModel { function lock() { $this->setStatus(UserAccountStatus::LOCKED); - $this->save(); + return $this->save(); + } + + function unlock() { + $this->clearStatus(UserAccountStatus::LOCKED); + return $this->save(); } function isLocked() { diff --git a/include/client/header.inc.php b/include/client/header.inc.php index 34cd76f4056355f180c5af257cf9192f56cc7009..19a00128011f9bac330a4bd05fe48f5b718063f7 100644 --- a/include/client/header.inc.php +++ b/include/client/header.inc.php @@ -5,7 +5,7 @@ $signin_url = ROOT_PATH . "login.php" . ($thisclient ? "?e=".urlencode($thisclient->getEmail()) : ""); $signout_url = ROOT_PATH . "logout.php?auth=".$ost->getLinkToken(); -header("Content-Type: text/html; charset=UTF-8\r\n"); +header("Content-Type: text/html; charset=UTF-8"); ?> <!DOCTYPE html> <html <?php diff --git a/include/i18n/en_US/help/tips/settings.alerts.yaml b/include/i18n/en_US/help/tips/settings.alerts.yaml index 323e1bba55882b32dc31c9cdcc725b2e6aa55052..21951c9e9595c1f911f4c95b50602ea03595ace6 100644 --- a/include/i18n/en_US/help/tips/settings.alerts.yaml +++ b/include/i18n/en_US/help/tips/settings.alerts.yaml @@ -42,10 +42,10 @@ message_alert: href: /scp/templates.php?default_for=message.alert internal_note_alert: - title: New Internal Note Alert + title: New Internal Activity Alert content: > - Alert sent out to Agents when a new <span - class="doc-desc-title">Internal Note</span> is appended to a ticket. + Alert sent out to Agents when internal activity such as an internal + note or an agent reply is appended to a ticket. links: - title: Default Ticket Activity Template href: /scp/templates.php?default_for=note.alert diff --git a/include/i18n/en_US/help/tips/staff.department.yaml b/include/i18n/en_US/help/tips/staff.department.yaml index ec16a46a1f13e2ad790f2ca9216fe447b5654ad8..0c6f555c240fb2b4609e8f325c2bf8059c0624fc 100644 --- a/include/i18n/en_US/help/tips/staff.department.yaml +++ b/include/i18n/en_US/help/tips/staff.department.yaml @@ -73,9 +73,9 @@ sandboxing: title: Ticket Assignment Restrictions content: > Enable this to restrict ticket assignement to only include members - of this Department. Department access can be extended to - Groups, if <span class="doc-desc-title">Group Membership</span> is - also enabled. + of this Department. Department membership can be extended to Groups, + if <span class="doc-desc-title">Alerts & Notices + Recipients</span> includes groups members. auto_response_settings: title: Autoresponder Settings diff --git a/include/i18n/en_US/templates/email/note.alert.yaml b/include/i18n/en_US/templates/email/note.alert.yaml index a704830d6bb3300607be5b3c7f0c6a55741b4955..5a1bcd91e3d69d21fe5b61ba4e1ab7cc9d2405e1 100644 --- a/include/i18n/en_US/templates/email/note.alert.yaml +++ b/include/i18n/en_US/templates/email/note.alert.yaml @@ -6,14 +6,14 @@ # --- notes: | - Sent to staff members when a new internal note is appended to a ticket. - Internal notes can only be added by staff members. + Alert sent out to Agents when internal activity such as an internal + note or an agent reply is appended to a ticket. subject: | - New Internal Note Alert + New Internal Activity Alert body: | <h3><strong>Hi %{recipient.name},</strong></h3> - An internal note has been appended to ticket <a + An agent has logged activity on ticket <a href="%{ticket.staff_link}">#%{ticket.number}</a> <br> <br> diff --git a/include/ost-sampleconfig.php b/include/ost-sampleconfig.php index 3fc0e71780edaca45f56fdefb698a59e5a5b2b1a..a4624d1e706ce75f36c2e23a6ac1c0ec3b916eb3 100644 --- a/include/ost-sampleconfig.php +++ b/include/ost-sampleconfig.php @@ -71,7 +71,7 @@ define('TABLE_PREFIX','%CONFIG-PREFIX'); # # Mail Options # --------------------------------------------------- -# Option: MAIL_EOL (default: \r\n) +# Option: MAIL_EOL (default: \n) # # Some mail setups do not handle emails with \r\n (CRLF) line endings for # headers and base64 and quoted-response encoded bodies. This is an error @@ -88,7 +88,7 @@ define('TABLE_PREFIX','%CONFIG-PREFIX'); # https://github.com/osTicket/osTicket-1.8/issues/759 # https://github.com/osTicket/osTicket-1.8/issues/1217 -# define(MAIL_EOL, "\n"); +# define(MAIL_EOL, "\r\n"); # # HTTP Server Options diff --git a/include/staff/email.inc.php b/include/staff/email.inc.php index 27e7dc449d0318da13a714748d5ced468cd4e737..8958d43a0fcb022d51445ebe93d2d4a665638a9d 100644 --- a/include/staff/email.inc.php +++ b/include/staff/email.inc.php @@ -217,7 +217,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <td> <span> <select name="mail_proto"> - <option value=''>— <?php __('Select protocol'); ?> —</option> + <option value=''>— <?php echo __('Select protocol'); ?> —</option> <?php foreach (MailFetcher::getSupportedProtos() as $proto=>$desc) { ?> <option value="<?php echo $proto; ?>" <?php diff --git a/include/staff/header.inc.php b/include/staff/header.inc.php index dd9945a1eaf27fe2d48024df9ea5c722b2c83f29..4027d7b61d62366d32797ff4a8143ec3d16c221b 100644 --- a/include/staff/header.inc.php +++ b/include/staff/header.inc.php @@ -1,4 +1,6 @@ -<?php if (!isset($_SERVER['HTTP_X_PJAX'])) { ?> +<?php +header("Content-Type: text/html; charset=UTF-8"); +if (!isset($_SERVER['HTTP_X_PJAX'])) { ?> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html <?php if (($lang = Internationalization::getCurrentLanguage()) diff --git a/include/staff/orgs.inc.php b/include/staff/orgs.inc.php index 4973b0b6252933fc87a8106b3ad3f0fcbe79f6bf..fff47ba84ba16bb6f2d5cf6dbc35407b6c08f942 100644 --- a/include/staff/orgs.inc.php +++ b/include/staff/orgs.inc.php @@ -72,7 +72,7 @@ $qhash = md5($query); $_SESSION['orgs_qs_'.$qhash] = $query; ?> <h2><?php echo __('Organizations'); ?></h2> -<div class="pull-left" style="width:700px;"> +<div class="pull-left"> <form action="orgs.php" method="get"> <?php csrf_token(); ?> <input type="hidden" name="a" value="search"> @@ -86,11 +86,31 @@ $_SESSION['orgs_qs_'.$qhash] = $query; </table> </form> </div> + +<div class="pull-right"> <?php if ($thisstaff->getRole()->hasPerm(Organization::PERM_CREATE)) { ?> - <div class="pull-right flush-right"> - <b><a href="#orgs/add" class="Icon newDepartment add-org"><?php - echo __('Add New Organization'); ?></a></b></div> + <a class="action-button add-org" + href="#"> + <i class="icon-plus-sign"></i> + <?php echo __('Add Organization'); ?> + </a> +<?php } +if ($thisstaff->getRole()->hasPerm(Organization::PERM_DELETE)) { ?> + <span class="action-button" data-dropdown="#action-dropdown-more" + style="/*DELME*/ vertical-align:top; margin-bottom:0"> + <i class="icon-caret-down pull-right"></i> + <span ><i class="icon-cog"></i> <?php echo __('More');?></span> + </span> + <div id="action-dropdown-more" class="action-dropdown anchor-right"> + <ul> + <li><a class="orgs-action" href="#delete"> + <i class="icon-trash icon-fixed-width"></i> + <?php echo __('Delete'); ?></a></li> + </ul> + </div> <?php } ?> +</div> + <div class="clear"></div> <?php $showing = $search ? __('Search Results').': ' : ''; @@ -100,14 +120,16 @@ if($res && ($num=db_num_rows($res))) else $showing .= __('No organizations found!'); ?> -<form action="orgs.php" method="POST" name="staff" > +<form id="orgs-list" action="orgs.php" method="POST" name="staff" > <?php csrf_token(); ?> - <input type="hidden" name="do" value="mass_process" > - <input type="hidden" id="action" name="a" value="" > + <input type="hidden" name="a" value="mass_process" > + <input type="hidden" id="action" name="do" value="" > + <input type="hidden" id="selected-count" name="count" value="" > <table class="list" border="0" cellspacing="1" cellpadding="0" width="940"> <caption><?php echo $showing; ?></caption> <thead> <tr> + <th nowrap width="12"> </th> <th width="400"><a <?php echo $name_sort; ?> href="orgs.php?<?php echo $qstr; ?>&sort=name"><?php echo __('Name'); ?></a></th> <th width="100"><a <?php echo $users_sort; ?> href="orgs.php?<?php echo $qstr; ?>&sort=users"><?php echo __('Users'); ?></a></th> <th width="150"><a <?php echo $create_sort; ?> href="orgs.php?<?php echo $qstr; ?>&sort=create"><?php echo __('Created'); ?></a></th> @@ -125,6 +147,9 @@ else $sel=true; ?> <tr id="<?php echo $row['id']; ?>"> + <td nowrap> + <input type="checkbox" value="<?php echo $row['id']; ?>" class="ckb mass nowarn"/> + </td> <td> <a href="orgs.php?id=<?php echo $row['id']; ?>"><?php echo $row['name']; ?></a> </td> <td> <?php echo $row['users']; ?></td> <td><?php echo Format::date($row['created']); ?></td> @@ -134,6 +159,22 @@ else } //end of while. endif; ?> </tbody> + <tfoot> + <tr> + <td colspan="7"> + <?php if ($res && $num) { ?> + <?php echo __('Select');?>: + <a id="selectAll" href="#ckb"><?php echo __('All');?></a> + <a id="selectNone" href="#ckb"><?php echo __('None');?></a> + <a id="selectToggle" href="#ckb"><?php echo __('Toggle');?></a> + <?php }else{ + echo '<i>'; + echo __('Query returned 0 results.'); + echo '</i>'; + } ?> + </td> + </tr> + </tfoot> </table> <?php if($res && $num): //Show options.. @@ -173,5 +214,31 @@ $(function() { return false; }); + + var goBaby = function(action) { + var ids = [], + $form = $('form#orgs-list'); + $(':checkbox.mass:checked', $form).each(function() { + ids.push($(this).val()); + }); + if (ids.length) { + var submit = function() { + $form.find('#action').val(action); + $.each(ids, function() { $form.append($('<input type="hidden" name="ids[]">').val(this)); }); + $form.find('#selected-count').val(ids.length); + $form.submit(); + }; + $.confirm(__('You sure?')).then(submit); + } + else if (!ids.length) { + $.sysAlert(__('Oops'), + __('You need to select at least one item')); + } + }; + $(document).on('click', 'a.orgs-action', function(e) { + e.preventDefault(); + goBaby($(this).attr('href').substr(1)); + return false; + }); }); </script> diff --git a/include/staff/settings-alerts.inc.php b/include/staff/settings-alerts.inc.php index 14b3fee30ade06c2b8d190b83e276cb295dd2351..3a9326bc4127343b57831a14a81b8d50f1b5c5bb 100644 --- a/include/staff/settings-alerts.inc.php +++ b/include/staff/settings-alerts.inc.php @@ -88,7 +88,7 @@ <?php echo __('Organization Account Manager'); ?> </td> </tr> - <tr><th><em><b><?php echo __('New Internal Note Alert'); ?></b>: + <tr><th><em><b><?php echo __('New Internal Activity Alert'); ?></b>: <i class="help-tip icon-question-sign" href="#internal_note_alert"></i> </em></th></tr> <tr> diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php index a30454b50449800d35b9ddde351faa9ec0722dd1..55241ed1859dcef7290eff40b96d5f613142178e 100644 --- a/include/staff/ticket-view.inc.php +++ b/include/staff/ticket-view.inc.php @@ -80,8 +80,10 @@ if($ticket->isOverdue()) echo __('Edit'); ?></a> <?php } - if ($ticket->isOpen() && !$ticket->isAssigned() && - $role->hasPerm(TicketModel::PERM_ASSIGN)) {?> + if ($ticket->isOpen() + && !$ticket->isAssigned() + && $role->hasPerm(TicketModel::PERM_ASSIGN) + && $ticket->getDept()->isMember($thisstaff)) {?> <a id="ticket-claim" class="action-button pull-right confirm-action" href="#claim"><i class="icon-user"></i> <?php echo __('Claim'); ?></a> @@ -868,7 +870,9 @@ $tcount = $ticket->getThreadEntries($types)->count(); <select id="assignId" name="assignId"> <option value="0" selected="selected">— <?php echo __('Select an Agent OR a Team');?> —</option> <?php - if($ticket->isOpen() && !$ticket->isAssigned()) + if ($ticket->isOpen() + && !$ticket->isAssigned() + && $ticket->getDept()->isMember($thisstaff)) echo sprintf('<option value="%d">'.__('Claim Ticket (comments optional)').'</option>', $thisstaff->getId()); $sid=$tid=0; diff --git a/include/staff/user-view.inc.php b/include/staff/user-view.inc.php index eae6c28cb223625efcdb99d813589e1faa19889c..0497b74b8ce27d2795d700c235d4b4368c985b79 100644 --- a/include/staff/user-view.inc.php +++ b/include/staff/user-view.inc.php @@ -34,7 +34,7 @@ $org = $user->getOrganization(); <?php } else { ?> <a id="user-register" class="action-button pull-right user-action" - href="#users/<?php echo $user->getId(); ?>/register"><i class="icon-edit"></i> + href="#users/<?php echo $user->getId(); ?>/register"><i class="icon-smile"></i> <?php echo __('Register'); ?></a> <?php } ?> diff --git a/include/staff/users.inc.php b/include/staff/users.inc.php index 7e9be8950c5cd1d9c5895018743c2ad79ecc51f7..e83671a684eb9bd63cfb79a83d57180bb615b587 100644 --- a/include/staff/users.inc.php +++ b/include/staff/users.inc.php @@ -57,7 +57,7 @@ $users->values('id', 'name', 'default_email__address', 'account__id', $users->order_by($order . $order_column); ?> <h2><?php echo __('User Directory'); ?></h2> -<div class="pull-left" style="width:700px;"> +<div class="pull-left"> <form action="users.php" method="get"> <?php csrf_token(); ?> <input type="hidden" name="a" value="search"> @@ -71,14 +71,60 @@ $users->order_by($order . $order_column); </table> </form> </div> + +<div class="pull-right"> <?php if ($thisstaff->getRole()->hasPerm(User::PERM_CREATE)) { ?> - <div class="pull-right flush-right" style="padding-right:5px;"> - <b><a href="#users/add" class="Icon newstaff popup-dialog"><?php echo __('Add User'); ?></a></b> - | - <b><a href="#users/import" class="popup-dialog"><i class="icon-cloud-upload icon-large"></i> - <?php echo __('Import'); ?></a></b> -</div> + <a class="action-button popup-dialog" + href="#users/add"> + <i class="icon-plus-sign"></i> + <?php echo __('Add User'); ?> + </a> + <a class="action-button popup-dialog" + href="#users/import"> + <i class="icon-upload"></i> + <?php echo __('Import'); ?> + </a> <?php } ?> + <span class="action-button" data-dropdown="#action-dropdown-more" + style="/*DELME*/ vertical-align:top; margin-bottom:0"> + <i class="icon-caret-down pull-right"></i> + <span ><i class="icon-cog"></i> <?php echo __('More');?></span> + </span> + <div id="action-dropdown-more" class="action-dropdown anchor-right"> + <ul> +<?php if ($thisstaff->getRole()->hasPerm(User::PERM_DELETE)) { ?> + <li><a class="users-action" href="#delete"> + <i class="icon-trash icon-fixed-width"></i> + <?php echo __('Delete'); ?></a></li> +<?php } +if ($thisstaff->getRole()->hasPerm(User::PERM_EDIT)) { ?> + <li><a href="#orgs/lookup/form" onclick="javascript: +$.dialog('ajax.php/orgs/lookup/form', 201); +return false;"> + <i class="icon-group icon-fixed-width"></i> + <?php echo __('Add to Organization'); ?></a></li> +<?php +} +if ('disabled' != $cfg->getClientRegistrationMode()) { ?> + <li><a class="users-action" href="#reset"> + <i class="icon-envelope icon-fixed-width"></i> + <?php echo __('Send Password Reset Email'); ?></a></li> +<?php if ($thisstaff->getRole()->hasPerm(User::PERM_MANAGE)) { ?> + <li><a class="users-action" href="#register"> + <i class="icon-smile icon-fixed-width"></i> + <?php echo __('Register'); ?></a></li> + <li><a class="users-action" href="#lock"> + <i class="icon-lock icon-fixed-width"></i> + <?php echo __('Lock'); ?></a></li> + <li><a class="users-action" href="#unlock"> + <i class="icon-unlock icon-fixed-width"></i> + <?php echo __('Unlock'); ?></a></li> +<?php } +} # end of registration-enabled? ?> + </ul> + </div> +</div> + <div class="clear"></div> <?php $showing = $search ? __('Search Results').': ' : ''; @@ -87,14 +133,17 @@ if($users->exists(true)) else $showing .= __('No users found!'); ?> -<form action="users.php" method="POST" name="staff" > +<form id="users-list" action="users.php" method="POST" name="staff" > <?php csrf_token(); ?> <input type="hidden" name="do" value="mass_process" > <input type="hidden" id="action" name="a" value="" > + <input type="hidden" id="selected-count" name="count" value="" > + <input type="hidden" id="org_id" name="org_id" value="" > <table class="list" border="0" cellspacing="1" cellpadding="0" width="940"> <caption><?php echo $showing; ?></caption> <thead> <tr> + <th nowrap width="12"> </th> <th width="350"><a <?php echo $name_sort; ?> href="users.php?<?php echo $qstr; ?>&sort=name"><?php echo __('Name'); ?></a></th> <th width="250"><a <?php echo $status_sort; ?> href="users.php?<?php @@ -126,6 +175,9 @@ else $sel=true; ?> <tr id="<?php echo $U['id']; ?>"> + <td nowrap> + <input type="checkbox" value="<?php echo $U['id']; ?>" class="ckb mass nowarn"/> + </td> <td> <a class="preview" href="users.php?id=<?php echo $U['id']; ?>" @@ -144,6 +196,22 @@ else </tr> <?php } //end of foreach. ?> </tbody> + <tfoot> + <tr> + <td colspan="7"> + <?php if ($res && $num) { ?> + <?php echo __('Select');?>: + <a id="selectAll" href="#ckb"><?php echo __('All');?></a> + <a id="selectNone" href="#ckb"><?php echo __('None');?></a> + <a id="selectToggle" href="#ckb"><?php echo __('Toggle');?></a> + <?php }else{ + echo '<i>'; + echo __('Query returned 0 results.'); + echo '</i>'; + } ?> + </td> + </tr> + </tfoot> </table> <?php if ($total) { @@ -183,7 +251,44 @@ $(function() { }); return false; - }); + }); + var goBaby = function(action, confirmed) { + var ids = [], + $form = $('form#users-list'); + $(':checkbox.mass:checked', $form).each(function() { + ids.push($(this).val()); + }); + if (ids.length) { + var submit = function() { + $form.find('#action').val(action); + $.each(ids, function() { $form.append($('<input type="hidden" name="ids[]">').val(this)); }); + $form.find('#selected-count').val(ids.length); + $form.submit(); + }; + if (!confirmed) + $.confirm(__('You sure?')).then(submit); + else + submit(); + } + else { + $.sysAlert(__('Oops'), + __('You need to select at least one item')); + } + }; + $(document).on('click', 'a.users-action', function(e) { + e.preventDefault(); + goBaby($(this).attr('href').substr(1)); + return false; + }); + $(document).on('dialog:close', function(e, json) { + $form = $('form#users-list'); + try { + var json = $.parseJSON(json); + $form.find('#org_id').val(json.id); + goBaby('setorg', true); + } + catch (e) { console.log(e); } + }); }); </script> diff --git a/scp/ajax.php b/scp/ajax.php index ed9e37345c16c29766b223c3d5a5da37ec02a1f0..7a6b95fcb3662333969698cfc58ff0527f82b5e7 100644 --- a/scp/ajax.php +++ b/scp/ajax.php @@ -118,7 +118,7 @@ $dispatcher = patterns('', url_post('^/(?P<id>\d+)/profile$', 'updateOrg', array(true)), url_get('^/(?P<id>\d+)/edit$', 'editOrg'), url_get('^/lookup/form$', 'lookup'), - url_post('^/lookup/form$', 'addOrg'), + url_post('^/lookup$', 'lookup'), url_get('^/add$', 'addOrg'), url_post('^/add$', 'addOrg'), url_get('^/select$', 'selectOrg'), diff --git a/scp/css/scp.css b/scp/css/scp.css index 30b384e1436c824a598c231203d9335700c6aaba..744fbf9d1e62d4a0041478a312a8735d9dc06061 100644 --- a/scp/css/scp.css +++ b/scp/css/scp.css @@ -1095,7 +1095,7 @@ ul.tabs.vertical li a { display:block; height:30px; position:absolute; - z-index:101; + z-index:5; } .tip_arrow { diff --git a/scp/js/scp.js b/scp/js/scp.js index 9e1b5d714419856c0b3ebd434701a5265bc7b8ff..3efd18bdbd74008f70e6360c0a3511cb8929c1f4 100644 --- a/scp/js/scp.js +++ b/scp/js/scp.js @@ -587,12 +587,14 @@ $.dialog = function (url, codes, cb, options) { data: $form.serialize(), cache: false, success: function(resp, status, xhr) { + var done = $.Event('dialog:close'); if (xhr && xhr.status && codes && $.inArray(xhr.status, codes) != -1) { $.toggleOverlay(false); $popup.hide(); $('div.body', $popup).empty(); if(cb) cb(xhr, resp); + $popup.trigger(done, [resp, status, xhr]); } else { try { var json = $.parseJSON(resp); @@ -629,6 +631,39 @@ $.sysAlert = function (title, msg, cb) { } }; +$.confirm = function(message, title) { + title = title || __('Please Confirm'); + var D = $.Deferred(), + $popup = $('.dialog#popup'), + hide = function() { + $('#overlay').hide(); + $popup.hide(); + }; + $('div#popup-loading', $popup).hide(); + $('div.body', $popup).empty() + .append($('<h3></h3>').text(title)) + .append($('<a class="close" href="#"><i class="icon-remove-circle"></i></a>')) + .append($('<hr/>')) + .append($('<p class="confirm-action"></p>') + .text(message) + ).append($('<div></div>') + .append($('<b>').text(__('Please confirm to continue.'))) + ).append($('<hr style="margin-top:1em"/>')) + .append($('<p class="full-width"></p>') + .append($('<span class="buttons pull-left"></span>') + .append($('<input type="button" class="close"/>') + .attr('value', __('Cancel')) + .click(function() { hide(); }) + )).append($('<span class="buttons pull-right"></span>') + .append($('<input type="button"/>') + .attr('value', __('OK')) + .click(function() { hide(); D.resolve(); }) + ))).append($('<div class="clear"></div>')); + $('#overlay').fadeIn(); + $popup.show(); + return D.promise(); +}; + $.userLookup = function (url, cb) { $.dialog(url, 201, function (xhr) { var user = $.parseJSON(xhr.responseText); diff --git a/scp/js/ticket.js b/scp/js/ticket.js index 7082679962d08b54af60a24b6b85645812613e55..a105918683214496cb4050d7d238089924dc120b 100644 --- a/scp/js/ticket.js +++ b/scp/js/ticket.js @@ -56,7 +56,7 @@ var autoLock = { //Incoming event... handleEvent: function(e) { - if(!autoLock.lasteventTime) { //I hate nav away warnings..but + if(autoLock.lockId && !autoLock.lasteventTime) { //I hate nav away warnings..but $(document).on('pjax:beforeSend.changed', function(e) { return confirm(__("Any changes or info you've entered will be discarded!")); }); diff --git a/scp/orgs.php b/scp/orgs.php index bb16dcce7e2f8da7ea949d7c1a2b9f9c1fc9bf6f..fb224a5047c58d21ee85d931b4092836f086d03e 100644 --- a/scp/orgs.php +++ b/scp/orgs.php @@ -59,6 +59,39 @@ if ($_POST) { _N('selected end user', 'selected end users', $count)); } break; + + case 'mass_process': + if (!$_POST['ids'] || !is_array($_POST['ids']) || !count($_POST['ids'])) { + $errors['err'] = sprintf(__('You must select at least %s.'), + __('one organization')); + } + else { + $orgs = Organization::objects()->filter( + array('id__in' => $_POST['ids']) + ); + $count = 0; + switch (strtolower($_POST['do'])) { + case 'delete': + foreach ($orgs as $O) + if ($O->delete()) + $count++; + break; + + default: + $errors['err']=__('Unknown action - get technical help.'); + } + if (!$errors['err'] && !$count) { + $errors['err'] = __('Unable to manage any of the selected organizations'); + } + elseif ($_POST['count'] && $count != $_POST['count']) { + $warn = __('Not all selected items were updated'); + } + elseif ($count) { + $msg = __('Successfully managed selected organizations'); + } + } + break; + default: $errors['err'] = __('Unknown action'); } diff --git a/scp/tickets.php b/scp/tickets.php index 9821e037c737e55299556a71ddd4169fe2febbe7..86247b2b03e0089e8f0dff7d42b5483cb232abdd 100644 --- a/scp/tickets.php +++ b/scp/tickets.php @@ -162,12 +162,21 @@ if($_POST && !$errors): $id = preg_replace("/[^0-9]/", "",$_POST['assignId']); $claim = (is_numeric($_POST['assignId']) && $_POST['assignId']==$thisstaff->getId()); + $dept = $ticket->getDept(); - if(!$_POST['assignId'] || !$id) + if (!$_POST['assignId'] || !$id) $errors['assignId'] = __('Select assignee'); - elseif($_POST['assignId'][0]!='s' && $_POST['assignId'][0]!='t' && !$claim) - $errors['assignId']=__('Invalid assignee ID - get technical support'); - elseif($ticket->isAssigned()) { + elseif ($_POST['assignId'][0]!='s' && $_POST['assignId'][0]!='t' && !$claim) + $errors['assignId']= sprintf('%s - %s', + __('Invalid assignee '), + __('get technical support')); + elseif ($_POST['assignId'][0]!='s' + && $dept->assignMembersOnly() + && !$dept->isMember($id)) { + $errors['assignId'] = sprintf('%s. %s', + __('Invalid assignee'), + __('Must be department member')); + } elseif($ticket->isAssigned()) { if($_POST['assignId'][0]=='s' && $id==$ticket->getStaffId()) $errors['assignId']=__('Ticket already assigned to the agent.'); elseif($_POST['assignId'][0]=='t' && $id==$ticket->getTeamId()) @@ -274,7 +283,7 @@ if($_POST && !$errors): $errors['err'] = __('Only open tickets can be assigned'); } elseif($ticket->isAssigned()) { $errors['err'] = sprintf(__('Ticket is already assigned to %s'),$ticket->getAssigned()); - } elseif($ticket->assignToStaff($thisstaff->getId(), (sprintf(__('Ticket claimed by %s'),$thisstaff->getName())), false)) { + } elseif ($ticket->claim()) { $msg = __('Ticket is now assigned to you!'); } else { $errors['err'] = __('Problems assigning the ticket. Try again'); diff --git a/scp/users.php b/scp/users.php index a606ddca6adadd5aa34b520348d3ad9f760cd045..20956ae8b21381b093f8d9aeb51849fa5ece351b 100644 --- a/scp/users.php +++ b/scp/users.php @@ -75,7 +75,70 @@ if ($_POST) { $errors['err'] = sprintf(__('You must select at least %s.'), __('one end user')); } else { - $errors['err'] = "Coming soon!"; + $users = User::objects()->filter( + array('id__in' => $_POST['ids']) + ); + $count = 0; + switch (strtolower($_POST['a'])) { + case 'lock': + foreach ($users as $U) + if (($acct = $U->getAccount()) && $acct->lock()) + $count++; + break; + + case 'unlock': + foreach ($users as $U) + if (($acct = $U->getAccount()) && $acct->unlock()) + $count++; + break; + + case 'delete': + foreach ($users as $U) + if ($U->delete()) + $count++; + break; + + case 'reset': + foreach ($users as $U) + if (($acct = $U->getAccount()) && $acct->sendResetEmail()) + $count++; + break; + + case 'register': + foreach ($users as $U) { + if (($acct = $U->getAccount()) && $acct->sendConfirmEmail()) + $count++; + elseif ($acct = UserAccount::register($U, + array('sendemail' => true), $errors + )) { + $count++; + } + } + break; + + case 'setorg': + if (!($org = Organization::lookup($_POST['org_id']))) + $errors['err'] = __('Unknown action - get technical help.'); + foreach ($users as $U) { + if ($U->setOrganization($org)) + $count++; + } + break; + + default: + $errors['err']=__('Unknown action - get technical help.'); + } + if (!$errors['err'] && !$count) { + $errors['err'] = __('Unable to manage any of the selected end users'); + } + elseif ($_POST['count'] && $count != $_POST['count']) { + $warn = __('Not all selected items were updated'); + } + elseif ($count) { + $msg = __('Successfully managed selected end users'); + } + + } break; case 'import-users': diff --git a/setup/cli/modules/class.module.php b/setup/cli/modules/class.module.php index c6af32572dfde210a1014d884d91cff4acf18d9a..f5f866b0e48196b81b36040edfa0a3601130463b 100644 --- a/setup/cli/modules/class.module.php +++ b/setup/cli/modules/class.module.php @@ -212,8 +212,11 @@ class Module { $this->parseArgs(array_slice($argv, 1)); foreach (array_keys($this->arguments) as $idx=>$name) - if (!isset($this->_args[$idx])) - $this->optionError($name . " is a required argument"); + if (!isset($this->_args[$idx])) { + $info = $this->arguments[$name]; + if (!is_array($info) || !isset($info['required']) || $info['required']) + $this->optionError($name . " is a required argument"); + } elseif (is_array($this->arguments[$name]) && isset($this->arguments[$name]['options']) && !isset($this->arguments[$name]['options'][$this->_args[$idx]])) diff --git a/setup/cli/modules/file.php b/setup/cli/modules/file.php index f5057230ac293cae0b23b0944e0b419aca033369..b1a2f9be697e36f6c837b4584d6a6f5c7eed6735 100644 --- a/setup/cli/modules/file.php +++ b/setup/cli/modules/file.php @@ -374,6 +374,14 @@ class FileManager extends Module { )); } } + + // Update file to record current backend + $sql = 'UPDATE '.FILE_TABLE.' SET bk=' + .db_input($bk->getBkChar()) + .' WHERE id='.db_input($f->getId()); + if (!db_query($sql) || db_affected_rows()!=1) + return false; + } // end try catch (Exception $ex) { if ($bk) $bk->unlink(); diff --git a/setup/test/tests/stubs.php b/setup/test/tests/stubs.php index fa0706313a77a2edde33868231556c017d88b306..392873e1957f9b29bb5daa9d2db2f7070fba3050 100644 --- a/setup/test/tests/stubs.php +++ b/setup/test/tests/stubs.php @@ -156,4 +156,7 @@ class ResourceBundle { function getLocales() {} } +class Aws_Route53_Client { + function changeResourceRecordSets() {} +} ?> diff --git a/setup/test/tests/test.header_functions.php b/setup/test/tests/test.header_functions.php index b64a1c400946e590e01a8742d47d9a09ffefbd46..0b2328ce4f30cd42ea6ee09a3e51fd138ffffb91 100644 --- a/setup/test/tests/test.header_functions.php +++ b/setup/test/tests/test.header_functions.php @@ -5,65 +5,67 @@ define('PEAR_DIR', INCLUDE_DIR.'/pear/'); require_once INCLUDE_DIR."class.mailparse.php"; abstract class Priorities { - const HIGH_PRIORITY = 1; - const NORMAL_PRIORITY = 2; - const LOW_PRIORITY = 3; - const NO_PRIORITY = 0; + const HIGH_PRIORITY = 1; + const NORMAL_PRIORITY = 2; + const LOW_PRIORITY = 3; + const NO_PRIORITY = 0; } class TestHeaderFunctions extends Test { var $name = "Email Header Function Algorithm Regression Tests."; - + function testMailParsePriority() { - $func_class_method = array('Mail_Parse','parsePriority'); - $strlen_base = strlen($this->h()); + $func_class_method = array('Mail_Parse','parsePriority'); + $strlen_base = strlen($this->h()); + + foreach ( array ( + // input => output + 'X-Priority: isNAN' => Priorities::NO_PRIORITY, + 'X-Priority: 1' => Priorities::HIGH_PRIORITY, + 'X-Priority: 2' => Priorities::HIGH_PRIORITY, + 'X-Priority: 3' => Priorities::NORMAL_PRIORITY, + 'X-Priority: 4' => Priorities::NORMAL_PRIORITY, + 'X-Priority: 5' => Priorities::LOW_PRIORITY, + 'X-Priority: 6' => Priorities::LOW_PRIORITY, + 'No priority set' => Priorities::NO_PRIORITY, + 'Priority: normal' => Priorities::NORMAL_PRIORITY, + 'xyz-priority: high' => Priorities::HIGH_PRIORITY, + 'Priority: high' => Priorities::HIGH_PRIORITY, + 'priority: low' => Priorities::LOW_PRIORITY, + 'x-priority: 1000' => Priorities::HIGH_PRIORITY, // only matches first 1, not the full 1000 + 'priority: 3' => Priorities::NORMAL_PRIORITY, + 'IPM-Importance: low' => Priorities::LOW_PRIORITY, + 'My-Importance: URGENT' => Priorities::HIGH_PRIORITY, + 'Urgency: High' => Priorities::NO_PRIORITY, //urgency doesn't match.. maybe it should? + 'Importance: Low' => Priorities::LOW_PRIORITY, + 'X-MSMail-Priority: High' => Priorities::HIGH_PRIORITY, + '' => Priorities::NO_PRIORITY + ) as $priority => $response ) { + $this->assert(is_int($response), "Setup fail, function should only return Integer values"); + //get header + $header = $this->h($priority); + + if(strlen($priority)){ + $this->assert((strlen($header) > $strlen_base), "Setup fail, function h not returning correct string length"); + } + if (! (call_user_func_array ($func_class_method , array($header) ) == $response)){ + //TODO: make line number dynamic + $this->fail ( "class.mailparse.php", 351, "Algorithm mistake: $priority should return $response!" ); + }else{ + $this->pass(); + } + } - foreach ( array ( - 'X-Priority: isNAN' => Priorities::NO_PRIORITY, // input => output - 'X-Priority: 1' => Priorities::LOW_PRIORITY, - 'X-Priority: 2' => Priorities::LOW_PRIORITY, - 'X-Priority: 3' => Priorities::NORMAL_PRIORITY, - 'X-Priority: 4' => Priorities::NORMAL_PRIORITY, - 'X-Priority: 5' => Priorities::HIGH_PRIORITY, - 'X-Priority: 6' => Priorities::HIGH_PRIORITY, - 'No priority set' => Priorities::NO_PRIORITY, - 'Priority: normal' => Priorities::NORMAL_PRIORITY, - 'xyz-priority: high' => Priorities::HIGH_PRIORITY, - 'Priority: high' => Priorities::HIGH_PRIORITY, - 'priority: low' => Priorities::LOW_PRIORITY, - 'x-priority: 1000' => Priorities::LOW_PRIORITY, // only matches first 1, not the full 1000 - 'priority: 3' => Priorities::NORMAL_PRIORITY, - 'IPM-Importance: low' => Priorities::LOW_PRIORITY, - 'My-Importance: URGENT' => Priorities::HIGH_PRIORITY, - 'Urgency: High' => Priorities::NO_PRIORITY, //urgency doesn't match.. maybe it should? - 'X-Importance: 5' => Priorities::HIGH_PRIORITY, - '' => Priorities::NO_PRIORITY - ) as $priority => $response ) { - $this->assert(is_int($response), "Setup fail, function should only return Integer values"); - //get header - $header = $this->h($priority); - - if(strlen($priority)){ - $this->assert((strlen($header) > $strlen_base), "Setup fail, function h not returning correct string length"); - } - if (! (call_user_func_array ($func_class_method , array($header) ) == $response)){ - //TODO: make line number dynamic - $this->fail ( "class.mailparse.php", 351, "Algorithm mistake: $priority should return $response!" ); - }else{ - $this->pass(); - } - } - } - + /** * Generate some header text to test with. Allows insertion of a known header variable - * + * * @param string $setPriority * @return string */ function h($setPriority = "") { - return <<<HEADER + return <<<HEADER Delivered-To: clonemeagain@gmail.com Received: by 10.69.18.42 with SMTP id gj10csp88238pbd; Fri, 20 Dec 2013 10:08:25 -0800 (PST)