/** * Saves changes to a ticket, adds a new comment/changelog, * notifies any relevant parties * * @return void */ public function saveTask($redirect = 1) { // Check for request forgeries Request::checkToken(); // Incoming $isNew = true; $id = Request::getInt('id', 0); if ($id) { $isNew = false; } // Load the old ticket so we can compare for the changelog $old = new Ticket($id); $old->set('tags', $old->tags('string')); // Initiate class and bind posted items to database fields $row = new Ticket($id); if (!$row->bind($_POST)) { throw new Exception($row->getError(), 500); } if ($row->get('target_date') && $row->get('target_date') != '0000-00-00 00:00:00') { $row->set('target_date', Date::of($row->get('target_date'), Config::get('offset'))->toSql()); } else { $row->set('target_date', '0000-00-00 00:00:00'); } $comment = Request::getVar('comment', '', 'post', 'none', 2); $rowc = new Comment(); $rowc->set('ticket', $id); // Check if changes were made inbetween the time the comment was started and posted if ($id) { $started = Request::getVar('started', Date::toSql(), 'post'); $lastcomment = $row->comments('list', array('sort' => 'created', 'sort_Dir' => 'DESC', 'limit' => 1, 'start' => 0, 'ticket' => $id))->first(); if (isset($lastcomment) && $lastcomment->created() >= $started) { $rowc->set('comment', $comment); \Notify::error(Lang::txt('Changes were made to this ticket in the time since you began commenting/making changes. Please review your changes before submitting.')); return $this->editTask($rowc); } } if ($id && isset($_POST['status']) && $_POST['status'] == 0) { $row->set('open', 0); $row->set('resolved', Lang::txt('COM_SUPPORT_TICKET_COMMENT_OPT_CLOSED')); } $row->set('open', $row->status('open')); // If an existing ticket AND closed AND previously open if ($id && !$row->get('open') && $row->get('open') != $old->get('open')) { // Record the closing time $row->set('closed', Date::toSql()); } // Check content if (!$row->check()) { throw new Exception($row->getError(), 500); } // Store new content if (!$row->store()) { throw new Exception($row->getError(), 500); } // Save the tags $row->tag(Request::getVar('tags', '', 'post'), User::get('id'), 1); $row->set('tags', $row->tags('string')); $base = Request::base(); if (substr($base, -14) == 'administrator/') { $base = substr($base, 0, strlen($base) - 14); } $webpath = trim($this->config->get('webpath'), '/'); $allowEmailResponses = $this->config->get('email_processing'); $this->config->set('email_terse', Request::getInt('email_terse', 0)); if ($this->config->get('email_terse')) { $allowEmailResponses = false; } if ($allowEmailResponses) { try { $encryptor = new \Hubzero\Mail\Token(); } catch (Exception $e) { $allowEmailResponses = false; } } // If a new ticket... if ($isNew) { // Get any set emails that should be notified of ticket submission $defs = explode(',', $this->config->get('emails', '{config.mailfrom}')); if ($defs) { // Get some email settings $msg = new \Hubzero\Mail\Message(); $msg->setSubject(Config::get('sitename') . ' ' . Lang::txt('COM_SUPPORT') . ', ' . Lang::txt('COM_SUPPORT_TICKET_NUMBER', $row->get('id'))); $msg->addFrom(Config::get('mailfrom'), Config::get('sitename') . ' ' . Lang::txt(strtoupper($this->_option))); // Plain text email $eview = new \Hubzero\Mail\View(array('base_path' => PATH_CORE . DS . 'components' . DS . $this->_option . DS . 'site', 'name' => 'emails', 'layout' => 'ticket_plain')); $eview->option = $this->_option; $eview->controller = $this->_controller; $eview->ticket = $row; $eview->config = $this->config; $eview->delimiter = ''; $plain = $eview->loadTemplate(false); $plain = str_replace("\n", "\r\n", $plain); $msg->addPart($plain, 'text/plain'); // HTML email $eview->setLayout('ticket_html'); $html = $eview->loadTemplate(); $html = str_replace("\n", "\r\n", $html); if (!$this->config->get('email_terse')) { foreach ($row->attachments() as $attachment) { if ($attachment->size() < 2097152) { if ($attachment->isImage()) { $file = basename($attachment->link('filepath')); $html = preg_replace('/<a class="img" data\\-filename="' . str_replace('.', '\\.', $file) . '" href="(.*?)"\\>(.*?)<\\/a>/i', '<img src="' . $message->getEmbed($attachment->link('filepath')) . '" alt="" />', $html); } else { $message->addAttachment($attachment->link('filepath')); } } } } $msg->addPart($html, 'text/html'); // Loop through the addresses foreach ($defs as $def) { $def = trim($def); // Check if the address should come from Joomla config if ($def == '{config.mailfrom}') { $def = Config::get('mailfrom'); } // Check for a valid address if (Validate::email($def)) { // Send e-mail $msg->setTo(array($def)); $msg->send(); } } } } // Incoming comment if ($comment) { // If a comment was posted by the ticket submitter to a "waiting user response" ticket, change status. if ($row->isWaiting() && User::get('username') == $row->get('login')) { $row->open(); } } // Create a new support comment object and populate it $access = Request::getInt('access', 0); //$rowc = new Comment(); $rowc->set('ticket', $row->get('id')); $rowc->set('comment', nl2br($comment)); $rowc->set('created', Date::toSql()); $rowc->set('created_by', User::get('id')); $rowc->set('access', $access); // Compare fields to find out what has changed for this ticket and build a changelog $rowc->changelog()->diff($old, $row); $rowc->changelog()->cced(Request::getVar('cc', '')); // Save the data if (!$rowc->store()) { throw new Exception($rowc->getError(), 500); } Event::trigger('support.onTicketUpdate', array($row, $rowc)); if ($tmp = Request::getInt('tmp_dir')) { $attach = new Tables\Attachment($this->database); $attach->updateCommentId($tmp, $rowc->get('id')); } if (!$isNew) { $attachment = $this->uploadTask($row->get('id'), $rowc->get('id')); } // Only do the following if a comment was posted or ticket was reassigned // otherwise, we're only recording a changelog if ($rowc->get('comment') || $row->get('owner') != $old->get('owner') || $row->get('group') != $old->get('group') || $rowc->attachments()->total() > 0) { // Send e-mail to ticket submitter? if (Request::getInt('email_submitter', 0) == 1) { // Is the comment private? If so, we do NOT send e-mail to the // submitter regardless of the above setting if (!$rowc->isPrivate()) { $rowc->addTo(array('role' => Lang::txt('COM_SUPPORT_COMMENT_SEND_EMAIL_SUBMITTER'), 'name' => $row->submitter('name'), 'email' => $row->submitter('email'), 'id' => $row->submitter('id'))); } } // Send e-mail to ticket owner? if (Request::getInt('email_owner', 0) == 1) { if ($old->get('owner') && $row->get('owner') != $old->get('owner')) { $rowc->addTo(array('role' => Lang::txt('COM_SUPPORT_COMMENT_SEND_EMAIL_PRIOR_OWNER'), 'name' => $old->owner('name'), 'email' => $old->owner('email'), 'id' => $old->owner('id'))); } if ($row->get('owner')) { $rowc->addTo(array('role' => Lang::txt('COM_SUPPORT_COMMENT_SEND_EMAIL_OWNER'), 'name' => $row->owner('name'), 'email' => $row->owner('email'), 'id' => $row->owner('id'))); } elseif ($row->get('group')) { $group = \Hubzero\User\Group::getInstance($row->get('group')); if ($group) { foreach ($group->get('managers') as $manager) { $manager = User::getInstance($manager); if (!$manager || !$manager->get('id')) { continue; } $rowc->addTo(array('role' => Lang::txt('COM_SUPPORT_COMMENT_SEND_EMAIL_GROUPMANAGER'), 'name' => $manager->get('name'), 'email' => $manager->get('email'), 'id' => $manager->get('id'))); } } } } // Add any CCs to the e-mail list foreach ($rowc->changelog()->get('cc') as $cc) { $rowc->addTo($cc, Lang::txt('COM_SUPPORT_COMMENT_SEND_EMAIL_CC')); } // Message people watching this ticket, // but ONLY if the comment was NOT marked private $this->acl = ACL::getACL(); foreach ($row->watchers() as $watcher) { $this->acl->setUser($watcher->user_id); if (!$rowc->isPrivate() || $rowc->isPrivate() && $this->acl->check('read', 'private_comments')) { $rowc->addTo($watcher->user_id, 'watcher'); } } $this->acl->setUser(User::get('id')); if (count($rowc->to())) { // Build e-mail components $subject = Lang::txt('COM_SUPPORT_EMAIL_SUBJECT_TICKET_COMMENT', $row->get('id')); $from = array('name' => Lang::txt('COM_SUPPORT_EMAIL_FROM', Config::get('sitename')), 'email' => Config::get('mailfrom'), 'multipart' => md5(date('U'))); // Plain text email $eview = new \Hubzero\Mail\View(array('base_path' => PATH_CORE . DS . 'components' . DS . $this->_option . DS . 'site', 'name' => 'emails', 'layout' => 'comment_plain')); $eview->option = $this->_option; $eview->controller = $this->_controller; $eview->comment = $rowc; $eview->ticket = $row; $eview->config = $this->config; $eview->delimiter = $allowEmailResponses ? '~!~!~!~!~!~!~!~!~!~!' : ''; $message['plaintext'] = $eview->loadTemplate(false); $message['plaintext'] = str_replace("\n", "\r\n", $message['plaintext']); // HTML email $eview->setLayout('comment_html'); $message['multipart'] = $eview->loadTemplate(); $message['multipart'] = str_replace("\n", "\r\n", $message['multipart']); $message['attachments'] = array(); if (!$this->config->get('email_terse')) { foreach ($rowc->attachments() as $attachment) { if ($attachment->size() < 2097152) { $message['attachments'][] = $attachment->link('filepath'); } } } // Send e-mail to admin? foreach ($rowc->to('ids') as $to) { if ($allowEmailResponses) { // The reply-to address contains the token $token = $encryptor->buildEmailToken(1, 1, $to['id'], $id); $from['replytoemail'] = 'htc-' . $token . strstr(Config::get('mailfrom'), '@'); } // Get the user's email address if (!Event::trigger('xmessage.onSendMessage', array('support_reply_submitted', $subject, $message, $from, array($to['id']), $this->_option))) { $this->setError(Lang::txt('COM_SUPPORT_ERROR_FAILED_TO_MESSAGE', $to['name'] . '(' . $to['role'] . ')')); } // Watching should be anonymous if ($to['role'] == 'watcher') { continue; } $rowc->changelog()->notified($to['role'], $to['name'], $to['email']); } foreach ($rowc->to('emails') as $to) { if ($allowEmailResponses) { $token = $encryptor->buildEmailToken(1, 1, -9999, $id); $email = array($to['email'], 'htc-' . $token . strstr(Config::get('mailfrom'), '@')); // In this case each item in email in an array, 1- To, 2:reply to address Utilities::sendEmail($email[0], $subject, $message, $from, $email[1]); } else { // Email is just a plain 'ol string Utilities::sendEmail($to['email'], $subject, $message, $from); } // Watching should be anonymous if ($to['role'] == 'watcher') { continue; } $rowc->changelog()->notified($to['role'], $to['name'], $to['email']); } } else { // Force entry to private if no comment or attachment was made if (!$rowc->get('comment') && $rowc->attachments()->total() <= 0) { $rowc->set('access', 1); } } // Were there any changes? if (count($rowc->changelog()->get('notifications')) > 0 || $access != $rowc->get('access')) { // Save the data if (!$rowc->store()) { throw new Exception($rowc->getError(), 500); } } } // output messsage and redirect if ($redirect) { $filters = Request::getVar('filters', ''); $filters = str_replace('&', '&', $filters); // Redirect App::redirect(Route::url('index.php?option=' . $this->_option . '&controller=' . $this->_controller . ($filters ? '&' . $filters : ''), false), Lang::txt('COM_SUPPORT_TICKET_SUCCESSFULLY_SAVED', $row->get('id'))); return; } $this->view->setLayout('edit'); $this->editTask(); }
/** * Updates a ticket with any changes and adds a new comment * * @return void */ public function updateTask() { // Make sure we are still logged in if (User::isGuest()) { $return = base64_encode(Route::url('index.php?option=' . $this->_option . '&controller=' . $this->_controller . '&task=' . $this->_task, false, true)); App::redirect(Route::url('index.php?option=com_users&view=login&return=' . $return, false)); return; } // Check for request forgeries Request::checkToken(); // Incoming $id = Request::getInt('id', 0, 'post'); if (!$id) { throw new Exception(Lang::txt('COM_SUPPORT_ERROR_MISSING_TICKET_ID'), 500); } $comment = Request::getVar('comment', '', 'post', 'none', 2); $incoming = Request::getVar('ticket', array(), 'post'); $incoming = array_map('trim', $incoming); if (isset($incoming['target_date'])) { if (!$incoming['target_date']) { $incoming['target_date'] = '0000-00-00 00:00:00'; } else { $incoming['target_date'] = Date::of($incoming['target_date'], Config::get('offset'))->toSql(); } } // Load the old ticket so we can compare for the changelog $old = new Ticket($id); $old->set('tags', $old->tags('string')); // Initiate class and bind posted items to database fields $row = new Ticket($id); if (!$row->bind($incoming)) { throw new Exception($row->getError(), 500); } $rowc = new Comment(); $rowc->set('ticket', $id); // Check if changes were made inbetween the time the comment was started and posted $started = Request::getVar('started', Date::toSql(), 'post'); $lastcomment = $row->comments('list', array('sort' => 'created', 'sort_Dir' => 'DESC', 'limit' => 1, 'start' => 0, 'ticket' => $id))->first(); if ($lastcomment && $lastcomment->created() > $started) { $rowc->set('comment', $comment); $this->setError(Lang::txt('Changes were made to this ticket in the time since you began commenting/making changes. Please review your changes before submitting.')); return $this->ticketTask($rowc); } // Update ticket status if necessary if ($id && isset($incoming['status']) && $incoming['status'] == 0) { $row->set('open', 0); $row->set('resolved', Lang::txt('COM_SUPPORT_COMMENT_OPT_CLOSED')); } $row->set('open', $row->status('open')); // Check content if (!$row->check()) { throw new Exception($row->getError(), 500); } // If an existing ticket AND closed AND previously open if ($id && !$row->get('open') && $row->get('open') != $old->get('open')) { // Record the closing time $row->set('closed', Date::toSql()); } // Incoming comment if ($comment) { // If a comment was posted by the ticket submitter to a "waiting user response" ticket, change status. if ($row->isWaiting() && User::get('username') == $row->get('login')) { $row->open(); } } // Store new content if (!$row->store()) { throw new Exception($row->getError(), 500); } // Save the tags $row->tag(Request::getVar('tags', '', 'post'), User::get('id'), 1); $row->set('tags', $row->tags('string')); // Create a new support comment object and populate it $access = Request::getInt('access', 0); $rowc->set('ticket', $id); $rowc->set('comment', nl2br($comment)); $rowc->set('created', Date::toSql()); $rowc->set('created_by', User::get('id')); $rowc->set('access', $access); // Compare fields to find out what has changed for this ticket and build a changelog $rowc->changelog()->diff($old, $row); $rowc->changelog()->cced(Request::getVar('cc', '')); // Save the data if (!$rowc->store()) { throw new Exception($rowc->getError(), 500); } Event::trigger('support.onTicketUpdate', array($row, $rowc)); $attach = new Tables\Attachment($this->database); if ($tmp = Request::getInt('tmp_dir')) { $attach->updateCommentId($tmp, $rowc->get('id')); } $attachment = $this->uploadTask($row->get('id'), $rowc->get('id')); // Only do the following if a comment was posted // otherwise, we're only recording a changelog if ($rowc->get('comment') || $row->get('owner') != $old->get('owner') || $row->get('group') != $old->get('group') || $rowc->attachments()->total() > 0) { // Send e-mail to ticket submitter? if (Request::getInt('email_submitter', 0) == 1) { // Is the comment private? If so, we do NOT send e-mail to the // submitter regardless of the above setting if (!$rowc->isPrivate()) { $rowc->addTo(array('role' => Lang::txt('COM_SUPPORT_COMMENT_SEND_EMAIL_SUBMITTER'), 'name' => $row->submitter('name'), 'email' => $row->submitter('email'), 'id' => $row->submitter('id'))); } } // Send e-mail to ticket owner? if (Request::getInt('email_owner', 0) == 1) { if ($old->get('owner') && $row->get('owner') != $old->get('owner')) { $rowc->addTo(array('role' => Lang::txt('COM_SUPPORT_COMMENT_SEND_EMAIL_PRIOR_OWNER'), 'name' => $old->owner('name'), 'email' => $old->owner('email'), 'id' => $old->owner('id'))); } if ($row->get('owner')) { $rowc->addTo(array('role' => Lang::txt('COM_SUPPORT_COMMENT_SEND_EMAIL_OWNER'), 'name' => $row->owner('name'), 'email' => $row->owner('email'), 'id' => $row->owner('id'))); } elseif ($row->get('group')) { $group = \Hubzero\User\Group::getInstance($row->get('group')); if ($group) { foreach ($group->get('managers') as $manager) { $manager = User::getInstance($manager); if (!$manager || !$manager->get('id')) { continue; } $rowc->addTo(array('role' => Lang::txt('COM_SUPPORT_COMMENT_SEND_EMAIL_GROUPMANAGER'), 'name' => $manager->get('name'), 'email' => $manager->get('email'), 'id' => $manager->get('id'))); } } } } // Add any CCs to the e-mail list foreach ($rowc->changelog()->get('cc') as $cc) { $rowc->addTo($cc, Lang::txt('COM_SUPPORT_COMMENT_SEND_EMAIL_CC')); } // Message people watching this ticket, // but ONLY if the comment was NOT marked private foreach ($row->watchers() as $watcher) { $this->acl->setUser($watcher->user_id); if (!$rowc->isPrivate() || $rowc->isPrivate() && $this->acl->check('read', 'private_comments')) { $rowc->addTo($watcher->user_id, 'watcher'); } } $this->acl->setUser(User::get('id')); $recipients = array(['support.tickets', 1]); if (count($rowc->to())) { $this->config->set('email_terse', Request::getInt('email_terse', 0)); $allowEmailResponses = $this->config->get('email_processing'); if ($this->config->get('email_terse')) { $allowEmailResponses = false; } if ($allowEmailResponses) { try { $encryptor = new \Hubzero\Mail\Token(); } catch (Exception $e) { $allowEmailResponses = false; } } // Build e-mail components $subject = Lang::txt('COM_SUPPORT_EMAIL_SUBJECT_TICKET_COMMENT', $row->get('id')); $from = array('name' => Lang::txt('COM_SUPPORT_EMAIL_FROM', Config::get('sitename')), 'email' => Config::get('mailfrom'), 'multipart' => md5(date('U'))); $message = array(); // Plain text email $eview = new \Hubzero\Mail\View(array('name' => 'emails', 'layout' => 'comment_plain')); $eview->option = $this->_option; $eview->controller = $this->_controller; $eview->comment = $rowc; $eview->ticket = $row; $eview->config = $this->config; $eview->delimiter = $allowEmailResponses ? '~!~!~!~!~!~!~!~!~!~!' : ''; $message['plaintext'] = $eview->loadTemplate(false); $message['plaintext'] = str_replace("\n", "\r\n", $message['plaintext']); // HTML email $eview->setLayout('comment_html'); $message['multipart'] = $eview->loadTemplate(); $message['multipart'] = str_replace("\n", "\r\n", $message['multipart']); $message['attachments'] = array(); if (!$this->config->get('email_terse')) { foreach ($rowc->attachments() as $attachment) { if ($attachment->size() < 2097152) { $message['attachments'][] = $attachment->link('filepath'); } } } foreach ($rowc->to('ids') as $to) { $recipients[] = ['user', $to['id']]; if ($allowEmailResponses) { // The reply-to address contains the token $token = $encryptor->buildEmailToken(1, 1, $to['id'], $id); $from['replytoemail'] = 'htc-' . $token . strstr(Config::get('mailfrom'), '@'); } // Get the user's email address if (!Event::trigger('xmessage.onSendMessage', array('support_reply_submitted', $subject, $message, $from, array($to['id']), $this->_option))) { $this->setError(Lang::txt('COM_SUPPORT_ERROR_FAILED_TO_MESSAGE', $to['name'] . '(' . $to['role'] . ')')); } // Watching should be anonymous if ($to['role'] == 'watcher') { continue; } $rowc->changelog()->notified($to['role'], $to['name'], $to['email']); } foreach ($rowc->to('emails') as $to) { if ($allowEmailResponses) { $token = $encryptor->buildEmailToken(1, 1, -9999, $id); $email = array($to['email'], 'htc-' . $token . strstr(Config::get('mailfrom'), '@')); // In this case each item in email in an array, 1- To, 2:reply to address Utilities::sendEmail($email[0], $subject, $message, $from, $email[1]); } else { // email is just a plain 'ol string Utilities::sendEmail($to['email'], $subject, $message, $from); } // Watching should be anonymous if ($to['role'] == 'watcher') { continue; } $rowc->changelog()->notified($to['role'], $to['name'], $to['email']); } } else { // Force entry to private if no comment or attachment was made if (!$rowc->get('comment') && $rowc->attachments()->total() <= 0) { $rowc->set('access', 1); } } // Were there any changes? if (count($rowc->changelog()->get('notifications')) > 0 || $access != $rowc->get('access')) { if (!$rowc->store()) { throw new Exception($rowc->getError(), 500); } } $desc = Lang::txt('COM_SUPPORT_ACTIVITY_TICKET_UPDATED', '<a href="' . Route::url($row->link()) . '">#' . $row->get('id') . ' - ' . $row->get('summary') . '</a>'); if ($rowc->get('comment')) { $desc = Lang::txt('COM_SUPPORT_ACTIVITY_COMMENT_CREATED', $rowc->get('id'), '<a href="' . Route::url($row->link()) . '">#' . $row->get('id') . ' - ' . $row->get('summary') . '</a>'); } Event::trigger('system.logActivity', ['activity' => ['action' => 'created', 'scope' => 'support.ticket.comment', 'scope_id' => $rowc->get('id'), 'description' => $desc, 'details' => array('id' => $row->get('id'), 'summary' => $row->get('summary'), 'url' => Route::url($row->link()), 'comment' => $rowc->get('id'))], 'recipients' => $recipients]); } // Display the ticket with changes, new comment App::redirect(Route::url('index.php?option=' . $this->_option . '&controller=' . $this->_controller . '&task=ticket&id=' . $id), $this->getError() ? $this->getError() : null, $this->getError() ? 'error' : null); }