diff --git a/include/ajax.config.php b/include/ajax.config.php index 938625ff1307f272abb9dff4aae65f1654455235..82d92f1ca333408a2eff3bc67c84f12d78807d39 100644 --- a/include/ajax.config.php +++ b/include/ajax.config.php @@ -26,6 +26,7 @@ class ConfigAjaxAPI extends AjaxController { 'lock_time' => ($cfg->getLockTime()*3600), 'max_file_uploads'=> (int) $cfg->getStaffMaxFileUploads(), 'html_thread' => (bool) $cfg->isHtmlThreadEnabled(), + 'date_format' => ($cfg->getDateFormat()), 'allow_attachments' => (bool) $cfg->allowAttachments(), ); return $this->json_encode($config); diff --git a/include/class.mailer.php b/include/class.mailer.php index c4c95492a4d281be643488263dabde69e393eda4..e5165ba697cc708b6fd3f4383f2e44a84c93b5a3 100644 --- a/include/class.mailer.php +++ b/include/class.mailer.php @@ -123,9 +123,9 @@ class Mailer { } if ($options) { - if (isset($options['replyto'])) - $headers += array('In-Reply-To' => $options['replyto']); - if (isset($options['references'])) { + if (isset($options['inreplyto']) && $options['inreplyto']) + $headers += array('In-Reply-To' => $options['inreplyto']); + if (isset($options['references']) && $options['references']) { if (is_array($options['references'])) $headers += array('References' => implode(' ', $options['references'])); diff --git a/include/class.mailfetch.php b/include/class.mailfetch.php index 0f1f7cef5a5737c1a23d2bdad285f0915c55fcf0..824a0358e8462a2d67eb652bb760062937317f9b 100644 --- a/include/class.mailfetch.php +++ b/include/class.mailfetch.php @@ -371,7 +371,7 @@ class MailFetcher { if($part && !$part->parts) { //Check if the part is an attachment. $filename = false; - if ($part->ifdisposition + if ($part->ifdisposition && $part->ifdparameters && in_array(strtolower($part->disposition), array('attachment', 'inline'))) { $filename = $this->findFilename($part->dparameters); diff --git a/include/class.mailparse.php b/include/class.mailparse.php index 8d84f3f5afeb4d8129490def2802b158aa3f0dd7..364d58d85213f9cc23d9c851abb56853167f3b03 100644 --- a/include/class.mailparse.php +++ b/include/class.mailparse.php @@ -86,7 +86,7 @@ class Mail_Parse { if (substr($headers[$i], 0, 1) == " ") { # Continuation from previous header (runon to next line) $j=$i-1; while (!isset($headers[$j]) && $j>0) $j--; - $headers[$j] .= "\n".ltrim($headers[$i]); + $headers[$j] .= " ".ltrim($headers[$i]); unset($headers[$i]); } elseif (strlen($headers[$i]) == 0) { unset($headers[$i]); diff --git a/include/class.thread.php b/include/class.thread.php index ef1d24ed8fdd15dc79d9e0013aadfca541ffc816..0fca591d9fbfadc2c07a09ecb6953102aff00d36 100644 --- a/include/class.thread.php +++ b/include/class.thread.php @@ -204,6 +204,19 @@ class Thread { && $thread->getId() )?$thread:null; } + + function getVar($name) { + switch ($name) { + case 'original': + return Message::firstByTicketId($this->ticket->getId()) + ->getBody(); + break; + case 'last_message': + case 'lastmessage': + return $this->ticket->getLastMessage()->getBody(); + break; + } + } } @@ -320,6 +333,25 @@ Class ThreadEntry { return $this->ht['email_mid']; } + function getEmailHeaders() { + require_once(INCLUDE_DIR.'class.mailparse.php'); + + $sql = 'SELECT headers FROM '.TICKET_EMAIL_INFO_TABLE + .' WHERE message_id='.$this->getId(); + $headers = db_result(db_query($sql)); + return Mail_Parse::splitHeaders($headers); + } + + function getEmailReferences() { + if (!isset($this->_references)) { + $this->_references = $this->getEmailMessageId(); + $headers = self::getEmailHeaders(); + if (isset($headers['References'])) + $this->_references .= " ".$headers['References']; + } + return $this->_references; + } + function getTicket() { if(!$this->ticket && $this->getTicketId()) @@ -849,10 +881,18 @@ class Message extends ThreadEntry { } function lastByTicketId($ticketId) { + return self::byTicketId($ticketId); + } + + function firstByTicketId($ticketId) { + return self::byTicketId($ticketId, false); + } + + function byTicketId($ticketId, $last=true) { $sql=' SELECT thread.id FROM '.TICKET_THREAD_TABLE.' thread ' .' WHERE thread_type=\'M\' AND thread.ticket_id = '.db_input($ticketId) - .' ORDER BY thread.id DESC LIMIT 1'; + .sprintf(' ORDER BY thread.id %s LIMIT 1', $last ? 'DESC' : 'ASC'); if (($res = db_query($sql)) && ($id = db_result($res))) return Message::lookup($id); diff --git a/include/class.ticket.php b/include/class.ticket.php index 545fb1691b7cf3f0874eb3bd492ab9fd3094a7f1..8afb80f38fc10098fc729dff987152221d1e7508 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -329,6 +329,7 @@ class Ticket { } function getUpdateInfo() { + global $cfg; $info=array('phone' => $this->getPhone(), 'phone_ext' => $this->getPhoneExt(), @@ -336,7 +337,10 @@ class Ticket { 'topicId' => $this->getTopicId(), 'priorityId' => $this->getPriorityId(), 'slaId' => $this->getSLAId(), - 'duedate' => $this->getDueDate()?(Format::userdate('m/d/Y', Misc::db2gmtime($this->getDueDate()))):'', + 'duedate' => $this->getDueDate() + ? Format::userdate($cfg->getDateFormat(), + Misc::db2gmtime($this->getDueDate())) + :'', 'time' => $this->getDueDate()?(Format::userdate('G:i', Misc::db2gmtime($this->getDueDate()))):'', ); @@ -762,7 +766,9 @@ class Ticket { if(!$dept || !($email=$dept->getAutoRespEmail())) $email =$cfg->getDefaultEmail(); - $options = array('references'=>$message->getEmailMessageId()); + $options = array( + 'inreplyto'=>$message->getEmailMessageId(), + 'references'=>$message->getEmailReferences()); //Send auto response - if enabled. if($autorespond && $email && $cfg->autoRespONNewTicket() @@ -911,7 +917,9 @@ class Ticket { if (!$message) $message = $this->getLastMessage(); - $options = array('references' => $message->getEmailMessageId()); + $options = array( + 'inreplyto'=>$message->getEmailMessageId(), + 'references'=>$message->getEmailReferences()); $email->sendAutoReply($this->getEmail(), $msg['subj'], $msg['body'], null, $options); } @@ -970,7 +978,9 @@ class Ticket { //Send the alerts. $sentlist=array(); - $options = array('references' => $note->getEmailMessageId()); + $options = array( + 'inreplyto'=>$note->getEmailMessageId(), + 'references'=>$note->getEmailReferences()); foreach( $recipients as $k=>$staff) { if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(), $sentlist)) continue; $alert = str_replace('%{recipient}', $staff->getFirstName(), $msg['body']); @@ -1231,7 +1241,9 @@ class Ticket { $recipients[]= $manager; $sentlist=array(); - $options = array('references' => $note->getEmailMessageId()); + $options = array( + 'inreplyto'=>$note->getEmailMessageId(), + 'references'=>$note->getEmailReferences()); foreach( $recipients as $k=>$staff) { if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(), $sentlist)) continue; $alert = str_replace('%{recipient}', $staff->getFirstName(), $msg['body']); @@ -1328,8 +1340,9 @@ class Ticket { //Strip quoted reply...on emailed replies if(!strcasecmp($origin, 'Email') && $cfg->stripQuotedReply() - && ($tag=$cfg->getReplySeparator()) && strpos($vars['message'], $tag)) - if(list($msg) = split($tag, $vars['message'])) + && ($tag=$cfg->getReplySeparator()) + && strpos($vars['message'], $tag)) + if((list($msg) = explode($tag, $vars['message'], 2)) && trim($msg)) $vars['message'] = $msg; if(isset($vars['ip'])) @@ -1381,7 +1394,9 @@ class Ticket { $recipients[]=$manager; $sentlist=array(); //I know it sucks...but..it works. - $options = array('references'=>$message->getEmailMessageId()); + $options = array( + 'inreplyto'=>$message->getEmailMessageId(), + 'references'=>$message->getEmailReferences()); foreach( $recipients as $k=>$staff) { if(!$staff || !$staff->getEmail() || !$staff->isAvailable() || in_array($staff->getEmail(), $sentlist)) continue; $alert = str_replace('%{recipient}', $staff->getFirstName(), $msg['body']); @@ -1440,7 +1455,10 @@ class Ticket { if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator())) $msg['body'] = "<p style=\"display:none\">$tag<p>".$msg['body']; - $options = array('references' => $response->getEmailMessageId()); + $attachments =($cfg->emailAttachments() && $files)?$response->getAttachments():array(); + $options = array( + 'inreplyto'=>$response->getEmailMessageId(), + 'references'=>$response->getEmailReferences()); $email->sendAutoReply($this->getEmail(), $msg['subj'], $msg['body'], $attachments, $options); } @@ -1498,7 +1516,11 @@ class Ticket { if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator())) $msg['body'] = "<p style=\"display:none\">$tag<p>".$msg['body']; - $options = array('references' => $response->getEmailMessageId()); + //Set attachments if emailing. + $attachments = $cfg->emailAttachments()?$response->getAttachments():array(); + $options = array( + 'inreplyto' => $response->getEmailMessageId(), + 'references' => $response->getEmailReferences()); //TODO: setup 5 param (options... e.g mid trackable on replies) $email->send($this->getEmail(), $msg['subj'], $msg['body'], $attachments, $options); @@ -1611,7 +1633,10 @@ class Ticket { if($cfg->alertDeptManagerONNewNote() && $dept && $dept->getManagerId()) $recipients[]=$dept->getManager(); - $options = array('references' => $note->getEmailMessageId()); + $attachments = $note->getAttachments(); + $options = array( + 'inreplyto'=>$note->getEmailMessageId(), + 'references'=>$note->getEmailReferences()); $sentlist=array(); foreach( $recipients as $k=>$staff) { if(!is_object($staff) diff --git a/include/client/faq-category.inc.php b/include/client/faq-category.inc.php index 4a87159cd3d0057232c659b101f211a801634ce2..11c558615dcd8166716bd3328babd15f5efdb227 100644 --- a/include/client/faq-category.inc.php +++ b/include/client/faq-category.inc.php @@ -12,7 +12,8 @@ $sql='SELECT faq.faq_id, question, count(attach.file_id) as attachments ' .' LEFT JOIN '.ATTACHMENT_TABLE.' attach ON(attach.object_id=faq.faq_id AND attach.type=\'F\' AND attach.inline = 0) ' .' WHERE faq.ispublished=1 AND faq.category_id='.db_input($category->getId()) - .' GROUP BY faq.faq_id'; + .' GROUP BY faq.faq_id ' + .' ORDER BY question'; if(($res=db_query($sql)) && db_num_rows($res)) { echo ' <h2>Frequently Asked Questions</h2> diff --git a/include/client/knowledgebase.inc.php b/include/client/knowledgebase.inc.php index 1d00ca59ca9ca0b5e6e42724986b61ccef4090fc..79bfa6836857a8b3123d030d8dbfd34c34e2da8a 100644 --- a/include/client/knowledgebase.inc.php +++ b/include/client/knowledgebase.inc.php @@ -78,7 +78,7 @@ if($_REQUEST['q'] || $_REQUEST['cid'] || $_REQUEST['topicId']) { //Search. )"; } - $sql.=' GROUP BY faq.faq_id'; + $sql.=' GROUP BY faq.faq_id ORDER BY question'; echo "<div><strong>Search Results</strong></div><div class='clear'></div>"; if(($res=db_query($sql)) && ($num=db_num_rows($res))) { echo '<div id="faq">'.$num.' FAQs matched your search criteria. diff --git a/include/index.html b/include/index.html deleted file mode 100644 index c4c9a988e260cbb934d504315ea7bb67d5a0935b..0000000000000000000000000000000000000000 --- a/include/index.html +++ /dev/null @@ -1,128 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" ?> -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" - "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> -<html version="-//W3C//DTD XHTML 1.1//EN" xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> -<head> -<link rel="stylesheet" type="text/css" href="/s/d16ebb.css" title="Default"/> -<title>xkcd: Monster</title> -<meta http-equiv="X-UA-Compatible" content="IE=edge"/> -<link rel="shortcut icon" href="/s/919f27.ico" type="image/x-icon"/> -<link rel="icon" href="/s/919f27.ico" type="image/x-icon"/> -<link rel="alternate" type="application/atom+xml" title="Atom 1.0" href="/atom.xml"/> -<link rel="alternate" type="application/rss+xml" title="RSS 2.0" href="/rss.xml"/> -<link rel="apple-touch-icon-precomposed" href="/s/d9522a.png" /> -<script type="text/javascript"> - var _gaq = _gaq || []; - _gaq.push(['_setAccount', 'UA-25700708-7']); - _gaq.push(['_setDomainName', 'xkcd.com']); - _gaq.push(['_setAllowLinker', true]); - _gaq.push(['_trackPageview']); - - (function() { - var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; - ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; - var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); - })(); -</script> - -</head> -<body> -<div id="topContainer"> -<div id="topLeft"> -<ul> -<li><a href="/archive">Archive</a></li> -<li><a href="http://what-if.xkcd.com">What If?</a></li> -<li><a href="http://blag.xkcd.com">Blag</a></li> -<li><a href="http://store.xkcd.com/">Store</a></li> -<li><a rel="author" href="/about">About</a></li> -</ul> -</div> -<div id="topRight"> -<div id="masthead"> -<span><a href="/"><img src="http://imgs.xkcd.com/static/terrible_small_logo.png" alt="xkcd.com logo" height="83" width="185"/></a></span> -<span id="slogan">A webcomic of romance,<br/> sarcasm, math, and language.</span> -</div> -<div id="news"> -You can get the Subways comic as a <a href="http://store-xkcd-com.myshopify.com/products/subways">poster</a>! -</div> -</div> -<div id="bgLeft" class="bg box"></div> -<div id="bgRight" class="bg box"></div> -</div> -<div id="middleContainer" class="box"> - -<div id="ctitle">Monster</div> -<ul class="comicNav"> -<li><a href="/1/">|<</a></li> -<li><a rel="prev" href="/1256/" accesskey="p">< Prev</a></li> -<li><a href="http://dynamic.xkcd.com/random/comic/">Random</a></li> -<li><a rel="next" href="#" accesskey="n">Next ></a></li> -<li><a href="/">>|</a></li> -</ul> -<div id="comic"> -<img src="http://imgs.xkcd.com/comics/monster.png" title="It was finally destroyed with a nuclear weapon carrying the destructive energy of the Hiroshima bomb." alt="Monster" /> -</div> -<ul class="comicNav"> -<li><a href="/1/">|<</a></li> -<li><a rel="prev" href="/1256/" accesskey="p">< Prev</a></li> -<li><a href="http://dynamic.xkcd.com/random/comic/">Random</a></li> -<li><a rel="next" href="#" accesskey="n">Next ></a></li> -<li><a href="/">>|</a></li> -</ul> -<br /> -Permanent link to this comic: http://xkcd.com/1257/<br /> -Image URL (for hotlinking/embedding): http://imgs.xkcd.com/comics/monster.png -<div id="transcript" style="display: none"></div> -</div> -<div id="bottom" class="box"> -<img src="http://imgs.xkcd.com/s/a899e84.jpg" width="520" height="100" alt="Selected Comics" usemap="#comicmap"/> -<map id="comicmap" name="comicmap"> -<!-- http://code.google.com/p/chromium/issues/detail?id=108489 Might be MIME dependent. --> -<area shape="rect" coords="0,0,100,100" href="/150/" alt="Grownups"/> -<area shape="rect" coords="104,0,204,100" href="/730/" alt="Circuit Diagram"/> -<area shape="rect" coords="208,0,308,100" href="/162/" alt="Angular Momentum"/> -<area shape="rect" coords="312,0,412,100" href="/688/" alt="Self-Description"/> -<area shape="rect" coords="416,0,520,100" href="/556/" alt="Alternative Energy Revolution"/> -</map> -<div> -Search comic titles and transcripts: -<script type="text/javascript" src="//www.google.com/jsapi"></script> -<script type="text/javascript">google.load('search', '1');google.setOnLoadCallback(function() {google.search.CustomSearchControl.attachAutoCompletion('012652707207066138651:zudjtuwe28q',document.getElementById('q'),'cse-search-box');});</script> -<form action="//www.google.com/cse" id="cse-search-box"> -<div> -<input type="hidden" name="cx" value="012652707207066138651:zudjtuwe28q"/> -<input type="hidden" name="ie" value="UTF-8"/> -<input type="text" name="q" id="q" size="31"/> -<input type="submit" name="sa" value="Search"/> -</div> -</form> -<script type="text/javascript" src="//www.google.com/cse/brand?form=cse-search-box&lang=en"></script> -<a href="/rss.xml">RSS Feed</a> - <a href="/atom.xml">Atom Feed</a> -</div> -<br /> -<div id="comicLinks"> -Comics I enjoy:<br/> - <a href="http://threewordphrase.com/">Three Word Phrase</a>, - <a href="http://oglaf.com/">Oglaf</a> (nsfw), - <a href="http://www.smbc-comics.com/">SMBC</a>, - <a href="http://www.qwantz.com">Dinosaur Comics</a>, - <a href="http://www.asofterworld.com">A Softer World</a>, - <a href="http://buttersafe.com/">Buttersafe</a>, - <a href="http://pbfcomics.com/">Perry Bible Fellowship</a>, - <a href="http://questionablecontent.net/">Questionable Content</a>, - <a href="http://www.buttercupfestival.com/">Buttercup Festival</a> -</div> -<p>Warning: this comic occasionally contains strong language (which may be unsuitable for children), unusual humor (which may be unsuitable for adults), and advanced mathematics (which may be unsuitable for liberal-arts majors).</p> -<div id="footnote">BTC 1NfBXWqseXc9rCBc3Cbbu6HjxYssFUgkH6<br />We did not invent the algorithm. The algorithm consistently finds Jesus. The algorithm killed Jeeves. <br/>The algorithm is banned in China. The algorithm is from Jersey. The algorithm constantly finds Jesus.<br/>This is not the algorithm. This is close.</div> -<div id="licenseText"> -<p> -This work is licensed under a -<a href="http://creativecommons.org/licenses/by-nc/2.5/">Creative Commons Attribution-NonCommercial 2.5 License</a>. -</p><p> -This means you're free to copy and share these comics (but not to sell them). <a rel="license" href="/license.html">More details</a>.</p> -</div> -</div> -</body> -<!-- Layout by Ian Clasbey, davean, and chromakode --> -</html> - diff --git a/include/staff/faq-categories.inc.php b/include/staff/faq-categories.inc.php index f39ad10c026a2f70d8b60700ed1abd09c2c753ca..46095c2b54ff659c12f9133c83a8b82fd519cf88 100644 --- a/include/staff/faq-categories.inc.php +++ b/include/staff/faq-categories.inc.php @@ -56,7 +56,7 @@ if(!defined('OSTSTAFFINC') || !$thisstaff) die('Access Denied'); if($_REQUEST['q'] || $_REQUEST['cid'] || $_REQUEST['topicId']) { //Search. $sql='SELECT faq.faq_id, question, ispublished, count(attach.file_id) as attachments, count(ft.topic_id) as topics ' .' FROM '.FAQ_TABLE.' faq ' - .' LEFT JOIN '.FAQ_CATEGORY.' cat ON(cat.category_id=faq.category_id) ' + .' LEFT JOIN '.FAQ_CATEGORY_TABLE.' cat ON(cat.category_id=faq.category_id) ' .' LEFT JOIN '.FAQ_TOPIC_TABLE.' ft ON(ft.faq_id=faq.faq_id) ' .' LEFT JOIN '.ATTACHMENT_TABLE.' attach ON(attach.object_id=faq.faq_id AND attach.type=\'F\' AND attach.inline = 0) ' @@ -77,7 +77,7 @@ if($_REQUEST['q'] || $_REQUEST['cid'] || $_REQUEST['topicId']) { //Search. )"; } - $sql.=' GROUP BY faq.faq_id'; + $sql.=' GROUP BY faq.faq_id ORDER BY question'; echo "<div><strong>Search Results</strong></div><div class='clear'></div>"; if(($res=db_query($sql)) && db_num_rows($res)) { diff --git a/include/staff/faq-category.inc.php b/include/staff/faq-category.inc.php index 39f70e6884139c738ee19330dfe3f1eeb9a97eee..6885f2f58fb93414d0a23226f7e6646ab2ec0e86 100644 --- a/include/staff/faq-category.inc.php +++ b/include/staff/faq-category.inc.php @@ -34,7 +34,7 @@ $sql='SELECT faq.faq_id, question, ispublished, count(attach.file_id) as attachm .' LEFT JOIN '.ATTACHMENT_TABLE.' attach ON(attach.object_id=faq.faq_id AND attach.type=\'F\' AND attach.inline = 0) ' .' WHERE faq.category_id='.db_input($category->getId()) - .' GROUP BY faq.faq_id'; + .' GROUP BY faq.faq_id ORDER BY question'; if(($res=db_query($sql)) && db_num_rows($res)) { echo '<div id="faq"> <ol>'; diff --git a/open.php b/open.php index 468f700b562289c56cda08d69c465acec420a63c..1821bf2c75bed559b9557cc326de349a4cd7514c 100644 --- a/open.php +++ b/open.php @@ -26,7 +26,7 @@ if($_POST): } elseif($cfg->isCaptchaEnabled()) { if(!$_POST['captcha']) $errors['captcha']='Enter text shown on the image'; - elseif(strcmp($_SESSION['captcha'],md5($_POST['captcha']))) + elseif(strcmp($_SESSION['captcha'], md5(strtoupper($_POST['captcha'])))) $errors['captcha']='Invalid - try again!'; } diff --git a/scp/images/kb_category_bg.png b/scp/images/kb_category_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..174d675f4c491060a413232bdef7e0e9298df60e Binary files /dev/null and b/scp/images/kb_category_bg.png differ diff --git a/scp/js/scp.js b/scp/js/scp.js index 961e9e9326516fff7de9bdf98a88ff7b53a4d77b..c0268bb385b7d2994118f96bc5851bd042aa53ed 100644 --- a/scp/js/scp.js +++ b/scp/js/scp.js @@ -276,12 +276,29 @@ $(document).ready(function(){ } /* Datepicker */ - $('.dp').datepicker({ - numberOfMonths: 2, - showButtonPanel: true, - buttonImage: './images/cal.png', - showOn:'both' - }); + getConfig().then(function(c) { + var df = c.date_format||'m/d/Y', + translation = { + 'd':'dd', + 'j':'d', + 'z':'o', + 'm':'mm', + 'F':'MM', + 'n':'m', + 'Y':'yy' + }; + // Change PHP formats to datepicker ones + $.each(translation, function(php, jqdp) { + df = df.replace(php, jqdp); + }); + $('.dp').datepicker({ + numberOfMonths: 2, + showButtonPanel: true, + buttonImage: './images/cal.png', + showOn:'both', + dateFormat: df, + }); + }); /* Typeahead tickets lookup */ $('#basic-ticket-search').typeahead({