diff --git a/api/api.inc.php b/api/api.inc.php index 926a0e2a9ddb4b0464c5f1385e56665312339d8e..48836382022d6b8ddec422e0afc46ee074930567 100644 --- a/api/api.inc.php +++ b/api/api.inc.php @@ -42,7 +42,7 @@ function api_exit($code,$msg='') { //Error occured... $_SESSION['api']['errors']+=1; $_SESSION['api']['time']=time(); - $ost->logWarning("API error - code #$code",$msg); + $ost->logWarning("API error - code #$code", $msg, ($_SESSION['api']['errors']>10)); //echo "API Error:.$msg"; } if($remotehost){ @@ -66,19 +66,20 @@ function api_exit($code,$msg='') { } //Remote hosts need authorization. +$apikey = null; if($remotehost) { - - $ip=$_SERVER['REMOTE_ADDR']; - $key=$_SERVER['HTTP_USER_AGENT']; //pulling all tricks. - //Upto 10 consecutive errors allowed...before a 5 minute timeout. + //Upto 10 consecutive errors allowed...before a 2 minute timeout. //One more error during timeout and timeout starts a new clock - if($_SESSION['api']['errors']>10 && (time()-$_SESSION['api']['time'])<=5*60) { // timeout! - api_exit(EX_NOPERM,"Remote host [$ip] in timeout - error #".$_SESSION['api']['errors']); - } - //Check API key & ip - if(!Validator::is_ip($ip) || !Api::validate($key,$ip)) { - api_exit(EX_NOPERM,'Unknown remote host ['.$ip.'] or invalid API key ['.$key.']'); - } + if($_SESSION['api']['errors']>10 && (time()-$_SESSION['api']['time'])<=2*60) // timeout! + api_exit(EX_NOPERM, 'Remote host ['.$_SERVER['REMOTE_ADDR'].'] in timeout - error #'.$_SESSION['api']['errors']); + + if(!isset($_SERVER['HTTP_X_API_KEY']) || !isset($_SERVER['REMOTE_ADDR'])) + api_exit(EX_NOPERM, 'API key required'); + elseif(!($apikey=API::lookupByKey($_SERVER['HTTP_X_API_KEY'], $_SERVER['REMOTE_ADDR'])) + || !$apikey->isActive() + || $apikey->getIPAddr()!=$_SERVER['REMOTE_ADDR']) + api_exit(EX_NOPERM, 'API key not found/active or source IP not authorized'); + //At this point we know the remote host/IP is allowed. $_SESSION['api']['errors']=0; //clear errors for the session. } diff --git a/api/pipe.php b/api/pipe.php index 699e7400001c8f497a5bbd30b5d921ca353abe2b..ff23cfa1b56c0f6fa75824f8106337c7440f6cb9 100644 --- a/api/pipe.php +++ b/api/pipe.php @@ -16,6 +16,7 @@ **********************************************************************/ @chdir(realpath(dirname(__FILE__)).'/'); //Change dir. ini_set('memory_limit', '256M'); //The concern here is having enough mem for emails with attachments. +$apikey = null; require('api.inc.php'); require_once(INCLUDE_DIR.'class.mailparse.php'); require_once(INCLUDE_DIR.'class.email.php'); @@ -23,6 +24,9 @@ require_once(INCLUDE_DIR.'class.email.php'); //Make sure piping is enabled! if(!$cfg->isEmailPipingEnabled()) api_exit(EX_UNAVAILABLE,'Email piping not enabled - check MTA settings.'); +elseif($apikey && !$apikey->canCreateTickets()) //apikey is ONLY set on remote post - local post don't need a key (for now). + api_exit(EX_NOPERM, 'API key not authorized'); + //Get the input $data=isset($_SERVER['HTTP_HOST'])?file_get_contents('php://input'):file_get_contents('php://stdin'); if(empty($data)){ @@ -77,8 +81,8 @@ $name=trim($from->personal,'"'); if($from->comment && $from->comment[0]) $name.=' ('.$from->comment[0].')'; $subj=utf8_encode($parser->getSubject()); -if(!($body=Format::stripEmptyLines($parser->getBody())) && $subj) - $body=$subj; +if(!($body=Format::stripEmptyLines($parser->getBody()))) + $body=$subj?$subj:'(EMPTY)'; $var['mid']=$parser->getMessageId(); $var['email']=$from->mailbox.'@'.$from->host; diff --git a/include/ajax.reports.php b/include/ajax.reports.php index 0ad0640a3d64270eda544011c2aa7ec28d101acb..bf86374055bd45b8e07a1860a157d77d3b53aa64 100644 --- a/include/ajax.reports.php +++ b/include/ajax.reports.php @@ -80,6 +80,8 @@ class OverviewReportAjaxAPI extends AjaxController { (T1.staff_id='.db_input($thisstaff->getId()) .(($depts=$thisstaff->getManagedDepartments())? (' OR T1.dept_id IN('.implode(',', db_input($depts)).')'):'') + .(($thisstaff->canViewStaffStats())? + (' OR T1.dept_id IN('.implode(',', db_input($thisstaff->getDepts())).')'):'') .')' ) ) diff --git a/include/api.ticket.php b/include/api.ticket.php index 51e129ad41e5cfe691517fbdb1a180326ecec4f7..7fd5ba713c8237aad483c4a44b08989cf9b17c3f 100644 --- a/include/api.ticket.php +++ b/include/api.ticket.php @@ -22,7 +22,9 @@ class TicketController extends ApiController { } function create($format) { - $this->requireApiKey(); + + if(!($key=$this->getApiKey()) || !$key->canCreateTickets()) + Http::response(401, 'API key not authorized'); # Parse request body $data = $this->getRequest($format); diff --git a/include/class.api.php b/include/class.api.php index f3d3094a5b695a1601cb0d91e5dafdfe6014918b..df8cfb0c8a9cb9f452b62de56c1e15181731d614 100644 --- a/include/class.api.php +++ b/include/class.api.php @@ -17,102 +17,115 @@ class API { var $id; - var $info; + var $ht; - function API($id){ - $this->id=0; + function API($id) { + $this->id = 0; $this->load($id); } - function load($id) { + function load($id=0) { + + if(!$id && !($id=$this->getId())) + return false; $sql='SELECT * FROM '.API_KEY_TABLE.' WHERE id='.db_input($id); - if(($res=db_query($sql)) && db_num_rows($res)) { - $info=db_fetch_array($res); - $this->id=$info['id']; - $this->info=$info; - return true; - } - return false; + if(!($res=db_query($sql)) || !db_num_rows($res)) + return false; + + $this->ht = db_fetch_array($res); + $this->id = $this->ht['id']; + + return true; } function reload() { - return $this->load($this->getId()); + return $this->load(); } - function getId(){ + function getId() { return $this->id; } - function getKey(){ - return $this->info['apikey']; + function getKey() { + return $this->ht['apikey']; } - function getIPAddr(){ - return $this->info['ipaddr']; + function getIPAddr() { + return $this->ht['ipaddr']; } - function getNotes(){ - return $this->info['notes']; + function getNotes() { + return $this->ht['notes']; } - function isActive(){ - return ($this->info['isactive']); + function getHashtable() { + return $this->ht; } - function update($vars,&$errors){ - if(API::save($this->getId(),$vars,$errors)){ - $this->reload(); - return true; - } + function isActive() { + return ($this->ht['isactive']); + } + + function canCreateTickets() { + return ($this->ht['can_create_tickets']); + } + + function update($vars, &$errors) { + + if(!API::save($this->getId(), $vars, $errors)) + return false; - return false; + $this->reload(); + + return true; } - function delete(){ + function delete() { $sql='DELETE FROM '.API_KEY_TABLE.' WHERE id='.db_input($this->getId()).' LIMIT 1'; return (db_query($sql) && ($num=db_affected_rows())); } /** Static functions **/ - function add($vars,&$errors){ - return API::save(0,$vars,$errors); + function add($vars, &$errors) { + return API::save(0, $vars, $errors); } - function validate($key,$ip){ - - $sql='SELECT id FROM '.API_KEY_TABLE.' WHERE ipaddr='.db_input($ip).' AND apikey='.db_input($key); - return (($res=db_query($sql)) && db_num_rows($res)); + function validate($key, $ip) { + return ($key && $ip && self::getIdByKey($key, $ip)); } - function getKeyByIPAddr($ip){ + function getIdByKey($key, $ip='') { - $sql='SELECT apikey FROM '.API_KEY_TABLE.' WHERE ipaddr='.db_input($ip); + $sql='SELECT id FROM '.API_KEY_TABLE.' WHERE apikey='.db_input($key); + if($ip) + $sql.=' AND ipaddr='.db_input($ip); + if(($res=db_query($sql)) && db_num_rows($res)) - list($key)=db_fetch_row($res); + list($id) = db_fetch_row($res); - return $key; + return $id; } - function lookup($id){ - return ($id && is_numeric($id) && ($k= new API($id)) && $k->getId()==$id)?$k:null; + function lookupByKey($key, $ip='') { + return self::lookup(self::getIdByKey($key, $ip)); } - function save($id,$vars,&$errors){ + function lookup($id) { + return ($id && is_numeric($id) && ($k= new API($id)) && $k->getId()==$id)?$k:null; + } - if(!$id) { - if(!$vars['ipaddr'] || !Validator::is_ip($vars['ipaddr'])) - $errors['ipaddr']='Valid IP required'; - elseif(API::getKeyByIPAddr($vars['ipaddr'])) - $errors['ipaddr']='API key for the IP already exists'; - } + function save($id, $vars, &$errors) { + if(!$id && (!$vars['ipaddr'] || !Validator::is_ip($vars['ipaddr']))) + $errors['ipaddr'] = 'Valid IP required'; + if($errors) return false; - - $sql=' updated=NOW() '. - ',isactive='.db_input($vars['isactive']). - ',notes='.db_input($vars['notes']); + $sql=' updated=NOW() ' + .',isactive='.db_input($vars['isactive']) + .',can_create_tickets='.db_input($vars['can_create_tickets']) + .',notes='.db_input($vars['notes']); if($id) { $sql='UPDATE '.API_KEY_TABLE.' SET '.$sql.' WHERE id='.db_input($id); @@ -120,14 +133,17 @@ class API { return true; $errors['err']='Unable to update API key. Internal error occurred'; - }else{ - $sql='INSERT INTO '.API_KEY_TABLE.' SET '.$sql.',created=NOW() '. - ',ipaddr='.db_input($vars['ipaddr']). - ',apikey='.db_input(strtoupper(md5(time().$vars['ipaddr'].md5(Misc::randcode(16))))); + + } else { + $sql='INSERT INTO '.API_KEY_TABLE.' SET '.$sql + .',created=NOW() ' + .',ipaddr='.db_input($vars['ipaddr']) + .',apikey='.db_input(strtoupper(md5(time().$vars['ipaddr'].md5(Misc::randcode(16))))); + if(db_query($sql) && ($id=db_insert_id())) return $id; - $errors['err']='Unable to add API key. Internal error'; + $errors['err']='Unable to add API key. Try again!'; } return false; @@ -141,16 +157,24 @@ class API { * API request. */ class ApiController { + function requireApiKey() { # Validate the API key -- required to be sent via the X-API-Key # header - if (!isset($_SERVER['HTTP_X_API_KEY'])) + if (!isset($_SERVER['HTTP_X_API_KEY']) || !isset($_SERVER['REMOTE_ADDR'])) Http::response(403, "API key required"); - else if (!Api::validate($_SERVER['HTTP_X_API_KEY'], - $_SERVER['REMOTE_ADDR'])) - Http::response(401, - "API key not found or source IP not authorized"); + elseif (!($key=API::lookupByKey($_SERVER['HTTP_X_API_KEY'], $_SERVER['REMOTE_ADDR'])) + || !$key->isActive() + || $key->getIPAddr()!=$_SERVER['REMOTE_ADDR']) + Http::response(401, "API key not found/active or source IP not authorized"); + + return $key; } + + function getApiKey() { + return $this->requireApiKey(); + } + /** * Retrieves the body of the API request and converts it to a common * hashtable. For JSON formats, this is mostly a noop, the conversion @@ -248,7 +272,7 @@ class ApiJsonDataParser extends JsonDataParser { return $current; foreach ($current as $key=>&$value) { if ($key == "phone") { - list($value,$current["phone_ext"]) + list($value, $current["phone_ext"]) = explode("X", strtoupper($value), 2); } else if ($key == "alert") { $value = (bool)$value; diff --git a/include/class.filter.php b/include/class.filter.php index b69a98987aeca7081a92f101e0dec00b50928bc1..0af8330b8022393162450c803031b000a7471ab0 100644 --- a/include/class.filter.php +++ b/include/class.filter.php @@ -233,7 +233,9 @@ class Filter { 'equal' => array('strcmp', 0), 'not_equal' => array('strcmp', null, 0), 'contains' => array('strpos', null, false), - 'dn_contain'=> array('strpos', false) + 'dn_contain'=> array('strpos', false), + 'starts' => array('strpos', 0), + 'ends' => array('endsWith', true) ); $match = false; @@ -309,7 +311,9 @@ class Filter { 'equal'=> 'Equal', 'not_equal'=> 'Not Equal', 'contains'=> 'Contains', - 'dn_contain'=> 'Does Not Contain' + 'dn_contain'=> 'Does Not Contain', + 'starts'=> 'Starts With', + 'ends'=> 'Ends With' ); } @@ -366,8 +370,8 @@ class Filter { function save_rules($id,$vars,&$errors) { - $matches=array('name','email','subject','body','header'); - $types=array('equal','not_equal','contains','dn_contain'); + $matches = array_keys(self::getSupportedMatches()); + $types = array_keys(self::getSupportedMatchTypes()); $rules=array(); for($i=1; $i<=25; $i++) { //Expecting no more than 25 rules... @@ -942,4 +946,20 @@ class TicketFilter { return $sources[strtolower($origin)]; } } + +/** + * Function: endsWith + * + * Returns TRUE if the haystack ends with needle and FALSE otherwise. + * Thanks, http://stackoverflow.com/a/834355 + */ +function endsWith($haystack, $needle) +{ + $length = strlen($needle); + if ($length == 0) { + return true; + } + + return (substr($haystack, -$length) === $needle); +} ?> diff --git a/include/class.group.php b/include/class.group.php index b13082be9f24301e12bf298cac0d043f86b7f8d1..10a5eadafe9525f0c044c46d918924c00e2451cc 100644 --- a/include/class.group.php +++ b/include/class.group.php @@ -209,6 +209,8 @@ class Group { .', can_ban_emails='.db_input($vars['can_ban_emails']) .', can_manage_premade='.db_input($vars['can_manage_premade']) .', can_manage_faq='.db_input($vars['can_manage_faq']) + .', can_post_ticket_reply='.db_input($vars['can_post_ticket_reply']) + .', can_view_staff_stats='.db_input($vars['can_view_staff_stats']) .', notes='.db_input($vars['notes']); if($id) { diff --git a/include/class.staff.php b/include/class.staff.php index 9c6078c7f148e3a01612c05cac9c796422871269..102e79ecda4ae642dc7a777f75143b3148078a22 100644 --- a/include/class.staff.php +++ b/include/class.staff.php @@ -302,7 +302,7 @@ class Staff { function canEditTickets() { return ($this->ht['can_edit_tickets']); } - + function canDeleteTickets() { return ($this->ht['can_delete_tickets']); } @@ -311,6 +311,14 @@ class Staff { return ($this->ht['can_close_tickets']); } + function canPostReply() { + return ($this->ht['can_post_ticket_reply']); + } + + function canViewStaffStats() { + return ($this->ht['can_view_staff_stats']); + } + function canAssignTickets() { return ($this->ht['can_assign_tickets']); } diff --git a/include/class.topic.php b/include/class.topic.php index 900ff76168363c47e9b4763fee6cf2f00334a0fd..ee579e45b2d74e292ff9fd118a5547e32aa2b553 100644 --- a/include/class.topic.php +++ b/include/class.topic.php @@ -165,10 +165,13 @@ class Topic { return self::getHelpTopics(true); } - function getIdByName($topic) { - $sql='SELECT topic_id FROM '.TOPIC_TABLE.' WHERE topic='.db_input($topic); + function getIdByName($name, $pid=0) { + + $sql='SELECT topic_id FROM '.TOPIC_TABLE + .' WHERE topic='.db_input($name) + .' AND topic_pid='.db_input($pid); if(($res=db_query($sql)) && db_num_rows($res)) - list($id)=db_fetch_row($res); + list($id) = db_fetch_row($res); return $id; } @@ -188,7 +191,7 @@ class Topic { $errors['topic']='Help topic required'; elseif(strlen($vars['topic'])<5) $errors['topic']='Topic is too short. 5 chars minimum'; - elseif(($tid=self::getIdByName($vars['topic'])) && $tid!=$id) + elseif(($tid=self::getIdByName($vars['topic'], $vars['pid'])) && $tid!=$id) $errors['topic']='Topic already exists'; if(!$vars['dept_id']) diff --git a/include/staff/apikey.inc.php b/include/staff/apikey.inc.php index ff5592b46fd57b374bb0a2d0825f99d3156bceef..6e2ffe6e6d8fdc4d41134726e6853162f3bfe0da 100644 --- a/include/staff/apikey.inc.php +++ b/include/staff/apikey.inc.php @@ -6,9 +6,7 @@ if($api && $_REQUEST['a']!='add'){ $title='Update API Key'; $action='update'; $submit_text='Save Changes'; - $info['id']=$api->getId(); - $info['isactive']=$api->isActive()?1:0; - $info['notes']=$api->getNotes(); + $info=$api->getHashtable(); $qstr.='&id='.$api->getId(); }else { $title='Add New API Key'; @@ -30,13 +28,13 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <tr> <th colspan="2"> <h4><?php echo $title; ?></h4> - <em>API Key is autogenerated and unique per IP address.</em> + <em>API Key is auto-generated. Delete and re-add to change the key.</em> </th> </tr> </thead> <tbody> <tr> - <td width="180" class="required"> + <td width="150" class="required"> Status: </td> <td> @@ -47,7 +45,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); </tr> <?php if($api){ ?> <tr> - <td width="180"> + <td width="150"> IP Address: </td> <td> @@ -55,14 +53,14 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); </td> </tr> <tr> - <td width="180"> + <td width="150"> API Key: </td> - <td><?php echo $api->getKey(); ?> <em>(Delete and re-add to change the key)</em></td> + <td><?php echo $api->getKey(); ?> </td> </tr> <?php }else{ ?> <tr> - <td width="180" class="required"> + <td width="150" class="required"> IP Address: </td> <td> @@ -73,16 +71,13 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <?php } ?> <tr> <th colspan="2"> - <em><strong>Enabled Services:</strong>: Check applicable API services.</em> + <em><strong>Enabled Services:</strong>: Check applicable API services. All active keys can make cron call.</em> </th> </tr> <tr> - <td width="180"> - Email piping: - </td> - <td> - <input type="checkbox" name="email_piping" value="1" checked="checked" disabled="disabled" > - <strong>Enable</strong> remote email piping. + <td colspan=2 style="padding-left:5px"> + <input type="checkbox" name="can_create_tickets" value="1" <?php echo $info['can_create_tickets']?'checked="checked"':''; ?> > + Can Create Tickets. <em>(XML/JSON/PIPE)</em> </td> </tr> <tr> diff --git a/include/staff/apikeys.inc.php b/include/staff/apikeys.inc.php index b085329343d101fe69de9b4628ed13ace6d6586b..90b4e31eef10af37b71ba2ebef6edffc9dc63d7b 100644 --- a/include/staff/apikeys.inc.php +++ b/include/staff/apikeys.inc.php @@ -5,7 +5,7 @@ $qstr=''; $sql='SELECT * FROM '.API_KEY_TABLE.' WHERE 1'; $sortOptions=array('key'=>'apikey','status'=>'isactive','ip'=>'ipaddr','date'=>'created','created'=>'created','updated'=>'updated'); $orderWays=array('DESC'=>'DESC','ASC'=>'ASC'); -$sort=($_REQUEST['sort'] && $sortOptions[strtolower($_REQUEST['sort'])])?strtolower($_REQUEST['sort']):'date'; +$sort=($_REQUEST['sort'] && $sortOptions[strtolower($_REQUEST['sort'])])?strtolower($_REQUEST['sort']):'key'; //Sorting options... if($sort && $sortOptions[$sort]) { $order_column =$sortOptions[$sort]; @@ -54,10 +54,10 @@ else <thead> <tr> <th width="7"> </th> - <th width="150" nowrap><a <?php echo $date_sort; ?>href="apikeys.php?<?php echo $qstr; ?>&sort=date">Date Added</a></th> <th width="320"><a <?php echo $key_sort; ?> href="apikeys.php?<?php echo $qstr; ?>&sort=key">API Key</a></th> - <th width="100"><a <?php echo $status_sort; ?> href="apikeys.php?<?php echo $qstr; ?>&sort=status">Status</a></th> <th width="120"><a <?php echo $ip_sort; ?> href="apikeys.php?<?php echo $qstr; ?>&sort=ip">IP Addr.</a></th> + <th width="100"><a <?php echo $status_sort; ?> href="apikeys.php?<?php echo $qstr; ?>&sort=status">Status</a></th> + <th width="150" nowrap><a <?php echo $date_sort; ?>href="apikeys.php?<?php echo $qstr; ?>&sort=date">Date Added</a></th> <th width="150" nowrap><a <?php echo $updated_sort; ?>href="apikeys.php?<?php echo $qstr; ?>&sort=updated">Last Updated</a></th> </tr> </thead> @@ -75,10 +75,10 @@ else <td width=7px> <input type="checkbox" class="ckb" name="ids[]" value="<?php echo $row['id']; ?>" <?php echo $sel?'checked="checked"':''; ?>> </td> - <td> <?php echo Format::db_date($row['created']); ?></td> <td> <a href="apikeys.php?id=<?php echo $row['id']; ?>"><?php echo Format::htmlchars($row['apikey']); ?></a></td> - <td><?php echo $row['isactive']?'Active':'<b>Disabled</b>'; ?></td> <td><?php echo $row['ipaddr']; ?></td> + <td><?php echo $row['isactive']?'Active':'<b>Disabled</b>'; ?></td> + <td> <?php echo Format::db_date($row['created']); ?></td> <td> <?php echo Format::db_datetime($row['updated']); ?></td> </tr> <?php diff --git a/include/staff/group.inc.php b/include/staff/group.inc.php index f2935d72e56237ce35dbc701f563fdeaf475a829..bfcc2a596a54850211a71ff72ea95cb698ecdb57 100644 --- a/include/staff/group.inc.php +++ b/include/staff/group.inc.php @@ -76,6 +76,14 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <i>Ability to edit tickets.</i> </td> </tr> + <tr><td>Can <b>Post Reply</b></td> + <td> + <input type="radio" name="can_post_ticket_reply" value="1" <?php echo $info['can_post_ticket_reply']?'checked="checked"':''; ?> />Yes + + <input type="radio" name="can_post_ticket_reply" value="0" <?php echo !$info['can_post_ticket_reply']?'checked="checked"':''; ?> />No + <i>Ability to post a ticket reply.</i> + </td> + </tr> <tr><td>Can <b>Close</b> Tickets</td> <td> <input type="radio" name="can_close_tickets" value="1" <?php echo $info['can_close_tickets']?'checked="checked"':''; ?> />Yes @@ -105,7 +113,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <input type="radio" name="can_delete_tickets" value="1" <?php echo $info['can_delete_tickets']?'checked="checked"':''; ?> />Yes <input type="radio" name="can_delete_tickets" value="0" <?php echo !$info['can_delete_tickets']?'checked="checked"':''; ?> />No - <i>Deleted tickets can't be recovered!</i> + <i>Ability to delete tickets (Deleted tickets can't be recovered!)</i> </td> </tr> <tr><td>Can Ban Emails</td> @@ -132,6 +140,14 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <i>Ability to add/update/disable/delete knowledgebase categories and FAQs.</i> </td> </tr> + <tr><td>Can View Staff Stats.</td> + <td> + <input type="radio" name="can_view_staff_stats" value="1" <?php echo $info['can_view_staff_stats']?'checked="checked"':''; ?> />Yes + + <input type="radio" name="can_view_staff_stats" value="0" <?php echo !$info['can_view_staff_stats']?'checked="checked"':''; ?> />No + <i>Ability to view stats of other staff members in allowed departments.</i> + </td> + </tr> <tr> <th colspan="2"> <em><strong>Department Access</strong>: Check all departments the group members are allowed to access. <a id="selectAll" href="#deptckb">Select All</a> <a id="selectNone" href="#deptckb">Select None</a> </em> diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php index 53bdd909b901e9c61be7ba1da9579c5e5bb81aef..b14a4654af003d0c5dd6b85635d8d91a63048940 100644 --- a/include/staff/ticket-view.inc.php +++ b/include/staff/ticket-view.inc.php @@ -348,7 +348,11 @@ if(!$cfg->showNotesInline()) { ?> <div id="response_options"> <ul> + <?php + if($thisstaff->canPostReply()) { ?> <li><a id="reply_tab" href="#reply">Post Reply</a></li> + <?php + } ?> <li><a id="note_tab" href="#note">Post Internal Note</a></li> <?php if($thisstaff->canTransferTickets()) { ?> @@ -361,12 +365,12 @@ if(!$cfg->showNotesInline()) { ?> <?php } ?> </ul> - + <?php + if($thisstaff->canPostReply()) { ?> <form id="reply" action="tickets.php?id=<?php echo $ticket->getId(); ?>#reply" name="reply" method="post" enctype="multipart/form-data"> <?php csrf_token(); ?> <input type="hidden" name="id" value="<?php echo $ticket->getId(); ?>"> <input type="hidden" name="msgId" value="<?php echo $msgId; ?>"> - <input type="hidden" name="locktime" value="<?php echo $cfg->getLockTime(); ?>"> <input type="hidden" name="a" value="reply"> <span class="error"></span> <table border="0" cellspacing="0" cellpadding="3"> @@ -485,9 +489,12 @@ if(!$cfg->showNotesInline()) { ?> <input class="btn_sm" type="reset" value="Reset"> </p> </form> + <?php + } ?> <form id="note" action="tickets.php?id=<?php echo $ticket->getId(); ?>#note" name="note" method="post" enctype="multipart/form-data"> <?php csrf_token(); ?> <input type="hidden" name="id" value="<?php echo $ticket->getId(); ?>"> + <input type="hidden" name="locktime" value="<?php echo $cfg->getLockTime(); ?>"> <input type="hidden" name="a" value="postnote"> <table border="0" cellspacing="0" cellpadding="3"> <?php diff --git a/include/upgrader/sql/00ff231f-9f3b454c.patch.sql b/include/upgrader/sql/00ff231f-9f3b454c.patch.sql new file mode 100644 index 0000000000000000000000000000000000000000..7c3e3bd3c681e1e75f07cb87fe5496c5116da859 --- /dev/null +++ b/include/upgrader/sql/00ff231f-9f3b454c.patch.sql @@ -0,0 +1,38 @@ +/** + * @version v1.7 RC4 + * @signature 9f3b454c06dfd5ee96003eae5182ac13 + * + * - Supports starts- and ends-with in ticket filter rules + * - Fix assigned template variable + * - Allow nested templates to have duplicate names + * - New permission settings for API key & groups + */ + +ALTER TABLE `%TABLE_PREFIX%filter_rule` CHANGE `how` `how` ENUM( 'equal', + 'not_equal', 'contains', 'dn_contain', 'starts', 'ends' ); + +-- templates -> %message +UPDATE `%TABLE_PREFIX%email_template` + SET `assigned_alert_body` = REPLACE(`assigned_alert_body`, '%message', '%{comments}'); + +-- API Access. +ALTER TABLE `%TABLE_PREFIX%api_key` + CHANGE `isactive` `isactive` TINYINT( 1 ) UNSIGNED NOT NULL DEFAULT '1', + ADD `can_create_tickets` TINYINT( 1 ) UNSIGNED NOT NULL DEFAULT '1' AFTER `apikey`, + DROP INDEX `ipaddr`, + ADD INDEX `ipaddr` ( `ipaddr` ); + +-- Help topics +ALTER TABLE `%TABLE_PREFIX%help_topic` + DROP INDEX `topic` , + ADD UNIQUE `topic` ( `topic` , `topic_pid` ); + + +-- group settings. +ALTER TABLE `%TABLE_PREFIX%groups` + ADD `can_post_ticket_reply` TINYINT( 1 ) UNSIGNED NOT NULL DEFAULT '1' AFTER `can_transfer_tickets` , + ADD `can_view_staff_stats` TINYINT( 1 ) UNSIGNED NOT NULL DEFAULT '0' AFTER `can_post_ticket_reply`; + +-- update schema signature. +UPDATE `%TABLE_PREFIX%config` + SET `schema_signature`='9f3b454c06dfd5ee96003eae5182ac13'; diff --git a/main.inc.php b/main.inc.php index 5b5fc593165e2c627cf4bcbf75ce58125501b0d2..baf06e4ec7773b2d104b517e4bde2aa3e46d2e5f 100644 --- a/main.inc.php +++ b/main.inc.php @@ -63,7 +63,7 @@ #Current version && schema signature (Changes from version to version) define('THIS_VERSION','1.7-RC3+'); //Shown on admin panel - define('SCHEMA_SIGNATURE','00ff231f2ade8797a0e7f2a7fccd52f4'); //MD5 signature of the db schema. (used to trigger upgrades) + define('SCHEMA_SIGNATURE','9f3b454c06dfd5ee96003eae5182ac13'); //MD5 signature of the db schema. (used to trigger upgrades) #load config info $configfile=''; if(file_exists(ROOT_DIR.'ostconfig.php')) //Old installs prior to v 1.6 RC5 diff --git a/scp/js/ticket.js b/scp/js/ticket.js index 65ee84aad0882649f8aec44daf447b45d38e64d9..8cb792d1c403661063ea564c3bff3b8938bc9d50 100644 --- a/scp/js/ticket.js +++ b/scp/js/ticket.js @@ -94,7 +94,7 @@ var autoLock = { Init: function(config) { //make sure we are on ticket view page & locking is enabled! - var fObj=$('form#reply'); + var fObj=$('form#note'); if(!fObj || !$(':input[name=id]',fObj).length || !$(':input[name=locktime]',fObj).length diff --git a/scp/js/upgrader.js b/scp/js/upgrader.js index 8fe378193842494d7bed4df290828e05e091a263..1631ac5cd5a7a156498f44e6f13020dae875dde2 100644 --- a/scp/js/upgrader.js +++ b/scp/js/upgrader.js @@ -8,7 +8,7 @@ jQuery(function($) { height : $(window).height() }); - $("#loading").css({ + $("#upgrading").css({ top : ($(window).height() / 3), left : ($(window).width() / 2 - 160) }); diff --git a/scp/tickets.php b/scp/tickets.php index 1eee98d0c6dec68a90fd26bd8804f9c3190dae26..17384ae50b627ab56bb1b8e9fe5c0df01034e062 100644 --- a/scp/tickets.php +++ b/scp/tickets.php @@ -42,18 +42,23 @@ if($_POST && !$errors): $statusKeys=array('open'=>'Open','Reopen'=>'Open','Close'=>'Closed'); switch(strtolower($_POST['a'])): case 'reply': + if(!$thisstaff->canPostReply()) + $errors['err'] = 'Action denied. Contact admin for access'; + else { - if(!$_POST['msgId']) - $errors['err']='Missing message ID - Internal error'; - if(!$_POST['response']) - $errors['response']='Response required'; - //Use locks to avoid double replies - if($lock && $lock->getStaffId()!=$thisstaff->getId()) - $errors['err']='Action Denied. Ticket is locked by someone else!'; + if(!$_POST['msgId']) + $errors['err']='Missing message ID - Internal error'; + if(!$_POST['response']) + $errors['response']='Response required'; + + //Use locks to avoid double replies + if($lock && $lock->getStaffId()!=$thisstaff->getId()) + $errors['err']='Action Denied. Ticket is locked by someone else!'; - //Make sure the email is not banned - if(!$errors['err'] && TicketFilter::isBanned($ticket->getEmail())) - $errors['err']='Email is in banlist. Must be removed to reply.'; + //Make sure the email is not banned + if(!$errors['err'] && TicketFilter::isBanned($ticket->getEmail())) + $errors['err']='Email is in banlist. Must be removed to reply.'; + } $wasOpen =($ticket->isOpen()); //If no error...do the do. @@ -470,7 +475,7 @@ if($cfg->showAnsweredTickets()) { } if($stats['assigned']) { - if(!$ost->getWarning() && $stats['assigned']>3) + if(!$ost->getWarning() && $stats['assigned']>10) $ost->setWarning($stats['assigned'].' tickets assigned to you! Do something about it!'); $nav->addSubMenu(array('desc'=>'My Tickets ('.$stats['assigned'].')', diff --git a/setup/inc/sql/osTicket-mysql.sql b/setup/inc/sql/osTicket-mysql.sql index 5d2f4ceefdb5d912ef5cf7b84f9b1e3a91508d75..004b9934092948798fc84adbe2e987af6006cd2b 100644 --- a/setup/inc/sql/osTicket-mysql.sql +++ b/setup/inc/sql/osTicket-mysql.sql @@ -5,11 +5,12 @@ CREATE TABLE `%TABLE_PREFIX%api_key` ( `isactive` tinyint(1) NOT NULL default '1', `ipaddr` varchar(64) NOT NULL, `apikey` varchar(255) NOT NULL, + `can_create_tickets` TINYINT( 1 ) UNSIGNED NOT NULL DEFAULT '1', `notes` text, `updated` datetime NOT NULL, `created` datetime NOT NULL, PRIMARY KEY (`id`), - UNIQUE KEY `ipaddr` (`ipaddr`), + KEY `ipaddr` (`ipaddr`), UNIQUE KEY `apikey` (`apikey`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; @@ -133,7 +134,7 @@ CREATE TABLE `%TABLE_PREFIX%config` ( `assigned_alert_team_members` tinyint(1) unsigned NOT NULL default '0', `auto_assign_reopened_tickets` tinyint(1) unsigned NOT NULL default '1', `show_related_tickets` tinyint(1) unsigned NOT NULL default '1', - `show_assigned_tickets` tinyint(1) unsigned NOT NULL default '0', + `show_assigned_tickets` tinyint(1) unsigned NOT NULL default '1', `show_answered_tickets` tinyint(1) unsigned NOT NULL default '0', `show_notes_inline` tinyint(1) unsigned NOT NULL default '1', `hide_staff_name` tinyint(1) unsigned NOT NULL default '0', @@ -265,7 +266,7 @@ CREATE TABLE `%TABLE_PREFIX%filter_rule` ( `id` int(11) unsigned NOT NULL auto_increment, `filter_id` int(10) unsigned NOT NULL default '0', `what` enum('name','email','subject','body','header') NOT NULL, - `how` enum('equal','not_equal','contains','dn_contain') NOT NULL, + `how` enum('equal','not_equal','contains','dn_contain','starts','ends') NOT NULL, `val` varchar(255) NOT NULL, `isactive` tinyint(1) unsigned NOT NULL DEFAULT '1', `notes` tinytext NOT NULL, @@ -355,6 +356,7 @@ CREATE TABLE `%TABLE_PREFIX%groups` ( `group_name` varchar(50) NOT NULL default '', `can_create_tickets` tinyint(1) unsigned NOT NULL default '1', `can_edit_tickets` tinyint(1) unsigned NOT NULL default '1', + `can_post_ticket_reply` tinyint( 1 ) unsigned NOT NULL DEFAULT '1', `can_delete_tickets` tinyint(1) unsigned NOT NULL default '0', `can_close_tickets` tinyint(1) unsigned NOT NULL default '1', `can_assign_tickets` tinyint(1) unsigned NOT NULL default '1', @@ -362,6 +364,7 @@ CREATE TABLE `%TABLE_PREFIX%groups` ( `can_ban_emails` tinyint(1) unsigned NOT NULL default '0', `can_manage_premade` tinyint(1) unsigned NOT NULL default '0', `can_manage_faq` tinyint(1) unsigned NOT NULL default '0', + `can_view_staff_stats` tinyint( 1 ) unsigned NOT NULL DEFAULT '0', `notes` text, `created` datetime NOT NULL, `updated` datetime NOT NULL, @@ -402,7 +405,7 @@ CREATE TABLE `%TABLE_PREFIX%help_topic` ( `created` datetime NOT NULL, `updated` datetime NOT NULL, PRIMARY KEY (`topic_id`), - UNIQUE KEY `topic` (`topic`), + UNIQUE KEY `topic` ( `topic` , `topic_pid` ), KEY `topic_pid` (`topic_pid`), KEY `priority_id` (`priority_id`), KEY `dept_id` (`dept_id`), diff --git a/setup/inc/sql/osTicket-mysql.sql.md5 b/setup/inc/sql/osTicket-mysql.sql.md5 index 4aa4254e6af1e5496dc39cf89726de1a607bab14..49a6e93dd4e013257c9abf0f1117e12241ee381c 100644 --- a/setup/inc/sql/osTicket-mysql.sql.md5 +++ b/setup/inc/sql/osTicket-mysql.sql.md5 @@ -1 +1 @@ -00ff231f2ade8797a0e7f2a7fccd52f4 +9f3b454c06dfd5ee96003eae5182ac13 diff --git a/setup/install.php b/setup/install.php index 5f590da1c915d1c2c794e8363b41abb8a5bb3afa..130983a692155869064ae08b8ad47e88f4a6b2f2 100644 --- a/setup/install.php +++ b/setup/install.php @@ -105,7 +105,12 @@ switch(strtolower($_SESSION['ost_installer']['s'])) { break; default: //Fail IF any of the old config files exists. - if(file_exists(INCLUDE_DIR.'settings.php') || file_exists(ROOT_DIR.'ostconfig.php')) + if(file_exists(INCLUDE_DIR.'settings.php') + || file_exists(ROOT_DIR.'ostconfig.php') + || (file_exists(OSTICKET_CONFIGFILE) + && preg_match("/define\('OSTINSTALLED',TRUE\)\;/i", + file_get_contents(OSTICKET_CONFIGFILE))) + ) $inc='file-unclean.inc.php'; else $inc='install-prereq.inc.php'; diff --git a/setup/upgrade.php b/setup/upgrade.php new file mode 100644 index 0000000000000000000000000000000000000000..2dfc5b475031ca9cf727962648d7c83db381ede5 --- /dev/null +++ b/setup/upgrade.php @@ -0,0 +1,4 @@ +<?php +/* Simply redirect to Admin Panel - new upgrade home */ +header('Location: ../scp/upgrade.php'); +?>