diff --git a/WHATSNEW.md b/WHATSNEW.md index 63abe0a617c3c5c558eda6c26dbc78c2d3dfebde..8cac25f814492d4a1790a948bbf959165d536223 100644 --- a/WHATSNEW.md +++ b/WHATSNEW.md @@ -1,3 +1,8 @@ +New stuff in 1.7-rc2 +==================== + * Bug fixes from rc1 + * Nested help topics support + New stuff in 1.7-rc1 ==================== * Upgrade support for osTicket 1.6-rc1 and later diff --git a/include/PasswordHash.php b/include/PasswordHash.php index a30f41137c1334522168deb428869d60c7ead7b2..b5b8efcadbb70123ddedc61ae673c15342f94955 100644 --- a/include/PasswordHash.php +++ b/include/PasswordHash.php @@ -48,7 +48,7 @@ class PasswordHash { function get_random_bytes($count) { $output = ''; - if (is_readable('/dev/urandom') && + if (@is_readable('/dev/urandom') && ($fh = @fopen('/dev/urandom', 'rb'))) { $output = fread($fh, $count); fclose($fh); diff --git a/include/ajax.content.php b/include/ajax.content.php index e77f45d23014d2176b24500d31e26c1ab263133f..e97e96a5c3d1d894f5d7cacfad702c97ced25cf6 100644 --- a/include/ajax.content.php +++ b/include/ajax.content.php @@ -36,46 +36,48 @@ class ContentAjaxAPI extends AjaxController { function ticket_variables() { - $content='<div style="width:600px;"> - <h2>Ticket Variables</h2> - Please note that non-base variables depends on the context of use. - <br> - <table width="100%" border="0" cellspacing=1 cellpadding=2> - <tr><td width="50%" valign="top"><b>Base Variables</b></td><td><b>Other Variables</b></td></tr> - <tr> - <td width="50%" valign="top"> - <table width="100%" border="0" cellspacing=1 cellpadding=1> - <tr><td width="100">%id</td><td>Ticket ID (internal ID)</td></tr> - <tr><td>%ticket</td><td>Ticket number (external ID)</td></tr> - <tr><td>%email</td><td>Email address</td></tr> - <tr><td>%name</td><td>Full name</td></tr> - <tr><td>%subject</td><td>Subject</td></tr> - <tr><td>%topic</td><td>Help topic (web only)</td></tr> - <tr><td>%phone</td><td>Phone number | ext</td></tr> - <tr><td>%status</td><td>Status</td></tr> - <tr><td>%priority</td><td>Priority</td></tr> - <tr><td>%dept</td><td>Department</td></tr> - <tr><td>%assigned</td><td>Assigned staff or team (if any)</td></tr> - <tr><td>%createdate</td><td>Date created</td></tr> - <tr><td>%duedate</td><td>Due date</td></tr> - <tr><td>%closedate</td><td>Date closed</td></tr> - </table> - </td> - <td valign="top"> - <table width="100%" border="0" cellspacing=1 cellpadding=1> - <tr><td width="100">%message</td><td>Message (incoming)</td></tr> - <tr><td>%response</td><td>Response (outgoing)</td></tr> - <tr><td>%note</td><td>Internal/transfer note</td></tr> - <tr><td>%staff</td><td>Staff\'s name (alert/notices)</td></tr> - <tr><td>%assignee</td><td>Assigned staff</td></tr> - <tr><td>%assigner</td><td>Staff assigning the ticket</td></tr> - <tr><td>%url</td><td>osTicket\'s base url (FQDN)</td></tr> - - </table> - </td> - </tr> + $content=' +<div style="width:600px;"> + <h2>Ticket Variables</h2> + Please note that non-base variables depends on the context of use. + <br/> + <table width="100%" border="0" cellspacing=1 cellpadding=2> + <tr><td width="50%" valign="top"><b>Base Variables</b></td><td><b>Other Variables</b></td></tr> + <tr> + <td width="50%" valign="top"> + <table width="100%" border="0" cellspacing=1 cellpadding=1> + <tr><td width="100">%id</td><td>Ticket ID (internal ID)</td></tr> + <tr><td>%ticket</td><td>Ticket number (external ID)</td></tr> + <tr><td>%email</td><td>Email address</td></tr> + <tr><td>%name</td><td>Full name</td></tr> + <tr><td>%subject</td><td>Subject</td></tr> + <tr><td>%topic</td><td>Help topic (web only)</td></tr> + <tr><td>%phone</td><td>Phone number | ext</td></tr> + <tr><td>%status</td><td>Status</td></tr> + <tr><td>%priority</td><td>Priority</td></tr> + <tr><td>%dept</td><td>Department</td></tr> + <tr><td>%assigned</td><td>Assigned staff or team (if any)</td></tr> + <tr><td>%createdate</td><td>Date created</td></tr> + <tr><td>%duedate</td><td>Due date</td></tr> + <tr><td>%closedate</td><td>Date closed</td></tr> + </table> + </td> + <td valign="top"> + <table width="100%" border="0" cellspacing=1 cellpadding=1> + <tr><td width="100">%message</td><td>Message (incoming)</td></tr> + <tr><td>%response</td><td>Response (outgoing)</td></tr> + <tr><td>%note</td><td>Internal/transfer note</td></tr> + <tr><td>%staff</td><td>Staff\'s name (alert/notices)</td></tr> + <tr><td>%assignee</td><td>Assigned staff</td></tr> + <tr><td>%assigner</td><td>Staff assigning the ticket</td></tr> + <tr><td>%url</td><td>osTicket\'s base url (FQDN)</td></tr> + <tr><td>%auth</td><td>Client authentication token</td></tr> + <tr><td>%clientlink</td><td>Client auto-login link</td></tr> </table> - </div>'; + </td> + </tr> + </table> +</div>'; return $content; } diff --git a/include/class.canned.php b/include/class.canned.php index 399b490d3a9cf3c1832fc4892c66ccd4aed06f6f..52a487529c368bc158777359260d36b808dc1d90 100644 --- a/include/class.canned.php +++ b/include/class.canned.php @@ -270,14 +270,14 @@ class Canned { if(db_query($sql)) return true; - $errors['err']='Unable to update canned reply.'; + $errors['err']='Unable to update canned response.'; } else { $sql='INSERT INTO '.CANNED_TABLE.' SET '.$sql.',created=NOW()'; if(db_query($sql) && ($id=db_insert_id())) return $id; - $errors['err']='Unable to create the canned reply. Internal error'; + $errors['err']='Unable to create the canned response. Internal error'; } return false; diff --git a/include/class.client.php b/include/class.client.php index 8786c0e19cdb9719a86a556b3ce26795ac5ce62f..5c5f3035ee6b485d6862950b35924f00e0f90b97 100644 --- a/include/class.client.php +++ b/include/class.client.php @@ -133,5 +133,73 @@ class Client { function lookupByEmail($email) { return (($id=self::getLastTicketIdByEmail($email)))?self::lookup($id, $email):null; } + + /* static */ function tryLogin($ticketID, $email, $auth=null) { + global $ost; + $cfg = $ost->getConfig(); + + # Only consider auth token for GET requests, and for GET requests, + # REQUIRE the auth token + $auto_login = $_SERVER['REQUEST_METHOD'] == 'GET'; + + //Check time for last max failed login attempt strike. + $loginmsg='Invalid login'; + # XXX: SECURITY: Max attempts is enforced client-side via the PHP + # session cookie. + if($_SESSION['_client']['laststrike']) { + if((time()-$_SESSION['_client']['laststrike'])<$cfg->getClientLoginTimeout()) { + $loginmsg='Excessive failed login attempts'; + $errors['err']='You\'ve reached maximum failed login attempts allowed. Try again later or <a href="open.php">open a new ticket</a>'; + }else{ //Timeout is over. + //Reset the counter for next round of attempts after the timeout. + $_SESSION['_client']['laststrike']=null; + $_SESSION['_client']['strikes']=0; + } + } + //See if we can fetch local ticket id associated with the ID given + if(!$errors && is_numeric($ticketID) && Validator::is_email($email) && ($ticket=Ticket::lookupByExtId($ticketID))) { + //At this point we know the ticket is valid. + //TODO: 1) Check how old the ticket is...3 months max?? 2) Must be the latest 5 tickets?? + //Check the email given. + # Require auth token for automatic logins + if (!$auto_login || $auth === $ticket->getAuthToken()) { + if($ticket->getId() && strcasecmp($ticket->getEmail(),$email)==0){ + //valid match...create session goodies for the client. + $user = new ClientSession($email,$ticket->getId()); + $_SESSION['_client']=array(); //clear. + $_SESSION['_client']['userID'] =$ticket->getEmail(); //Email + $_SESSION['_client']['key'] =$ticket->getExtId(); //Ticket ID --acts as password when used with email. See above. + $_SESSION['_client']['token'] =$user->getSessionToken(); + $_SESSION['TZ_OFFSET']=$cfg->getTZoffset(); + $_SESSION['TZ_DST']=$cfg->observeDaylightSaving(); + //Log login info... + $msg=sprintf("%s/%s logged in [%s]",$ticket->getEmail(),$ticket->getExtId(),$_SERVER['REMOTE_ADDR']); + $ost->logDebug('User login', $msg); + //Redirect tickets.php + session_write_close(); + session_regenerate_id(); + @header("Location: tickets.php?id=".$ticket->getExtId()); + require_once('tickets.php'); //Just incase. of header already sent error. + exit; + } + } + } + //If we get to this point we know the login failed. + $_SESSION['_client']['strikes']+=1; + if(!$errors && $_SESSION['_client']['strikes']>$cfg->getClientMaxLogins()) { + $loginmsg='Access Denied'; + $errors['err']='Forgot your login info? Please <a href="open.php">open a new ticket</a>.'; + $_SESSION['_client']['laststrike']=time(); + $alert='Excessive login attempts by a client?'."\n". + 'Email: '.$_POST['lemail']."\n".'Ticket#: '.$_POST['lticket']."\n". + 'IP: '.$_SERVER['REMOTE_ADDR']."\n".'Time:'.date('M j, Y, g:i a T')."\n\n". + 'Attempts #'.$_SESSION['_client']['strikes']; + $ost->logError('Excessive login attempts (client)', $alert, ($cfg->alertONLoginError())); + }elseif($_SESSION['_client']['strikes']%2==0){ //Log every other failed login attempt as a warning. + $alert='Email: '.$_POST['lemail']."\n".'Ticket #: '.$_POST['lticket']."\n".'IP: '.$_SERVER['REMOTE_ADDR']. + "\n".'TIME: '.date('M j, Y, g:i a T')."\n\n".'Attempts #'.$_SESSION['_client']['strikes']; + $ost->logWarning('Failed login attempt (client)', $alert); + } + } } ?> diff --git a/include/class.file.php b/include/class.file.php index c27c9fdd07b5aa7477336c009af2fdb75605233c..e4012bb4b9f7665438cc28fbc3818162f48579a7 100644 --- a/include/class.file.php +++ b/include/class.file.php @@ -108,7 +108,7 @@ class AttachmentFile { function display() { - header('Content-type: '.$this->getType()?$this->getType():'application/octet-stream'); + header('Content-Type: '.($this->getType()?$this->getType():'application/octet-stream')); header('Content-Length: '.$this->getSize()); echo $this->getData(); exit(); @@ -120,9 +120,7 @@ class AttachmentFile { header('Expires: 0'); header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); header('Cache-Control: public'); - header('Content-Type: application/octet-stream'); - - //header('Content-Type: '.$this->getType()?$this->getType():'application/octet-stream'); + header('Content-Type: '.($this->getType()?$this->getType():'application/octet-stream')); $filename=basename($this->getName()); $user_agent = strtolower ($_SERVER['HTTP_USER_AGENT']); diff --git a/include/class.filter.php b/include/class.filter.php index bc9b0847132aff00f794dd74abf6b2ec71379e35..b6c81b42b60d0d4b8c67d3f7b33b1138695fa1e7 100644 --- a/include/class.filter.php +++ b/include/class.filter.php @@ -236,9 +236,9 @@ class Filter { ); $match = false; # Respect configured filter email-id - if ($email['emailId'] && $this->getEmailId() - && $this->getEmailId() != $email['emailId']) + if ($this->getEmailId() && $this->getEmailId() != $email['emailId']) return false; + foreach ($this->getRules() as $rule) { list($func, $pos, $neg) = $how[$rule['h']]; # TODO: convert $what and $rule['v'] to mb_strtoupper and do diff --git a/include/class.nav.php b/include/class.nav.php index a5ce84cb07affcec52e9ecec88460b790ece4fb6..0d5fddcbf3a48772db052a71363bfe0fb3b64ad5 100644 --- a/include/class.nav.php +++ b/include/class.nav.php @@ -123,12 +123,12 @@ class StaffNav { $subnav[]=array('desc'=>'My Profile','href'=>'profile.php','iconclass'=>'users'); break; case 'kbase': - $subnav[]=array('desc'=>'Knowledgebase','href'=>'kb.php', 'urls'=>array('faq.php'), 'iconclass'=>'kb'); + $subnav[]=array('desc'=>'FAQs','href'=>'kb.php', 'urls'=>array('faq.php'), 'iconclass'=>'kb'); if($staff) { if($staff->canManageFAQ()) - $subnav[]=array('desc'=>'Categories','href'=>'categories.php','iconclass'=>'kb-categories'); + $subnav[]=array('desc'=>'Categories','href'=>'categories.php','iconclass'=>'faq-categories'); if($staff->canManageCannedResponses()) - $subnav[]=array('desc'=>'Canned Replies','href'=>'canned.php','iconclass'=>'canned'); + $subnav[]=array('desc'=>'Canned Responses','href'=>'canned.php','iconclass'=>'canned'); } break; } diff --git a/include/class.staff.php b/include/class.staff.php index b607f6eac145aa7e7c13547218d936f7662ceb8a..83c9ce2e58da2905c4716a28ff159dfb870074b1 100644 --- a/include/class.staff.php +++ b/include/class.staff.php @@ -82,20 +82,28 @@ class Staff { } /*compares user password*/ - function check_passwd($password) { + function check_passwd($password, $autoupdate=true) { /*bcrypt based password match*/ if(Passwd::cmp($password, $this->getPasswd())) return true; - /*Fall back to MD5 && force a password reset if it matches*/ - if(strlen($this->getPasswd()) && !strcmp($this->getPasswd(), MD5($password))) { + //Fall back to MD5 + if(!$password || strcmp($this->getPasswd(), MD5($password))) + return false; + + //Password is a MD5 hash: rehash it (if enabled) otherwise force passwd change. + $sql='UPDATE '.STAFF_TABLE.' SET passwd='.db_input(Passwd::hash($password)) + .' WHERE staff_id='.db_input($this->getId()); + + if(!$autoupdate || !db_query($sql)) $this->forcePasswdRest(); - return true; - } + return true; + } - return false; + function cmp_passwd($password) { + return $this->check_passwd($password, false); } function forcePasswdRest() { @@ -406,8 +414,10 @@ class Staff { if(!$vars['cpasswd']) $errors['cpasswd']='Current password required'; - elseif(!$this->check_passwd($vars['cpasswd'])) + elseif(!$this->cmp_passwd($vars['cpasswd'])) $errors['cpasswd']='Invalid current password!'; + elseif(!strcasecmp($vars['passwd1'], $vars['cpasswd'])) + $errors['passwd1']='New password MUST be different from the current password!'; } if(!$vars['timezone_id']) diff --git a/include/class.ticket.php b/include/class.ticket.php index da45d51935322d606f6e6b60b102c6c63ea363b8..8b9581d532a1e94c8076d3eca0b82bcfe88d7d0e 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -74,7 +74,7 @@ class Ticket{ //TODO: delete helptopic field in ticket table. - $sql='SELECT ticket.*, topic.topic as helptopic, lock_id, dept_name, priority_desc ' + $sql='SELECT ticket.*, lock_id, dept_name, priority_desc ' .' ,count(attach.attach_id) as attachments ' .' ,count(DISTINCT message.id) as messages ' .' ,count(DISTINCT response.id) as responses ' @@ -83,8 +83,6 @@ class Ticket{ .' LEFT JOIN '.DEPT_TABLE.' dept ON (ticket.dept_id=dept.dept_id) ' .' LEFT JOIN '.TICKET_PRIORITY_TABLE.' pri ON (' .'ticket.priority_id=pri.priority_id) ' - .' LEFT JOIN '.TOPIC_TABLE.' topic ON (' - .'ticket.topic_id=topic.topic_id) ' .' LEFT JOIN '.TICKET_LOCK_TABLE.' tlock ON (' .'ticket.ticket_id=tlock.ticket_id AND tlock.expire>NOW()) ' .' LEFT JOIN '.TICKET_ATTACHMENT_TABLE.' attach ON (' @@ -128,7 +126,6 @@ class Ticket{ $this->dept_name = $this->ht['dept_name']; $this->sla_id = $this->ht['sla_id']; $this->topic_id = $this->ht['topic_id']; - $this->helptopic = $this->ht['helptopic']; $this->subject = $this->ht['subject']; $this->overdue = $this->ht['isoverdue']; @@ -213,6 +210,11 @@ class Ticket{ return $this->email; } + function getAuthToken() { + # XXX: Support variable email address (for CCs) + return md5($this->getId() . $this->getEmail() . SECRET_SALT); + } + function getName(){ return $this->fullname; } @@ -227,7 +229,7 @@ class Ticket{ if(!$this->helpTopic && ($topic=$this->getTopic())) $this->helpTopic = $topic->getName(); - return $this->helptopic; + return $this->helpTopic; } function getCreateDate(){ @@ -417,14 +419,14 @@ class Ticket{ return $assignees; } - function getTopicId(){ + function getTopicId() { return $this->topic_id; } function getTopic() { if(!$this->topic && $this->getTopicId()) - $this->topic = Topic::lookup($this->getTopicId); + $this->topic = Topic::lookup($this->getTopicId()); return $this->topic; } @@ -976,7 +978,6 @@ class Ticket{ $email->send($this->getEmail(),$subj,$body); } } - } function onAssign($note, $alert=true) { @@ -1105,7 +1106,8 @@ class Ticket{ $search = array('/%id/','/%ticket/','/%email/','/%name/','/%subject/','/%topic/','/%phone/','/%status/','/%priority/', - '/%dept/','/%assigned_staff/','/%createdate/','/%duedate/','/%closedate/','/%url/'); + '/%dept/','/%assigned_staff/','/%createdate/','/%duedate/','/%closedate/','/%url/', + '/%auth/', '/%clientlink/'); $replace = array($this->getId(), $this->getExtId(), $this->getEmail(), @@ -1120,13 +1122,15 @@ class Ticket{ Format::db_daydatetime($this->getCreateDate()), Format::db_daydatetime($this->getDueDate()), Format::db_daydatetime($this->getCloseDate()), - $cfg->getBaseUrl()); - return preg_replace($search,$replace,$text); + $cfg->getBaseUrl(), + $this->getAuthToken(), + '%url/view.php?t=%ticket&e=%email&a=%auth'); + while ($text != ($T = preg_replace($search,$replace,$text))) { + $text = $T; + } + return $text; } - - - function markUnAnswered() { return (!$this->isAnswered() || $this->setAnsweredState(0)); } @@ -1138,7 +1142,6 @@ class Ticket{ function markOverdue($whine=true) { global $cfg; - if($this->isOverdue()) return true; @@ -1297,14 +1300,11 @@ class Ticket{ if(!$this->getId()) return 0; - - //Strip quoted reply...on emailed replies if(!strcasecmp($source, 'Email') && $cfg->stripQuotedReply() - && ($tag=$cfg->getReplySeparator()) && strpos($msg, $tag)) - list($msg)=split($tag, $msg); - + && ($tag=$cfg->getReplySeparator()) && strpos($message, $tag)) + list($message)=split($tag, $message); # XXX: Refuse auto-response messages? (via email) XXX: No - but kill our auto-responder. diff --git a/include/class.topic.php b/include/class.topic.php index 40ab8096bc1831b821ea37754b18e3d1a1a22e3e..c8e6a9e72b9a8d26da82e97f48cfc9f25e97d51c 100644 --- a/include/class.topic.php +++ b/include/class.topic.php @@ -16,11 +16,11 @@ class Topic { var $id; - var $topic; var $ht; + var $parent; - function Topic($id){ + function Topic($id) { $this->id=0; $this->load($id); } @@ -30,12 +30,16 @@ class Topic { if(!$id && !($id=$this->getId())) return false; - $sql='SELECT * FROM '.TOPIC_TABLE - .' WHERE topic_id='.db_input($id); + $sql='SELECT ht.* ' + .', IF(ht.topic_pid IS NULL, ht.topic, CONCAT_WS(" / ", ht2.topic, ht.topic)) as name ' + .' FROM '.TOPIC_TABLE.' ht ' + .' LEFT JOIN '.TOPIC_TABLE.' ht2 ON(ht2.topic_id=ht.topic_pid) ' + .' WHERE ht.topic_id='.db_input($id); + if(!($res=db_query($sql)) || !db_num_rows($res)) return false; - $this->ht=db_fetch_array($res); + $this->ht = db_fetch_array($res); $this->id=$this->ht['topic_id']; return true; @@ -45,31 +49,42 @@ class Topic { return $this->load(); } - function getId(){ + function getId() { return $this->id; } - - function getName(){ - return $this->ht['topic']; + + function getPid() { + return $this->ht['topic_pid']; + } + + function getParent() { + if(!$this->parent && $this->getPid()) + $this->parent = self::lookup($this->getPid()); + + return $this->parent; + } + + function getName() { + return $this->ht['name']; } - function getDeptId(){ + function getDeptId() { return $this->ht['dept_id']; } - function getSLAId(){ + function getSLAId() { return $this->ht['sla_id']; } - function getPriorityId(){ + function getPriorityId() { return $this->ht['priority_id']; } - function getStaffId(){ + function getStaffId() { return $this->ht['staff_id']; } - function getTeamId(){ + function getTeamId() { return $this->ht['team_id']; } @@ -81,11 +96,11 @@ class Topic { return ($this->ht['isactive']); } - function isActive(){ + function isActive() { return $this->isEnabled(); } - function isPublic(){ + function isPublic() { return ($this->ht['ispublic']); } @@ -97,18 +112,20 @@ class Topic { return $this->getHashtable(); } - function update($vars,&$errors) { + function update($vars, &$errors) { - if($this->save($this->getId(),$vars,$errors)){ - $this->reload(); - return true; - } - return false; + if(!$this->save($this->getId(), $vars, $errors)) + return false; + + $this->reload(); + return true; } - function delete(){ + function delete() { + $sql='DELETE FROM '.TOPIC_TABLE.' WHERE topic_id='.db_input($this->getId()).' LIMIT 1'; - if(db_query($sql) && ($num=db_affected_rows())){ + if(db_query($sql) && ($num=db_affected_rows())) { + db_query('UPDATE '.TOPIC_TABLE.' SET topic_pid=0 WHERE topic_pid='.db_input($this->getId())); db_query('UPDATE '.TICKET_TABLE.' SET topic_id=0 WHERE topic_id='.db_input($this->getId())); db_query('DELETE FROM '.FAQ_TOPIC_TABLE.' WHERE topic_id='.db_input($this->getId())); } @@ -117,19 +134,24 @@ class Topic { } /*** Static functions ***/ function create($vars,&$errors) { - return self::save(0,$vars,$errors); + return self::save(0, $vars, $errors); } function getHelpTopics($publicOnly=false) { $topics=array(); - $sql='SELECT topic_id, topic FROM '.TOPIC_TABLE - .' WHERE isactive=1'; + $sql='SELECT ht.topic_id' + .', IF(ht2.topic_pid IS NULL, ht.topic, CONCAT_WS(" / ", ht2.topic, ht.topic)) as name ' + .' FROM '.TOPIC_TABLE. ' ht ' + .' LEFT JOIN '.TOPIC_TABLE.' ht2 ON(ht2.topic_id=ht.topic_pid) ' + .' WHERE ht.isactive=1'; + if($publicOnly) - $sql.=' AND ispublic=1'; - $sql.=' ORDER BY topic'; + $sql.=' AND ht.ispublic=1'; + + $sql.=' ORDER BY name'; if(($res=db_query($sql)) && db_num_rows($res)) - while(list($id,$name)=db_fetch_row($res)) + while(list($id, $name)=db_fetch_row($res)) $topics[$id]=$name; return $topics; @@ -139,8 +161,7 @@ class Topic { return self::getHelpTopics(true); } - - function getIdByName($topic){ + function getIdByName($topic) { $sql='SELECT topic_id FROM '.TOPIC_TABLE.' WHERE topic='.db_input($topic); if(($res=db_query($sql)) && db_num_rows($res)) list($id)=db_fetch_row($res); @@ -148,11 +169,11 @@ class Topic { return $id; } - function lookup($id){ + function lookup($id) { return ($id && is_numeric($id) && ($t= new Topic($id)) && $t->getId()==$id)?$t:null; } - function save($id,$vars,&$errors) { + function save($id, $vars, &$errors) { $vars['topic']=Format::striptags(trim($vars['topic'])); @@ -174,22 +195,24 @@ class Topic { if($errors) return false; - $sql=' updated=NOW(),topic='.db_input($vars['topic']). - ',dept_id='.db_input($vars['dept_id']). - ',priority_id='.db_input($vars['priority_id']). - ',sla_id='.db_input($vars['sla_id']). - ',isactive='.db_input($vars['isactive']). - ',ispublic='.db_input($vars['ispublic']). - ',noautoresp='.db_input(isset($vars['noautoresp'])?1:0). - ',notes='.db_input($vars['notes']); + $sql=' updated=NOW() ' + .',topic='.db_input($vars['topic']) + .',topic_pid='.db_input($vars['pid']) + .',dept_id='.db_input($vars['dept_id']) + .',priority_id='.db_input($vars['priority_id']) + .',sla_id='.db_input($vars['sla_id']) + .',isactive='.db_input($vars['isactive']) + .',ispublic='.db_input($vars['ispublic']) + .',noautoresp='.db_input(isset($vars['noautoresp'])?1:0) + .',notes='.db_input($vars['notes']); //Auto assign ID is overloaded... if($vars['assign'] && $vars['assign'][0]=='s') - $sql.=',team_id=0,staff_id='.db_input(preg_replace("/[^0-9]/", "",$vars['assign'])); + $sql.=',team_id=0, staff_id='.db_input(preg_replace("/[^0-9]/", "", $vars['assign'])); elseif($vars['assign'] && $vars['assign'][0]=='t') - $sql.=',staff_id=0,team_id='.db_input(preg_replace("/[^0-9]/", "",$vars['assign'])); + $sql.=',staff_id=0, team_id='.db_input(preg_replace("/[^0-9]/", "", $vars['assign'])); else - $sql.=',staff_id=0,team_id=0 '; //no auto-assignment! + $sql.=',staff_id=0, team_id=0 '; //no auto-assignment! if($id) { $sql='UPDATE '.TOPIC_TABLE.' SET '.$sql.' WHERE topic_id='.db_input($id); @@ -197,7 +220,7 @@ class Topic { return true; $errors['err']='Unable to update topic. Internal error occurred'; - }else{ + } else { $sql='INSERT INTO '.TOPIC_TABLE.' SET '.$sql.',created=NOW()'; if(db_query($sql) && ($id=db_insert_id())) return $id; diff --git a/include/client/faq.inc.php b/include/client/faq.inc.php index 65f770e9673083d2474d2a9eb260771492541d41..d0be23c9fd41b1e9d9572bdcdd854623e5e51a0c 100644 --- a/include/client/faq.inc.php +++ b/include/client/faq.inc.php @@ -7,7 +7,7 @@ $category=$faq->getCategory(); <h1>Frequently Asked Questions</h1> <div id="breadcrumbs"> <a href="index.php">All Categories</a> - » <a href="faq.php?cid=<? echo $category->getId(); ?>"><? echo $category->getName(); ?></a> + » <a href="faq.php?cid=<?php echo $category->getId(); ?>"><?php echo $category->getName(); ?></a> </div> <div style="width:700;padding-top:2px; float:left;"> <strong style="font-size:16px;"><?php echo $faq->getQuestion() ?></strong> @@ -21,8 +21,8 @@ $category=$faq->getCategory(); <?php if($faq->getNumAttachments()) { ?> <div><span class="faded"><b>Attachments:</b></span> <?php echo $faq->getAttachmentsLinks(); ?></div> -<? -}?> +<?php +} ?> <div class="article-meta"><span class="faded"><b>Help Topics:</b></span> <?php echo ($topics=$faq->getHelpTopics())?implode(', ',$topics):' '; ?> diff --git a/include/client/tickets.inc.php b/include/client/tickets.inc.php index 7719ec74a7ef2a58babcae7112a0d2cb05caa92e..1ee781a908a4ba275ca734f1d62be5d684198b9a 100644 --- a/include/client/tickets.inc.php +++ b/include/client/tickets.inc.php @@ -147,12 +147,12 @@ $negorder=$order=='DESC'?'ASC':'DESC'; //Negate the sorting <a class="Icon <?php echo strtolower($row['source']); ?>Ticket" title="<?php echo $row['email']; ?>" href="tickets.php?id=<?php echo $row['ticketID']; ?>"><?php echo $ticketID; ?></a> </td> - <td> <?=Format::db_date($row['created'])?></td> - <td> <?=ucfirst($row['status'])?></td> + <td> <?php echo Format::db_date($row['created']); ?></td> + <td> <?php echo ucfirst($row['status']); ?></td> <td> <a href="tickets.php?id=<?php echo $row['ticketID']; ?>"><?php echo $subject; ?></a> </td> - <td> <?=Format::truncate($dept,30)?></td> + <td> <?php echo Format::truncate($dept,30); ?></td> <td><?php echo $phone; ?></td> </tr> <?php diff --git a/include/pear/Auth/SASL/DigestMD5.php b/include/pear/Auth/SASL/DigestMD5.php index 4534e500bb35712acc25b87f4272f6ba39132e42..e97e15daba0c01f2cc361ecffec3cd177fdb7ee0 100644 --- a/include/pear/Auth/SASL/DigestMD5.php +++ b/include/pear/Auth/SASL/DigestMD5.php @@ -178,10 +178,10 @@ class Auth_SASL_DigestMD5 extends Auth_SASL_Common */ function _getCnonce() { - if (file_exists('/dev/urandom') && $fd = @fopen('/dev/urandom', 'r')) { + if (@file_exists('/dev/urandom') && $fd = @fopen('/dev/urandom', 'r')) { return base64_encode(fread($fd, 32)); - } elseif (file_exists('/dev/random') && $fd = @fopen('/dev/random', 'r')) { + } elseif (@file_exists('/dev/random') && $fd = @fopen('/dev/random', 'r')) { return base64_encode(fread($fd, 32)); } else { diff --git a/include/staff/cannedreply.inc.php b/include/staff/cannedresponse.inc.php similarity index 92% rename from include/staff/cannedreply.inc.php rename to include/staff/cannedresponse.inc.php index f18b114a2225fde48d9bddbc73ff3ec93e5a3fd4..cc495d534c919e4954e35a4e3db325746f7d2b4e 100644 --- a/include/staff/cannedreply.inc.php +++ b/include/staff/cannedresponse.inc.php @@ -3,16 +3,16 @@ if(!defined('OSTSCPINC') || !$thisstaff) die('Access Denied'); $info=array(); $qstr=''; if($canned && $_REQUEST['a']!='add'){ - $title='Update Canned Reply'; + $title='Update Canned Response'; $action='update'; $submit_text='Save Changes'; $info=$canned->getInfo(); $info['id']=$canned->getId(); $qstr.='&id='.$canned->getId(); }else { - $title='Add New Canned Reply'; + $title='Add New Canned Response'; $action='create'; - $submit_text='Add Reply'; + $submit_text='Add Response'; $info['isenabled']=isset($info['isenabled'])?$info['isenabled']:1; $qstr.='&a='.$_REQUEST['a']; } @@ -24,13 +24,13 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> <input type="hidden" name="id" value="<?php echo $info['id']; ?>"> - <h2>Canned Reply</h2> + <h2>Canned Response</h2> <table class="form_table" width="940" border="0" cellspacing="0" cellpadding="2"> <thead> <tr> <th colspan="2"> <h4><?php echo $title; ?></h4> - <em>Canned reply settings</em> + <em>Canned response settings</em> </th> </tr> </thead> @@ -63,7 +63,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); </tr> <tr> <th colspan="2"> - <em><strong>Canned Reply</strong>: Make the title short and clear. </em> + <em><strong>Canned Response</strong>: Make the title short and clear. </em> </th> </tr> <tr> @@ -92,13 +92,14 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <div> <input type="file" name="attachments[]" value=""/> </div> - <?}?> + <?php + }?> <div class="faded">You can upload up to 10 attachments per canned response.</div> </td> </tr> <tr> <th colspan="2"> - <em><strong>Internal Notes</strong>: Notes about the canned reply. </em> + <em><strong>Internal Notes</strong>: Notes about the canned response. </em> </th> </tr> <tr> @@ -110,7 +111,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); </table> <?php if ($canned && $canned->getFilters()) { ?> <br/> - <div id="msg_warning">Canned reply is in use by email filter(s): <?php + <div id="msg_warning">Canned response is in use by email filter(s): <?php echo implode(', ', $canned->getFilters()); ?></div> <?php } ?> <p style="padding-left:225px;"> diff --git a/include/staff/cannedreplies.inc.php b/include/staff/cannedresponses.inc.php similarity index 96% rename from include/staff/cannedreplies.inc.php rename to include/staff/cannedresponses.inc.php index 2f19cbd194a1ee7570fcda67eca30f295bc1d2fa..a2fe2b02531ce16d735cabee7612e35e10ebc568 100644 --- a/include/staff/cannedreplies.inc.php +++ b/include/staff/cannedresponses.inc.php @@ -47,10 +47,10 @@ else ?> <div style="width:700;padding-top:5px; float:left;"> - <h2>Canned Replies</h2> + <h2>Canned Responses</h2> </div> <div style="float:right;text-align:right;padding-top:5px;padding-right:5px;"> - <b><a href="canned.php?a=add" class="Icon newReply">Add New Reply</a></b></div> + <b><a href="canned.php?a=add" class="Icon newReply">Add New Response</a></b></div> <div class="clear"></div> <form action="canned.php" method="POST" name="canned" onSubmit="return checkbox_checker(this,1,0);"> <?php csrf_token(); ?> @@ -105,7 +105,7 @@ else <a href="#" onclick="return reset_all(document.forms['canned'])">None</a> <a href="#" onclick="return toogle_all(document.forms['canned'],true)">Toggle</a> <?php }else{ - echo 'No premade replies'; + echo 'No canned responses'; } ?> </td> </tr> @@ -117,11 +117,11 @@ if($res && $num): //Show options.. ?> <p class="centered"> <input class="button" type="submit" name="enable" value="Enable" - onClick=' return confirm("Are you sure you want to ENABLE selected replies?");'> + onClick=' return confirm("Are you sure you want to ENABLE selected responses?");'> <input class="button" type="submit" name="disable" value="Disable" - onClick=' return confirm("Are you sure you want to DISABLE selected replies?");'> + onClick=' return confirm("Are you sure you want to DISABLE selected responses?");'> <input class="button" type="submit" name="delete" value="Delete" - onClick=' return confirm("Are you sure you want to DELETE selected replies?");'> + onClick=' return confirm("Are you sure you want to DELETE selected responses?");'> </p> <?php endif; diff --git a/include/staff/categories.inc.php b/include/staff/categories.inc.php index df750fe795f680963663edd7282d0bfa66e316ef..8d966d9c2b648645146ed8dff536ad472d20c29c 100644 --- a/include/staff/categories.inc.php +++ b/include/staff/categories.inc.php @@ -113,7 +113,7 @@ if($res && $num): //Show options.. <input class="button" type="submit" name="public" value="Make Public" onClick=' return confirm("Are you sure you want to make selected categories PUBLIC?");'> <input class="button" type="submit" name="private" value="Make Internal" - onClick=' return confirm("Are you sure you want to make selected replies INTERNAL?");'> + onClick=' return confirm("Are you sure you want to make selected categories INTERNAL?");'> <input class="button" type="submit" name="delete" value="Delete" onClick=' return confirm("Are you sure you want to DELETE selected categories - including associated FAQs?");'> </p> diff --git a/include/staff/kb-categories.inc.php b/include/staff/faq-categories.inc.php similarity index 100% rename from include/staff/kb-categories.inc.php rename to include/staff/faq-categories.inc.php diff --git a/include/staff/kb-category.inc.php b/include/staff/faq-category.inc.php similarity index 100% rename from include/staff/kb-category.inc.php rename to include/staff/faq-category.inc.php diff --git a/include/staff/groups.inc.php b/include/staff/groups.inc.php index ccb16257bf74660ae43d4a6abad2013984323221..2e994710b806bee813dc7daa4d8f89d911efbb3d 100644 --- a/include/staff/groups.inc.php +++ b/include/staff/groups.inc.php @@ -3,7 +3,7 @@ if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access $qstr=''; -$sql='SELECT grp.*,count(staff.staff_id) as users, count(dept.dept_id) as depts ' +$sql='SELECT grp.*,count(DISTINCT staff.staff_id) as users, count(DISTINCT dept.dept_id) as depts ' .' FROM '.GROUP_TABLE.' grp ' .' LEFT JOIN '.STAFF_TABLE.' staff ON(staff.group_id=grp.group_id) ' .' LEFT JOIN '.GROUP_DEPT_TABLE.' dept ON(dept.group_id=grp.group_id) ' diff --git a/include/staff/helptopic.inc.php b/include/staff/helptopic.inc.php index 6c7c94211ec855b5b7c4111c7382b15f93883239..c222f745c9419eca924b1a975781791b7142e418 100644 --- a/include/staff/helptopic.inc.php +++ b/include/staff/helptopic.inc.php @@ -2,14 +2,15 @@ if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access Denied'); $info=array(); $qstr=''; -if($topic && $_REQUEST['a']!='add'){ +if($topic && $_REQUEST['a']!='add') { $title='Update Help Topic'; $action='update'; $submit_text='Save Changes'; $info=$topic->getInfo(); $info['id']=$topic->getId(); + $info['pid']=$topic->getPid(); $qstr.='&id='.$topic->getId(); -}else { +} else { $title='Add New Help Topic'; $action='create'; $submit_text='Add Topic'; @@ -60,10 +61,34 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); </td> <td> <input type="radio" name="ispublic" value="1" <?php echo $info['ispublic']?'checked="checked"':''; ?>>Public - <input type="radio" name="ispublic" value="0" <?php echo !$info['ispublic']?'checked="checked"':''; ?>>Private <em>(Internal)</em> + <input type="radio" name="ispublic" value="0" <?php echo !$info['ispublic']?'checked="checked"':''; ?>>Private/Internal <span class="error">* </span> </td> </tr> + <tr> + <td width="180"> + Parent Topic: + </td> + <td> + <select name="pid"> + <option value="">— Select Parent Topic —</option> + <?php + $sql='SELECT topic_id, topic FROM '.TOPIC_TABLE + .' WHERE topic_pid=0 ' + .' ORDER by topic'; + if(($res=db_query($sql)) && db_num_rows($res)) { + while(list($id, $name)=db_fetch_row($res)) { + echo sprintf('<option value="%d" %s>%s</option>', + $id, (($info['pid'] && $id==$info['pid'])?'selected="selected"':'') ,$name); + } + } + ?> + </select> (<em>optional</em>) + <span class="error"> <?php echo $errors['pid']; ?></span> + </td> + </tr> + + <tr><th colspan="2"><em>New ticket options</em></th></tr> <tr> <td width="180" class="required"> Priority: diff --git a/include/staff/helptopics.inc.php b/include/staff/helptopics.inc.php index 9e5482ff9b1197a14aa732ee161f3ff5e392724c..da26fbc850f642004426c075e1986efe625c2e0f 100644 --- a/include/staff/helptopics.inc.php +++ b/include/staff/helptopics.inc.php @@ -2,12 +2,16 @@ if(!defined('OSTADMININC') || !$thisstaff->isAdmin()) die('Access Denied'); $qstr=''; -$sql='SELECT topic.*,dept.dept_name as department,priority_desc as priority '. - ' FROM '.TOPIC_TABLE.' topic '. - ' LEFT JOIN '.DEPT_TABLE.' dept ON (dept.dept_id=topic.dept_id) '. - ' LEFT JOIN '.TICKET_PRIORITY_TABLE.' pri ON (pri.priority_id=topic.priority_id) '; +$sql='SELECT topic.* ' + .', IF(ptopic.topic_pid IS NULL, topic.topic, CONCAT_WS(" / ", ptopic.topic, topic.topic)) as name ' + .', dept.dept_name as department ' + .', priority_desc as priority ' + .' FROM '.TOPIC_TABLE.' topic ' + .' LEFT JOIN '.TOPIC_TABLE.' ptopic ON (ptopic.topic_id=topic.topic_pid) ' + .' LEFT JOIN '.DEPT_TABLE.' dept ON (dept.dept_id=topic.dept_id) ' + .' LEFT JOIN '.TICKET_PRIORITY_TABLE.' pri ON (pri.priority_id=topic.priority_id) '; $sql.=' WHERE 1'; -$sortOptions=array('name'=>'topic.topic','status'=>'topic.isactive','type'=>'topic.ispublic', +$sortOptions=array('name'=>'name','status'=>'topic.isactive','type'=>'topic.ispublic', 'dept'=>'department','priority'=>'priority','updated'=>'topic.updated'); $orderWays=array('DESC'=>'DESC','ASC'=>'ASC'); $sort=($_REQUEST['sort'] && $sortOptions[strtolower($_REQUEST['sort'])])?strtolower($_REQUEST['sort']):'name'; @@ -83,7 +87,7 @@ else <input type="checkbox" name="ids[]" value="<?php echo $row['topic_id']; ?>" <?php echo $sel?'checked="checked"':''; ?> <?php echo $default?'disabled="disabled"':''; ?> onClick="highLight(this.value,this.checked);"> </td> - <td><a href="helptopics.php?id=<?php echo $row['topic_id']; ?>"><?php echo $row['topic']; ?></a> </td> + <td><a href="helptopics.php?id=<?php echo $row['topic_id']; ?>"><?php echo $row['name']; ?></a> </td> <td><?php echo $row['isactive']?'Active':'<b>Disabled</b>'; ?></td> <td><?php echo $row['ispublic']?'Public':'<b>Private</b>'; ?></td> <td><?php echo $row['priority']; ?></td> diff --git a/include/staff/staff.inc.php b/include/staff/staff.inc.php index ab10d7d1955c090adeef4887c319045e7190265a..5e13d943c576b53286297ca0c36865ffefceb14f 100644 --- a/include/staff/staff.inc.php +++ b/include/staff/staff.inc.php @@ -11,6 +11,7 @@ if($staff && $_REQUEST['a']!='add'){ $passwd_text='To reset the password enter a new one below'; $info=$staff->getInfo(); $info['id']=$staff->getId(); + $info['teams'] = $staff->getTeams(); $qstr.='&id='.$staff->getId(); }else { $title='Add New Staff'; @@ -295,6 +296,6 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <p style="padding-left:250px;"> <input type="submit" name="submit" value="<?php echo $submit_text; ?>"> <input type="reset" name="reset" value="Reset"> - <input type="button" name="cancel" value="Cancel" onclick='window.location.href="departments.php"'> + <input type="button" name="cancel" value="Cancel" onclick='window.location.href="staff.php"'> </p> </form> diff --git a/include/staff/ticket-edit.inc.php b/include/staff/ticket-edit.inc.php index a0138118886857922da11f3a10bcb5d7bb1d1938..39093db710d6ca41cf681232ed4fa082b1278532 100644 --- a/include/staff/ticket-edit.inc.php +++ b/include/staff/ticket-edit.inc.php @@ -66,7 +66,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$ticket->getUpdateInfo()); <option value="API" <?php echo ($info['source']=='API')?'selected="selected"':''; ?>>API</option> <option value="Other" <?php echo ($info['source']=='Other')?'selected="selected"':''; ?>>Other</option> </select> - <font class="error"><b>*</b> <?=$errors['source']?></font> + <font class="error"><b>*</b> <?php echo $errors['source']; ?></font> </td> </tr> <tr> @@ -85,7 +85,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$ticket->getUpdateInfo()); } ?> </select> - <font class="error"><b>*</b> <?=$errors['topicId']?></font> + <font class="error"><b>*</b> <?php echo $errors['topicId']; ?></font> </td> </tr> <tr> @@ -104,7 +104,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$ticket->getUpdateInfo()); } ?> </select> - <font class="error">* <?=$errors['priorityId']?></font> + <font class="error">* <?php echo $errors['priorityId']; ?></font> </td> </tr> <tr> @@ -123,7 +123,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$ticket->getUpdateInfo()); } ?> </select> - <font class="error">* <?=$errors['slaId']?></font> + <font class="error">* <?php echo $errors['slaId']; ?></font> </td> </tr> <tr> @@ -131,8 +131,8 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$ticket->getUpdateInfo()); Subject: </td> <td> - <input type="text" name="subject" size="60" value="<?=$info['subject']?>"> - <font class="error">* <?=$errors['subject']?></font> + <input type="text" name="subject" size="60" value="<?php echo $info['subject']; ?>"> + <font class="error">* <?php $errors['subject']; ?></font> </td> </tr> <tr> @@ -149,7 +149,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$ticket->getUpdateInfo()); echo Misc::timeDropdown($hr, $min, 'time'); ?> - <font class="error"> <?=$errors['duedate']?> <?php echo $errors['time']; ?></font> + <font class="error"> <?php echo $errors['duedate']; ?> <?php echo $errors['time']; ?></font> <em>Time is based on your time zone (GMT <?php echo $thisstaff->getTZoffset(); ?>)</em> </td> </tr> diff --git a/include/staff/ticket-open.inc.php b/include/staff/ticket-open.inc.php index db3aca84018b1d0744a46363243a6f111f3bfdba..1ef3a7ba52ee0925ee4166984629d6661ce861a6 100644 --- a/include/staff/ticket-open.inc.php +++ b/include/staff/ticket-open.inc.php @@ -30,7 +30,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <?php if($cfg->notifyONNewStaffTicket()) { ?> - <input type="checkbox" name="alertuser" <?php echo (!$errors || $info['alertuser'])? 'checked="checked"': ''?>>Send alert to user. + <input type="checkbox" name="alertuser" <?php echo (!$errors || $info['alertuser'])? 'checked="checked"': ''; ?>>Send alert to user. <?php } ?> </td> @@ -71,7 +71,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <option value="Email" <?php echo ($info['source']=='Email')?'selected="selected"':''; ?>>Email</option> <option value="Other" <?php echo ($info['source']=='Other')?'selected="selected"':''; ?>>Other</option> </select> - <font class="error"><b>*</b> <?=$errors['source']?></font> + <font class="error"><b>*</b> <?php echo $errors['source']; ?></font> </td> </tr> <tr> @@ -90,7 +90,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); } ?> </select> - <font class="error"><b>*</b> <?=$errors['deptId']?></font> + <font class="error"><b>*</b> <?php echo $errors['deptId']; ?></font> </td> </tr> @@ -110,7 +110,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); } ?> </select> - <font class="error"><b>*</b> <?=$errors['topicId']?></font> + <font class="error"><b>*</b> <?php echo $errors['topicId']; ?></font> </td> </tr> <tr> @@ -118,13 +118,13 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); Subject: </td> <td> - <input type="text" name="subject" size="55" value="<?=$info['subject']?>"> - <font class="error">* <?=$errors['subject']?></font> + <input type="text" name="subject" size="55" value="<?php echo $info['subject']; ?>"> + <font class="error">* <?php echo $errors['subject']; ?></font> </td> </tr> <tr> <th colspan="2"> - <em><strong>Issue summary</strong>: Detailed summary of the reason(s) of opening the ticket. <font class="error">* <?=$errors['issue']?></font></em> + <em><strong>Issue summary</strong>: Detailed summary of the reason(s) of opening the ticket. <font class="error">* <?php echo $errors['issue']; ?></font></em> </th> </tr> <tr> @@ -179,14 +179,14 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <div class="file_input"> <input type="file" class="multifile" name="attachments[]" size="30" value="" /> </div> - <? + <?php } ?> </td> </tr> <tr> <th colspan="2"> - <em><strong>Internal Note</strong>: Optional internal note (recommended on assignment) <font class="error"> <?php echo $errors['note'];?></font></em> + <em><strong>Internal Note</strong>: Optional internal note (recommended on assignment) <font class="error"> <?php echo $errors['note']; ?></font></em> </th> </tr> <tr> @@ -213,7 +213,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); echo Misc::timeDropdown($hr, $min, 'time'); ?> - <font class="error"> <?=$errors['duedate']?> <?php echo $errors['time']; ?></font> + <font class="error"> <?php echo $errors['duedate']; ?> <?php echo $errors['time']; ?></font> <em>Time is based on your time zone (GMT <?php echo $thisstaff->getTZoffset(); ?>)</em> </td> </tr> @@ -233,7 +233,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); } ?> </select> - <font class="error"> <?=$errors['priorityId']?></font> + <font class="error"> <?php echo $errors['priorityId']; ?></font> </td> </tr> <?php @@ -269,6 +269,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); </tr> <?php } ?> + <?php if($thisstaff->canCloseTickets()) { ?> <tr> @@ -290,7 +291,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); ?> <label><input type="radio" name="signature" value="none" checked="checked"> None</label> <?php - if($thisstaff->getSignature()) {?> + if($thisstaff->getSignature()) { ?> <label><input type="radio" name="signature" value="mine" <?php echo ($info['signature']=='mine')?'checked="checked"':''; ?>> My signature</label> <?php diff --git a/include/upgrader/prereq.inc.php b/include/upgrader/prereq.inc.php index d32fc328fbe3dd51759ddbaee6f58ac25d862aa2..c08c7fedcc3618ab8f9f1e368423f5cf1deaf36e 100644 --- a/include/upgrader/prereq.inc.php +++ b/include/upgrader/prereq.inc.php @@ -14,9 +14,9 @@ if(!defined('OSTSCPINC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access D <h3>Prerequisites: <font color="red"><?php echo $errors['prereq']; ?></font></h3> These items are necessary in order to run the latest version of osTicket. <ul class="progress"> - <li class="<? echo $upgrader->check_php()?'yes':'no'; ?>"> + <li class="<?php echo $upgrader->check_php()?'yes':'no'; ?>"> PHP v4.3 or greater - (<small><b><?php echo PHP_VERSION; ?></b></small>)</li> - <li class="<? echo $upgrader->check_mysql()?'yes':'no'; ?>"> + <li class="<?php echo $upgrader->check_mysql()?'yes':'no'; ?>"> MySQL v4.4 or greater - (<small><b><?php echo extension_loaded('mysql')?'module loaded':'missing!'; ?></b></small>)</li> </ul> <h3>Higly Recommended:</h3> diff --git a/include/upgrader/sql/2e7531a2-d0e37dca.patch.sql b/include/upgrader/sql/2e7531a2-d0e37dca.patch.sql new file mode 100644 index 0000000000000000000000000000000000000000..8fbcd2fda8d1538d7e7b2b9a4a8032b559fc96bd --- /dev/null +++ b/include/upgrader/sql/2e7531a2-d0e37dca.patch.sql @@ -0,0 +1,14 @@ +/** + * Add help topic nesting support. + * + * @version 1.7-rc2 - nested help topics. + */ + +-- Add help topic parent id. +ALTER TABLE `%TABLE_PREFIX%help_topic` + ADD `topic_pid` INT(10) UNSIGNED NOT NULL DEFAULT '0' AFTER `topic_id` , + ADD INDEX ( `topic_pid` ); + +-- Finished with patch +UPDATE `%TABLE_PREFIX%config` + SET `schema_signature`='d0e37dca324648f1ce2d10528a6026d4'; diff --git a/kb/faq.php b/kb/faq.php index e0c1661c4ff62f8bc85b6c492b2eaf4467d19570..d8c1634e35e1aa625c835143e5cea925c2b0ce0e 100644 --- a/kb/faq.php +++ b/kb/faq.php @@ -28,7 +28,7 @@ $inc='knowledgebase.inc.php'; //FAQs landing page. if($faq && $faq->isPublished()) { $inc='faq.inc.php'; } elseif($category && $category->isPublic() && $_REQUEST['a']!='search') { - $inc='kb-category.inc.php'; + $inc='faq-category.inc.php'; } require_once(CLIENTINC_DIR.'header.inc.php'); require_once(CLIENTINC_DIR.$inc); diff --git a/login.php b/login.php index 823c48a030d494f2b6ae4f06bbbcf35c8de8918d..f35693fd4083401a1aea72125e8cb1cc116710c7 100644 --- a/login.php +++ b/login.php @@ -20,68 +20,9 @@ define('OSTCLIENTINC',TRUE); //make includes happy require_once(INCLUDE_DIR.'class.client.php'); require_once(INCLUDE_DIR.'class.ticket.php'); -//We are ready baby -$loginmsg='Authentication Required'; -if($_POST && (!empty($_POST['lemail']) && !empty($_POST['lticket']))): - $loginmsg='Authentication Required'; - $email=trim($_POST['lemail']); - $ticketID=trim($_POST['lticket']); - //$_SESSION['_client']=array(); #Uncomment to disable login strikes. - - //Check time for last max failed login attempt strike. - $loginmsg='Invalid login'; - if($_SESSION['_client']['laststrike']) { - if((time()-$_SESSION['_client']['laststrike'])<$cfg->getClientLoginTimeout()) { - $loginmsg='Excessive failed login attempts'; - $errors['err']='You\'ve reached maximum failed login attempts allowed. Try again later or <a href="open.php">open a new ticket</a>'; - }else{ //Timeout is over. - //Reset the counter for next round of attempts after the timeout. - $_SESSION['_client']['laststrike']=null; - $_SESSION['_client']['strikes']=0; - } - } - //See if we can fetch local ticket id associated with the ID given - if(!$errors && is_numeric($ticketID) && Validator::is_email($email) && ($ticket=Ticket::lookupByExtId($ticketID))) { - //At this point we know the ticket is valid. - //TODO: 1) Check how old the ticket is...3 months max?? 2) Must be the latest 5 tickets?? - //Check the email given. - if($ticket->getId() && strcasecmp($ticket->getEmail(),$email)==0){ - //valid match...create session goodies for the client. - $user = new ClientSession($email,$ticket->getId()); - $_SESSION['_client']=array(); //clear. - $_SESSION['_client']['userID'] =$ticket->getEmail(); //Email - $_SESSION['_client']['key'] =$ticket->getExtId(); //Ticket ID --acts as password when used with email. See above. - $_SESSION['_client']['token'] =$user->getSessionToken(); - $_SESSION['TZ_OFFSET']=$cfg->getTZoffset(); - $_SESSION['TZ_DST']=$cfg->observeDaylightSaving(); - //Log login info... - $msg=sprintf("%s/%s logged in [%s]",$ticket->getEmail(),$ticket->getExtId(),$_SERVER['REMOTE_ADDR']); - $ost->logDebug('User login', $msg); - //Redirect tickets.php - session_write_close(); - session_regenerate_id(); - @header("Location: tickets.php?id=".$ticket->getExtId()); - require_once('tickets.php'); //Just incase. of header already sent error. - exit; - } - } - //If we get to this point we know the login failed. - $_SESSION['_client']['strikes']+=1; - if(!$errors && $_SESSION['_client']['strikes']>$cfg->getClientMaxLogins()) { - $loginmsg='Access Denied'; - $errors['err']='Forgot your login info? Please <a href="open.php">open a new ticket</a>.'; - $_SESSION['_client']['laststrike']=time(); - $alert='Excessive login attempts by a client?'."\n". - 'Email: '.$_POST['lemail']."\n".'Ticket#: '.$_POST['lticket']."\n". - 'IP: '.$_SERVER['REMOTE_ADDR']."\n".'Time:'.date('M j, Y, g:i a T')."\n\n". - 'Attempts #'.$_SESSION['_client']['strikes']; - $ost->logError('Excessive login attempts (client)', $alert, ($cfg->alertONLoginError())); - }elseif($_SESSION['_client']['strikes']%2==0){ //Log every other failed login attempt as a warning. - $alert='Email: '.$_POST['lemail']."\n".'Ticket #: '.$_POST['lticket']."\n".'IP: '.$_SERVER['REMOTE_ADDR']. - "\n".'TIME: '.date('M j, Y, g:i a T')."\n\n".'Attempts #'.$_SESSION['_client']['strikes']; - $ost->logWarning('Failed login attempt (client)', $alert); - } -endif; + +if ($_POST) ClientSession::tryLogin($_POST['lticket'], $_POST['lemail']); +else ClientSession::tryLogin($_GET['t'], $_GET['e'], $_GET['a']); $nav = new UserNav(); $nav->setActiveNav('status'); diff --git a/main.inc.php b/main.inc.php index 6fd31bbbd8f7a72cf4fd6997d6172bd2ab20fbc6..abcbfeabdaee005bdd83abb7719ac163e0e531af 100644 --- a/main.inc.php +++ b/main.inc.php @@ -62,8 +62,8 @@ /*############## Do NOT monkey with anything else beyond this point UNLESS you really know what you are doing ##############*/ #Current version && schema signature (Changes from version to version) - define('THIS_VERSION','1.7-RC1'); //Shown on admin panel - define('SCHEMA_SIGNATURE','2e7531a201b5b8650dcd43681a832ebd'); //MD5 signature of the db schema. (used to trigger upgrades) + define('THIS_VERSION','1.7-RC2'); //Shown on admin panel + define('SCHEMA_SIGNATURE','d0e37dca324648f1ce2d10528a6026d4'); //MD5 signature of the db schema. (used to trigger upgrades) #load config info $configfile=''; diff --git a/scp/canned.php b/scp/canned.php index 2a2252233627510e35f0f12da6f9c916751c8143..72a1680aa21b3777afc765a2f2256d2fd433782c 100644 --- a/scp/canned.php +++ b/scp/canned.php @@ -2,7 +2,7 @@ /********************************************************************* canned.php - Canned Replies aka Premade Responses. + Canned Responses aka Premade Responses. Peter Rotich <peter@osticket.com> Copyright (c) 2006-2012 osTicket @@ -25,15 +25,15 @@ if(!$thisstaff || !$thisstaff->canManageCannedResponses()) { $canned=null; if($_REQUEST['id'] && !($canned=Canned::lookup($_REQUEST['id']))) - $errors['err']='Unknown or invalid canned reply ID.'; + $errors['err']='Unknown or invalid canned response ID.'; if($_POST && $thisstaff->canManageCannedResponses()) { switch(strtolower($_POST['do'])) { case 'update': if(!$canned) { - $errors['err']='Unknown or invalid canned reply.'; + $errors['err']='Unknown or invalid canned response.'; } elseif($canned->update($_POST, $errors)) { - $msg='Canned reply updated successfully'; + $msg='Canned response updated successfully'; //Delete removed attachments. //XXX: files[] shouldn't be changed under any circumstances. $keepers = $_POST['files']?$_POST['files']:array(); @@ -50,7 +50,7 @@ if($_POST && $thisstaff->canManageCannedResponses()) { $canned->reload(); } elseif(!$errors['err']) { - $errors['err']='Error updating canned reply. Try again!'; + $errors['err']='Error updating canned response. Try again!'; } break; case 'create': @@ -75,22 +75,22 @@ if($_POST && $thisstaff->canManageCannedResponses()) { implode(',', db_input($_POST['ids'])).')'; if(db_query($sql) && ($num=db_affected_rows())) { if($num==$count) - $msg='Selected canned replies enabled'; + $msg='Selected canned responses enabled'; else - $warn="$num of $count selected canned replies enabled"; + $warn="$num of $count selected canned responses enabled"; } else { - $errors['err']='Unable to enable selected canned replies.'; + $errors['err']='Unable to enable selected canned responses.'; } } elseif($_POST['disable']) { $sql='UPDATE '.CANNED_TABLE.' SET isenabled=0 WHERE canned_id IN ('. implode(',', db_input($_POST['ids'])).')'; if(db_query($sql) && ($num=db_affected_rows())) { if($num==$count) - $msg='Selected canned replies disabled'; + $msg='Selected canned responses disabled'; else - $warn="$num of $count selected canned replies disabled"; + $warn="$num of $count selected canned responses disabled"; } else { - $errors['err']='Unable to disable selected canned replies'; + $errors['err']='Unable to disable selected canned responses'; } }elseif($_POST['delete']) { $i=0; @@ -100,11 +100,11 @@ if($_POST && $thisstaff->canManageCannedResponses()) { } if($i==$count) - $msg='Selected canned replies deleted successfully'; + $msg='Selected canned responses deleted successfully'; elseif($i>0) - $warn="$i of $count selected canned replies deleted"; + $warn="$i of $count selected canned responses deleted"; elseif(!$errors['err']) - $errors['err']='Unable to delete selected canned replies'; + $errors['err']='Unable to delete selected canned responses'; } else { $errors['err']='Unknown command'; @@ -117,9 +117,9 @@ if($_POST && $thisstaff->canManageCannedResponses()) { } } -$page='cannedreplies.inc.php'; +$page='cannedresponses.inc.php'; if($canned || ($_REQUEST['a'] && !strcasecmp($_REQUEST['a'],'add'))) - $page='cannedreply.inc.php'; + $page='cannedresponse.inc.php'; $nav->setTabActive('kbase'); require(STAFFINC_DIR.'header.inc.php'); diff --git a/scp/css/scp.css b/scp/css/scp.css index 3c7b83e12f3fc85001b1fd451c6fcc3e586e2752..0042ed4306a406337f3a3deec2811467c06a325d 100644 --- a/scp/css/scp.css +++ b/scp/css/scp.css @@ -234,7 +234,7 @@ a.premade { background:url(../images/icons/premade_reply.gif) } a.newPremade { background:url(../images/icons/new_premade_reply.gif) } a.kb { background:url(../images/icons/kb.gif) } -a.kb-categories { background:url(../images/icons/kb-categories.gif) } +a.faq-categories { background:url(../images/icons/faq-categories.gif) } a.canned { background:url(../images/icons/canned.gif) } a.staff { background:url(../images/icons/list_groups.gif) } diff --git a/scp/dashboard.php b/scp/dashboard.php index 8756ca1ea86522eb77c8a7b8ee381f3928de27f9..3b41ccfdfc1a2f4e123c501d8b0a1088584cffb3 100644 --- a/scp/dashboard.php +++ b/scp/dashboard.php @@ -63,6 +63,6 @@ member.</p> <div id="table-here"></div> -<? +<?php include(STAFFINC_DIR.'footer.inc.php'); ?> diff --git a/scp/images/kb_category_bg.png b/scp/images/faq_category_bg.png similarity index 100% rename from scp/images/kb_category_bg.png rename to scp/images/faq_category_bg.png diff --git a/scp/images/icons/kb-categories.gif b/scp/images/icons/faq-categories.gif similarity index 100% rename from scp/images/icons/kb-categories.gif rename to scp/images/icons/faq-categories.gif diff --git a/scp/images/icons/kb-categories.png b/scp/images/icons/faq-categories.png similarity index 100% rename from scp/images/icons/kb-categories.png rename to scp/images/icons/faq-categories.png diff --git a/scp/kb.php b/scp/kb.php index 0a683a05f01d8cf294b336929038a9803171ab85..65e3a66ef2930c5b01c4fd7f9ffd6b6fa0733962 100644 --- a/scp/kb.php +++ b/scp/kb.php @@ -19,9 +19,9 @@ $category=null; if($_REQUEST['cid'] && !($category=Category::lookup($_REQUEST['cid']))) $errors['err']='Unknown or invalid FAQ category'; -$inc='kb-categories.inc.php'; //KB landing page. +$inc='faq-categories.inc.php'; //KB landing page. if($category && $_REQUEST['a']!='search') { - $inc='kb-category.inc.php'; + $inc='faq-category.inc.php'; } $nav->setTabActive('kbase'); require_once(STAFFINC_DIR.'header.inc.php'); diff --git a/scp/tickets.php b/scp/tickets.php index 66af215ea732a492b5f9e99cf5b0d99c16ebdb6c..8c799a2a6a02a26c4185afece329ffc5e57cdcf4 100644 --- a/scp/tickets.php +++ b/scp/tickets.php @@ -163,7 +163,10 @@ if($_POST && !$errors): $errors['err']='Perm. Denied. You are not allowed to edit tickets'; elseif($ticket->update($_POST,$errors)) { $msg='Ticket updated successfully'; - $_REQUEST['a'] = null; + $_REQUEST['a'] = null; //Clear edit action - going back to view. + //Check to make sure the staff STILL has access post-update (e.g dept change). + if(!$ticket->checkStaffAccess($thisstaff)) + $ticket=null; } elseif(!$errors['err']) { $errors['err']='Unable to update the ticket. Correct the errors below and try again!'; } diff --git a/setup/inc/install-prereq.inc.php b/setup/inc/install-prereq.inc.php index b07ffd996c5f231c18c7471d419d7ec62b55969b..11746eda72bb2afe44cee62eb7f3ae99e4145a15 100644 --- a/setup/inc/install-prereq.inc.php +++ b/setup/inc/install-prereq.inc.php @@ -14,17 +14,17 @@ if(!defined('SETUPINC')) die('Kwaheri!'); <h3>Required: <font color="red"><?php echo $errors['prereq']; ?></font></h3> These items are necessary in order to install and use osTicket. <ul class="progress"> - <li class="<? echo $installer->check_php()?'yes':'no'; ?>"> + <li class="<?php echo $installer->check_php()?'yes':'no'; ?>"> PHP v4.3 or greater - (<small><b><?php echo PHP_VERSION; ?></b></small>)</li> - <li class="<? echo $installer->check_mysql()?'yes':'no'; ?>"> + <li class="<?php echo $installer->check_mysql()?'yes':'no'; ?>"> MySQL v4.4 or greater - (<small><b><?php echo extension_loaded('mysql')?'module loaded':'missing!'; ?></b></small>)</li> </ul> <h3>Recommended:</h3> You can use osTicket without these, but you may not be able to use all features. <ul class="progress"> - <li class="<? echo extension_loaded('mcrypt')?'yes':'no'; ?>">Mcrypt extension</li> - <li class="<? echo extension_loaded('gd')?'yes':'no'; ?>">Gdlib extension</li> - <li class="<? echo extension_loaded('imap')?'yes':'no'; ?>">PHP IMAP extension</li> + <li class="<?php echo extension_loaded('mcrypt')?'yes':'no'; ?>">Mcrypt extension</li> + <li class="<?php echo extension_loaded('gd')?'yes':'no'; ?>">Gdlib extension</li> + <li class="<?php echo extension_loaded('imap')?'yes':'no'; ?>">PHP IMAP extension</li> </ul> <div id="bar"> <form method="post" action="install.php"> diff --git a/setup/inc/sql/osTicket-mysql.sql b/setup/inc/sql/osTicket-mysql.sql index 21acfcfe83efe5b52d9ba5d9b153a87a25cc7eb9..5cfba00d6538506e29e136f188610df4508551cb 100644 --- a/setup/inc/sql/osTicket-mysql.sql +++ b/setup/inc/sql/osTicket-mysql.sql @@ -375,6 +375,7 @@ INSERT INTO `%TABLE_PREFIX%group_dept_access` (`group_id`, `dept_id`) VALUES DROP TABLE IF EXISTS `%TABLE_PREFIX%help_topic`; CREATE TABLE `%TABLE_PREFIX%help_topic` ( `topic_id` int(11) unsigned NOT NULL auto_increment, + `topic_pid` int(10) unsigned NOT NULL default '0', `isactive` tinyint(1) unsigned NOT NULL default '1', `ispublic` tinyint(1) unsigned NOT NULL default '1', `noautoresp` tinyint(3) unsigned NOT NULL default '0', @@ -389,6 +390,7 @@ CREATE TABLE `%TABLE_PREFIX%help_topic` ( `updated` datetime NOT NULL, PRIMARY KEY (`topic_id`), UNIQUE KEY `topic` (`topic`), + KEY `topic_pid` (`topic_pid`), KEY `priority_id` (`priority_id`), KEY `dept_id` (`dept_id`), KEY `staff_id` (`staff_id`,`team_id`), diff --git a/setup/inc/sql/osTicket-mysql.sql.md5 b/setup/inc/sql/osTicket-mysql.sql.md5 index ad088e947dfe5eac019e5860c29e776bfabe60b1..63d2ce443b0448ca776ebc37b298a3c4ff93c387 100644 --- a/setup/inc/sql/osTicket-mysql.sql.md5 +++ b/setup/inc/sql/osTicket-mysql.sql.md5 @@ -1 +1 @@ -2e7531a201b5b8650dcd43681a832ebd +d0e37dca324648f1ce2d10528a6026d4 diff --git a/setup/setup.inc.php b/setup/setup.inc.php index d6f54ca268c784e471dc756d228ff888763d4730..07b86c6ffa43e13338f3b1f6c176e0cee2f53316 100644 --- a/setup/setup.inc.php +++ b/setup/setup.inc.php @@ -15,7 +15,7 @@ **********************************************************************/ #This version - changed on every release -define('THIS_VERSION', '1.7-RC1'); +define('THIS_VERSION', '1.7-RC2'); #inits error_reporting(E_ALL ^ E_NOTICE); //turn on errors?? diff --git a/setup/test/lint.php b/setup/test/lint.php index 4566c495a247874f538fc043cd777185e17d509c..4887856182e23d3acfed19168a92dbc744f4e9e8 100644 --- a/setup/test/lint.php +++ b/setup/test/lint.php @@ -44,7 +44,40 @@ if (strlen($syntax_errors)) { echo "$syntax_errors"; exit(); } else { + echo "pass\n"; +} + +function line_number_for_offset($filename, $offset) { + $lines = file($filename); + $bytes = $line = 0; + while ($bytes < $offset) { + $bytes += strlen(array_shift($lines)); + $line += 1; + } + return $line; +} +echo "Short open tags: "; +$fails = array(); +foreach ($scripts as $s) { + $matches = array(); + if (preg_match_all('/<\?\s*(?!php|xml).*$/m', file_get_contents($s), $matches, + PREG_OFFSET_CAPTURE) > 0) { + foreach ($matches[0] as $match) + $fails[] = array( + str_replace($root.'/', '', $s), + $match[0], + line_number_for_offset($s, $match[1])); + } +} +if (count($fails)) { + echo "FAIL\n"; + echo "-------------------------------------------------------\n"; + foreach ($fails as $f) + echo sprintf("In %s, line %d: %s\n", $f[0], $f[2], + str_replace("\n", " ", $f[1])); echo "\n"; +} else { + echo "pass\n"; } # Run phplint across all php files