diff --git a/api/.htaccess b/api/.htaccess index e73e2eb3b9c3f1204223b4426274817c0200e279..f460420d6d2f55f362fb420788b6751afe38bbba 100644 --- a/api/.htaccess +++ b/api/.htaccess @@ -1,8 +1,11 @@ -RewriteEngine On +<IfModule mod_rewrite.c> -RewriteBase /api/ +RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d +RewriteCond %{REQUEST_URI} (.*/api) + +RewriteRule ^(.*)$ %1/http.php/$1 [L] -RewriteRule ^(.*)$ http.php/$1 [L] +</IfModule> 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 29dfcff1d1aa10a20386589927830ffed8211911..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; @@ -90,31 +94,38 @@ $var['header']=$parser->getHeader(); $var['priorityId']=$cfg->useEmailPriority()?$parser->getPriority():0; $ticket=null; -if(preg_match ("[[#][0-9]{1,10}]",$var['subject'],$regs)) { +if(preg_match ("[[#][0-9]{1,10}]", $var['subject'], $regs)) { $extid=trim(preg_replace("/[^0-9]/", "", $regs[0])); - $ticket= new Ticket(Ticket::getIdByExtId($extid)); - //Allow mismatched emails?? For now hell NO. - if(!is_object($ticket) || strcasecmp($ticket->getEmail(),$var['email'])) - $ticket=null; + if(!($ticket=Ticket::lookupByExtId($extid, $var['email'])) || strcasecmp($ticket->getEmail(), $var['email'])) + $ticket = null; } + $errors=array(); $msgid=0; -if(!$ticket) { //New tickets... - $ticket=Ticket::create($var,$errors,'email'); - if(!is_object($ticket) || $errors) { - api_exit(EX_DATAERR,'Ticket create Failed '.implode("\n",$errors)."\n\n"); - } +if($ticket) { + //post message....postMessage does the cleanup. + if(!($msgid=$ticket->postMessage($var['message'], 'Email',$var['mid'],$var['header']))) + api_exit(EX_DATAERR, 'Unable to post message'); +} elseif(($ticket=Ticket::create($var, $errors, 'email'))) { // create new ticket. $msgid=$ticket->getLastMsgId(); +} else { // failure.... -} else { - //post message....postMessage does the cleanup. - if(!($msgid=$ticket->postMessage($var['message'], 'Email',$var['mid'],$var['header']))) { - api_exit(EX_DATAERR, 'Unable to post message'); + // report success on hard rejection + if(isset($errors['errno']) && $errors['errno'] == 403) + api_exit(EX_SUCCESS); + + // check if it's a bounce! + if($var['header'] && TicketFilter::isAutoBounce($var['header'])) { + $ost->logWarning('Bounced email', $var['message'], false); + api_exit(EX_SUCCESS); } + + api_exit(EX_DATAERR, 'Ticket create Failed '.implode("\n",$errors)."\n\n"); } + //Ticket created...save attachments if enabled. -if($cfg->allowEmailAttachments() && ($attachments=$parser->getAttachments())) { +if($ticket && $cfg->allowEmailAttachments() && ($attachments=$parser->getAttachments())) { foreach($attachments as $attachment) { if($attachment['filename'] && $ost->isFileTypeAllowed($attachment['filename'])) $ticket->saveAttachment(array('name' => $attachment['filename'], 'data' => $attachment['body']), $msgid, 'M'); diff --git a/include/ajax.reports.php b/include/ajax.reports.php index 7fbe19c89c81605725572e45433ef20ea374fdcf..bf86374055bd45b8e07a1860a157d77d3b53aa64 100644 --- a/include/ajax.reports.php +++ b/include/ajax.reports.php @@ -37,10 +37,15 @@ class OverviewReportAjaxAPI extends AjaxController { function getData() { global $thisstaff; - $start = $this->get('start', 'last month'); - $stop = $this->get('stop', 'now'); - if (substr($stop, 0, 1) == '+') - $stop = $start . $stop; + if(($start = $this->get('start', 'last month'))) { + $stop = $this->get('stop', 'now'); + if (substr($stop, 0, 1) == '+') + $stop = $start . $stop; + } else { + $start = 'last month'; + $stop = 'now'; + } + $start = 'FROM_UNIXTIME('.strtotime($start).')'; $stop = 'FROM_UNIXTIME('.strtotime($stop).')'; @@ -72,9 +77,11 @@ class OverviewReportAjaxAPI extends AjaxController { "filter" => ('T1.staff_id=S1.staff_id AND - (T1.staff_id='.db_input($thisstaff->getDeptId()) + (T1.staff_id='.db_input($thisstaff->getId()) .(($depts=$thisstaff->getManagedDepartments())? - (' OR T1.staff_id IN('.implode(',', db_input($depts)).')'):'') + (' OR T1.dept_id IN('.implode(',', db_input($depts)).')'):'') + .(($thisstaff->canViewStaffStats())? + (' OR T1.dept_id IN('.implode(',', db_input($thisstaff->getDepts())).')'):'') .')' ) ) @@ -166,10 +173,17 @@ class OverviewReportAjaxAPI extends AjaxController { } function getPlotData() { - $start = $this->get('start', 'last month'); - $stop = $this->get('stop', 'now'); - if (substr($stop, 0, 1) == '+') - $stop = $start . $stop; + + + if(($start = $this->get('start', 'last month'))) { + $stop = $this->get('stop', 'now'); + if (substr($stop, 0, 1) == '+') + $stop = $start . $stop; + } else { + $start = 'last month'; + $stop = 'now'; + } + $start = strtotime($start); $stop = strtotime($stop); diff --git a/include/api.ticket.php b/include/api.ticket.php index 4fcae4b183925dfe32bb91c95275a986fc492ea6..7fd5ba713c8237aad483c4a44b08989cf9b17c3f 100644 --- a/include/api.ticket.php +++ b/include/api.ticket.php @@ -15,14 +15,16 @@ class TicketController extends ApiController { "attachments" => array("*" => array("name", "type", "data", "encoding") ), - "message", "ip" + "message", "ip", "priorityId" ); if ($format == "xml") return array("ticket" => $supported); else return $supported; } 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); @@ -43,7 +45,7 @@ class TicketController extends ApiController { if (!($info["data"] = base64_decode($info["data"], true))) Http::response(400, sprintf( "%s: Poorly encoded base64 data", - $filename)); + $info['name'])); } $info['size'] = strlen($info['data']); } diff --git a/include/class.api.php b/include/class.api.php index 1bd25463484a4e2f01a1d72050dde4e92d89d967..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 @@ -184,7 +208,7 @@ class ApiController { function validate($data, $structure, $prefix="") { foreach ($data as $key=>$info) { if (is_array($structure) and is_array($info)) { - $search = isset($structure[$key]) ? $key : "*"; + $search = (isset($structure[$key]) && !is_numeric($key)) ? $key : "*"; if (isset($structure[$search])) { $this->validate($info, $structure[$search], "$prefix$key/"); continue; @@ -219,16 +243,21 @@ class ApiXmlDataParser extends XmlDataParser { } else if ($key == "autorespond") { $value = (bool)$value; } else if ($key == "attachments") { - foreach ($value as &$info) { - $info["data"] = $info[":text"]; - unset($info[":text"]); + if(!isset($value['file'][':text'])) + $value = $value['file']; + + if($value && is_array($value)) { + foreach ($value as &$info) { + $info["data"] = $info[":text"]; + unset($info[":text"]); + } + unset($info); } - unset($info); - } - if (is_array($value)) { + } else if(is_array($value)) { $value = $this->fixup($value); } } + return $current; } } @@ -243,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; @@ -255,7 +284,7 @@ class ApiJsonDataParser extends JsonDataParser { # PHP5: fopen("data://$data[5:]"); if (substr($data, 0, 5) != "data:") { $info = array( - "data" => $data, + "data" => $data, "type" => "text/plain", "name" => key($info)); } else { @@ -264,11 +293,17 @@ class ApiJsonDataParser extends JsonDataParser { list($type, $extra) = explode(";", $meta); $info = array( "data" => $contents, - "type" => $type, + "type" => ($type) ? $type : "text/plain", "name" => key($info)); if (substr($extra, -6) == "base64") $info["encoding"] = "base64"; - # TODO: Handle 'charset' hint in $extra + # Handle 'charset' hint in $extra, such as + # data:text/plain;charset=iso-8859-1,Blah + # Convert to utf-8 since it's the encoding scheme + # for the database. Otherwise, assume utf-8 + list($param,$charset) = explode('=', $extra); + if ($param == 'charset' && function_exists('iconv')) + $contents = iconv($charset, "UTF-8", $contents); } } unset($value); diff --git a/include/class.config.php b/include/class.config.php index 3aff085f77be0820d61c980a3445a889e7dc19d5..c7e9669742d26f9e45063e02d26d1b2a7737e16a 100644 --- a/include/class.config.php +++ b/include/class.config.php @@ -868,11 +868,11 @@ class Config { .',transfer_alert_active='.db_input($vars['transfer_alert_active']) .',transfer_alert_assigned='.db_input(isset($vars['transfer_alert_assigned'])?1:0) .',transfer_alert_dept_manager='.db_input(isset($vars['transfer_alert_dept_manager'])?1:0) - .',transfer_alert_dept_members='.db_input(isset($var['transfer_alert_dept_members'])?1:0) + .',transfer_alert_dept_members='.db_input(isset($vars['transfer_alert_dept_members'])?1:0) .',overdue_alert_active='.db_input($vars['overdue_alert_active']) .',overdue_alert_assigned='.db_input(isset($vars['overdue_alert_assigned'])?1:0) .',overdue_alert_dept_manager='.db_input(isset($vars['overdue_alert_dept_manager'])?1:0) - .',overdue_alert_dept_members='.db_input(isset($var['overdue_alert_dept_members'])?1:0) + .',overdue_alert_dept_members='.db_input(isset($vars['overdue_alert_dept_members'])?1:0) .',send_sys_errors='.db_input(isset($vars['send_sys_errors'])?1:0) .',send_sql_errors='.db_input(isset($vars['send_sql_errors'])?1:0) .',send_login_errors='.db_input(isset($vars['send_login_errors'])?1:0) diff --git a/include/class.email.php b/include/class.email.php index e6b10c3892ece68f9d0410a77116f67cf7de61dd..c38144c4b1281fabf18ffd90b7e4a3937ff4cd90 100644 --- a/include/class.email.php +++ b/include/class.email.php @@ -142,7 +142,6 @@ class Email { function send($to, $subject, $message, $attachments=null, $options=null) { - $mailer = new Mailer($this); if($attachments) $mailer->addAttachments($attachments); @@ -150,6 +149,16 @@ class Email { return $mailer->send($to, $subject, $message, $options); } + function sendAutoReply($to, $subject, $message, $attachments=null, $options=array()) { + $options+= array('autoreply' => true); + return $this->send($to, $subject, $message, $attachments, $options); + } + + function sendAlert($to, $subject, $message, $attachments=null, $options=array()) { + $options+= array('bulk' => true); + return $this->send($to, $subject, $message, $attachments, $options); + } + function update($vars,&$errors) { $vars=$vars; $vars['cpasswd']=$this->getPasswd(); //Current decrypted password. diff --git a/include/class.file.php b/include/class.file.php index 364e7f14ac570d3cc0c2bb2fa074bb05eef1c29a..da8ad76dc0ed9e9917596829fa7a6450f8176b2d 100644 --- a/include/class.file.php +++ b/include/class.file.php @@ -181,7 +181,7 @@ class AttachmentFile { $sql='INSERT INTO '.FILE_TABLE.' SET created=NOW() ' .',type='.db_input($file['type']) .',size='.db_input($file['size']) - .',name='.db_input($file['name']) + .',name='.db_input(Format::file_name($file['name'])) .',hash='.db_input($file['hash']); if (!(db_query($sql) && ($id=db_insert_id()))) diff --git a/include/class.filter.php b/include/class.filter.php index 9c172edaabaf3f819df46e49e5fd2da33eb90d62..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... @@ -866,30 +870,69 @@ class TicketFilter { * http://msdn.microsoft.com/en-us/library/ee219609(v=exchg.80).aspx */ /* static */ function isAutoResponse($headers) { + + if($headers && !is_array($headers)) + $headers = Mail_Parse::splitHeaders($headers); + $auto_headers = array( 'Auto-Submitted' => 'AUTO-REPLIED', 'Precedence' => array('AUTO_REPLY', 'BULK', 'JUNK', 'LIST'), 'Subject' => array('OUT OF OFFICE', 'AUTO-REPLY:', 'AUTORESPONSE'), 'X-Autoreply' => 'YES', - 'X-Auto-Response-Suppress' => 'OOF', + 'X-Auto-Response-Suppress' => array('ALL', 'DR', 'RN', 'NRN', 'OOF', 'AutoReply'), 'X-Autoresponse' => '', 'X-Auto-Reply-From' => '' ); + foreach ($auto_headers as $header=>$find) { - if ($value = strtoupper($headers[$header])) { - # Search text must be found at the beginning of the header - # value. This is especially import for something like the - # subject line, where something like an autoreponse may - # appear somewhere else in the value. - if (is_array($find)) { - foreach ($find as $f) - if (strpos($value, $f) === 0) - return true; - } elseif (strpos($value, $find) === 0) { - return true; - } + if(!isset($headers[$header])) continue; + + $value = strtoupper($headers[$header]); + # Search text must be found at the beginning of the header + # value. This is especially import for something like the + # subject line, where something like an autoreponse may + # appear somewhere else in the value. + + if (is_array($find)) { + foreach ($find as $f) + if (strpos($value, $f) === 0) + return true; + } elseif (strpos($value, $find) === 0) { + return true; + } + } + + # Bounces also counts as auto-responses. + if(self::isAutoBounce($headers)) + return true; + + return false; + } + + function isAutoBounce($headers) { + + if($headers && !is_array($headers)) + $headers = Mail_Parse::splitHeaders($headers); + + $bounce_headers = array( + 'From' => array('<MAILER-DAEMON@MAILER-DAEMON>', 'MAILER-DAEMON', '<>'), + 'Subject' => array('DELIVERY FAILURE', 'DELIVERY STATUS', 'UNDELIVERABLE:'), + ); + + foreach ($bounce_headers as $header => $find) { + if(!isset($headers[$header])) continue; + + $value = strtoupper($headers[$header]); + + if (is_array($find)) { + foreach ($find as $f) + if (strpos($value, $f) === 0) + return true; + } elseif (strpos($value, $find) === 0) { + return true; } } + return false; } @@ -903,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.format.php b/include/class.format.php index aaa6667d32c2fce14da2cb9f5e15387ec9608b0c..b510221e02b41a8da8b4bfd27278cfdb9e5d5e9d 100644 --- a/include/class.format.php +++ b/include/class.format.php @@ -31,10 +31,7 @@ class Format { } function file_name($filename) { - - $search = array('/ß/','/ä/','/Ä/','/ö/','/Ö/','/ü/','/Ü/','([^[:alnum:]._])'); - $replace = array('ss','ae','Ae','oe','Oe','ue','Ue','_'); - return preg_replace($search,$replace,$filename); + return preg_replace('/\s+/', '_', $filename); } /* re-arrange $_FILES array for the sane */ 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.mailer.php b/include/class.mailer.php index 2a965eb371cf3d3cf952e48be313f533bdd084a5..57242e61605b77f241c5ec09973d28a1518b4836 100644 --- a/include/class.mailer.php +++ b/include/class.mailer.php @@ -92,9 +92,9 @@ class Mailer { require_once (PEAR_DIR.'Mail/mime.php'); // PEAR Mail_Mime packge //do some cleanup - $to=preg_replace("/(\r\n|\r|\n)/s",'', trim($to)); - $subject=stripslashes(preg_replace("/(\r\n|\r|\n)/s",'', trim($subject))); - $body = stripslashes(preg_replace("/(\r\n|\r)/s", "\n", trim($message))); + $to = preg_replace("/(\r\n|\r|\n)/s",'', trim($to)); + $subject = preg_replace("/(\r\n|\r|\n)/s",'', trim($subject)); + $body = preg_replace("/(\r\n|\r)/s", "\n", trim($message)); /* Message ID - generated for each outgoing email */ $messageId = sprintf('<%s%d-%s>', Misc::randCode(6), time(), @@ -107,7 +107,20 @@ class Mailer { 'Date'=> date('D, d M Y H:i:s O'), 'Message-ID' => $messageId, 'X-Mailer' =>'osTicket Mailer' - ); + ); + + //Set bulk/auto-response headers. + if($options && ($options['autoreply'] or $options['bulk'])) { + $headers+= array( + 'X-Autoreply' => 'yes', + 'X-Auto-Response-Suppress' => 'ALL, AutoReply', + 'Auto-Submitted' => 'auto-replied'); + + if($options['bulk']) + $headers+= array('Precedence' => 'bulk'); + else + $headers+= array('Precedence' => 'auto_reply'); + } $mime = new Mail_mime(); $mime->setTXTBody($body); diff --git a/include/class.mailfetch.php b/include/class.mailfetch.php index aa43ce89e960231bdf2e25bf0f622a24d2b44741..c567819cbdb27b6e4fe5910865b4a36c0c01faa9 100644 --- a/include/class.mailfetch.php +++ b/include/class.mailfetch.php @@ -196,23 +196,25 @@ class MailFetcher { //Convert text to desired encoding..defaults to utf8 function mime_encode($text, $charset=null, $enc='utf-8') { //Thank in part to afterburner - if(function_exists('iconv') and $text) { + if(function_exists('iconv') and ($charset or function_exists('mb_detect_encoding'))) { if($charset) return iconv($charset, $enc.'//IGNORE', $text); elseif(function_exists('mb_detect_encoding')) return iconv(mb_detect_encoding($text, $this->encodings), $enc, $text); + } elseif(function_exists('iconv_mime_decode')) { + return iconv_mime_decode($text, 0, $enc); } return utf8_encode($text); } - //Generic decoder - mirrors imap_utf8 + //Generic decoder - resuting text is utf8 encoded -> mirrors imap_utf8 function mime_decode($text) { $str = ''; $parts = imap_mime_header_decode($text); foreach ($parts as $part) - $str.= $part->text; + $str.= $this->mime_encode($part->text, ($part->charset=='default'?'ASCII':$part->charset), 'utf-8'); return $str?$str:imap_utf8($text); } @@ -310,15 +312,31 @@ class MailFetcher { if($part && !$part->parts) { //Check if the part is an attachment. $filename = ''; - if($part->ifdisposition && in_array(strtolower($part->disposition), array('attachment', 'inline'))) + if($part->ifdisposition && in_array(strtolower($part->disposition), array('attachment', 'inline'))) { $filename = $part->dparameters[0]->value; - elseif($part->ifparameters && $part->type == 5) //inline image without disposition. + //Some inline attachments have multiple parameters. + if(count($part->dparameters)>1) { + foreach($part->dparameters as $dparameter) { + if(strcasecmp($dparameter->attribute, 'FILENAME')) continue; + $filename = $dparameter->value; + break; + } + } + } elseif($part->ifparameters && $part->type == 5) { //inline image without disposition. $filename = $part->parameters[0]->value; + if(count($part->parameters)>1) { + foreach($part->parameters as $parameter) { + if(strcasecmp($parameter->attribute, 'FILENAME')) continue; + $filename = $parameter->value; + break; + } + } + } if($filename) { return array( array( - 'name' => $filename, + 'name' => $this->mime_decode($filename), 'mime' => $this->getMimeType($part), 'encoding' => $part->encoding, 'index' => ($index?$index:1) @@ -378,7 +396,7 @@ class MailFetcher { //Is the email address banned? if($mailinfo['email'] && TicketFilter::isBanned($mailinfo['email'])) { //We need to let admin know... - $ost->logWarning('Ticket denied', 'Banned email - '.$mailinfo['email']); + $ost->logWarning('Ticket denied', 'Banned email - '.$mailinfo['email'], false); return true; //Report success (moved or delete) } @@ -417,6 +435,16 @@ class MailFetcher { } elseif (($ticket=Ticket::create($var, $errors, 'Email'))) { $msgid = $ticket->getLastMsgId(); } else { + //Report success if the email was absolutely rejected. + if(isset($errors['errno']) && $errors['errno'] == 403) + return true; + + # check if it's a bounce! + if($var['header'] && TicketFilter::isAutoBounce($var['header'])) { + $ost->logWarning('Bounced email', $var['message'], false); + return true; + } + //TODO: Log error.. return null; } diff --git a/include/class.osticket.php b/include/class.osticket.php index 86d206f212d6450db9755ac4f5c77926ff2a53c8..fd300184a96b985b1c1b1022817b2b11e85fc4aa 100644 --- a/include/class.osticket.php +++ b/include/class.osticket.php @@ -26,7 +26,16 @@ define('LOG_WARN',LOG_WARNING); class osTicket { var $loglevel=array(1=>'Error','Warning','Debug'); + + //Page errors. var $errors; + + //System + var $system; + + + + var $warning; var $message; @@ -136,14 +145,19 @@ class osTicket { $errors=0; foreach($files as &$file) { - if(!$this->isFileTypeAllowed($file)) - $file['error']='Invalid file type for '.$file['name']; + //skip no file upload "error" - why PHP calls it an error is beyond me. + if($file['error'] && $file['error']==UPLOAD_ERR_NO_FILE) continue; + + if($file['error']) //PHP defined error! + $file['error'] = 'File upload error #'.$file['error']; + elseif(!$file['tmp_name'] || !is_uploaded_file($file['tmp_name'])) + $file['error'] = 'Invalid or bad upload POST'; + elseif(!$this->isFileTypeAllowed($file)) + $file['error'] = 'Invalid file type for '.$file['name']; elseif($file['size']>$this->getConfig()->getMaxFileSize()) - $file['error']=sprintf('File (%s) is too big. Maximum of %s allowed', + $file['error'] = sprintf('File (%s) is too big. Maximum of %s allowed', $file['name'], Format::file_size($this->getConfig()->getMaxFileSize())); - elseif(!$file['error'] && !is_uploaded_file($file['tmp_name'])) - $file['error']='Invalid or bad upload POST'; - + if($file['error']) $errors++; } @@ -182,18 +196,15 @@ class osTicket { } function setErrors($errors) { - if(!is_array($errors)) - return $this->setError($errors); - $this->errors = $errors; } function getError() { - return $this->errors['err']; + return $this->system['err']; } function setError($error) { - $this->errors['err'] = $error; + $this->system['error'] = $error; } function clearError() { @@ -201,11 +212,11 @@ class osTicket { } function getWarning() { - return $this->warning; + return $this->system['warning']; } - function setWarning($warn) { - $this->warning = $warn; + function setWarning($warning) { + $this->system['warning'] = $warning; } function clearWarning() { @@ -213,16 +224,16 @@ class osTicket { } - function getMessage() { - return $this->message; + function getNotice() { + return $this->system['notice']; } - function setMessage($msg) { - $this->message = $msg; + function setNotice($notice) { + $this->system['notice'] = $notice; } - function clearMessage() { - $this->setMessage(''); + function clearNotice() { + $this->setNotice(''); } @@ -242,7 +253,7 @@ class osTicket { $email=$this->getConfig()->getDefaultEmail(); //will take the default email. if($email) { - $email->send($to, $subject, $message); + $email->sendAlert($to, $subject, $message); } else {//no luck - try the system mail. Email::sendmail($to, $subject, $message, sprintf('"osTicket Alerts"<%s>',$to)); } 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.ticket.php b/include/class.ticket.php index 50e8173611715153187190c253ea98aec2a77759..5a3fd83f3883c432b17bf89d4ac08d6b4ee95de6 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -867,8 +867,7 @@ class Ticket { if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator())) $msg['body'] ="\n$tag\n\n".$msg['body']; - //TODO: add auto flags....be nice to mail servers and sysadmins!! - $email->send($this->getEmail(), $msg['subj'], $msg['body']); + $email->sendAutoReply($this->getEmail(), $msg['subj'], $msg['body']); } if(!($email=$cfg->getAlertEmail())) @@ -885,7 +884,7 @@ class Ticket { //Alert admin?? if($cfg->alertAdminONNewTicket()) { $alert = str_replace('%{recipient}', 'Admin', $msg['body']); - $email->send($cfg->getAdminEmail(), $msg['subj'], $alert); + $email->sendAlert($cfg->getAdminEmail(), $msg['subj'], $alert); $sentlist[]=$cfg->getAdminEmail(); } @@ -901,7 +900,7 @@ class Ticket { foreach( $recipients as $k=>$staff){ if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(),$sentlist)) continue; $alert = str_replace('%{recipient}', $staff->getFirstName(), $msg['body']); - $email->send($staff->getEmail(), $msg['subj'], $alert); + $email->sendAlert($staff->getEmail(), $msg['subj'], $alert); $sentlist[] = $staff->getEmail(); } @@ -934,7 +933,7 @@ class Ticket { $msg = $this->replaceVars($msg, array('signature' => ($dept && $dept->isPublic())?$dept->getSignature():'')); - $email->send($this->getEmail(), $msg['subj'], $msg['body']); + $email->sendAutoReply($this->getEmail(), $msg['subj'], $msg['body']); } $client= $this->getClient(); @@ -976,7 +975,7 @@ class Ticket { $autorespond=$dept->autoRespONNewMessage(); - if(!$autorespond && !$cfg->autoRespONNewMessage()) return; //no autoresp or alerts. + if(!$autorespond || !$cfg->autoRespONNewMessage()) return; //no autoresp or alerts. $this->reload(); @@ -997,7 +996,7 @@ class Ticket { if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator())) $msg['body'] ="\n$tag\n\n".$msg['body']; - $email->send($this->getEmail(), $msg['subj'], $msg['body']); + $email->sendAutoReply($this->getEmail(), $msg['subj'], $msg['body']); } } @@ -1056,7 +1055,7 @@ class Ticket { foreach( $recipients as $k=>$staff) { if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(),$sentlist)) continue; $alert = str_replace('%{recipient}', $staff->getFirstName(), $msg['body']); - $email->send($staff->getEmail(), $msg['subj'], $alert); + $email->sendAlert($staff->getEmail(), $msg['subj'], $alert); $sentlist[] = $staff->getEmail(); } } @@ -1109,7 +1108,7 @@ class Ticket { foreach( $recipients as $k=>$staff){ if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(),$sentlist)) continue; $alert = str_replace("%{recipient}", $staff->getFirstName(), $msg['body']); - $email->send($staff->getEmail(), $msg['subj'], $alert); + $email->sendAlert($staff->getEmail(), $msg['subj'], $alert); $sentlist[] = $staff->getEmail(); } @@ -1294,7 +1293,7 @@ class Ticket { foreach( $recipients as $k=>$staff){ if(!is_object($staff) || !$staff->isAvailable() || in_array($staff->getEmail(),$sentlist)) continue; $alert = str_replace('%{recipient}',$staff->getFirstName(), $msg['body']); - $email->send($staff->getEmail(), $msg['subj'], $alert); + $email->sendAlert($staff->getEmail(), $msg['subj'], $alert); $sentlist[] = $staff->getEmail(); } } @@ -1451,9 +1450,9 @@ class Ticket { $sentlist=array(); //I know it sucks...but..it works. foreach( $recipients as $k=>$staff){ - if(!$staff || !$staff->getEmail() || !$staff->isAvailable() && in_array($staff->getEmail(),$sentlist)) continue; + if(!$staff || !$staff->getEmail() || !$staff->isAvailable() || in_array($staff->getEmail(), $sentlist)) continue; $alert = str_replace('%{recipient}', $staff->getFirstName(), $msg['body']); - $email->send($staff->getEmail(), $msg['subj'], $alert); + $email->sendAlert($staff->getEmail(), $msg['subj'], $alert); $sentlist[] = $staff->getEmail(); } } @@ -1503,7 +1502,7 @@ class Ticket { $msg['body'] ="\n$tag\n\n".$msg['body']; $attachments =($cfg->emailAttachments() && $files)?$this->getAttachments($respId, 'R'):array(); - $email->send($this->getEmail(), $msg['subj'], $msg['body'], $attachments); + $email->sendAutoReply($this->getEmail(), $msg['subj'], $msg['body'], $attachments); } return $respId; @@ -1717,7 +1716,7 @@ class Ticket { if(!$staff || !is_object($staff) || !$staff->getEmail() || !$staff->isAvailable()) continue; if(in_array($staff->getEmail(),$sentlist) || ($staffId && $staffId==$staff->getId())) continue; $alert = str_replace('%{recipient}',$staff->getFirstName(), $msg['body']); - $email->send($staff->getEmail(), $msg['subj'], $alert, $attachments); + $email->sendAlert($staff->getEmail(), $msg['subj'], $alert, $attachments); $sentlist[] = $staff->getEmail(); } } @@ -2019,6 +2018,7 @@ class Ticket { //Make sure the email address is not banned if(TicketFilter::isBanned($vars['email'])) { $errors['err']='Ticket denied. Error #403'; + $errors['errno'] = 403; $ost->logWarning('Ticket denied', 'Banned email - '.$vars['email']); return 0; } @@ -2044,6 +2044,7 @@ class Ticket { if($ticket_filter && ($filter=$ticket_filter->shouldReject())) { $errors['err']='Ticket denied. Error #403'; + $errors['errno'] = 403; $ost->logWarning('Ticket denied', sprintf('Ticket rejected ( %s) by filter "%s"', $vars['email'], $filter->getName())); 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/class.upgrader.php b/include/class.upgrader.php index 012f88d1160bea1fb09d2104129d19e8fccebd3e..e437fcad7acd8d9b69045eb65655fa0f4ad835a5 100644 --- a/include/class.upgrader.php +++ b/include/class.upgrader.php @@ -67,7 +67,7 @@ class Upgrader extends SetupWizard { $subject = 'Upgrader Error'; if($email) { - $email->send($thisstaff->getEmail(), $subject, $error); + $email->sendAlert($thisstaff->getEmail(), $subject, $error); } else {//no luck - try the system mail. Mailer::sendmail($thisstaff->getEmail(), $subject, $error, sprintf('"osTicket Alerts"<%s>', $thisstaff->getEmail())); } diff --git a/include/class.variable.php b/include/class.variable.php index f83ba61be9063d385943b4fe4f4fdc751a58f422..7d49ce592855e3d65bb5d41165bcbad6b5346bc9 100644 --- a/include/class.variable.php +++ b/include/class.variable.php @@ -98,7 +98,7 @@ class VariableReplacer { if(!($vars=$this->_parse($input))) return $input; - return preg_replace($this->_delimit(array_keys($vars)), array_values($vars), $input); + return str_replace(array_keys($vars), array_values($vars), $input); } function _resolveVar($var) { @@ -134,14 +134,5 @@ class VariableReplacer { return $vars; } - - //Helper function - will be replaced by a lambda function (PHP 5.3+) - function _delimit($val, $d='/') { - - if($val && is_array($val)) - return array_map(array($this, '_delimit'), $val); - - return $d.$val.$d; - } } ?> diff --git a/include/class.xml.php b/include/class.xml.php index 854f182372c94f57338b8b45a94d155f69f5bd33..56baf4fbccaf65985921e081afd8e5f475c95c07 100644 --- a/include/class.xml.php +++ b/include/class.xml.php @@ -77,10 +77,14 @@ class XmlDataParser { $this->content = array_pop($this->stack); $i = 1; if (array_key_exists($name, $this->content)) { - while (array_key_exists("$name$i", $this->content)) $i++; - $name = "$name$i"; - } - $this->content[$name] = $prev; + if(!isset($this->content[$name][0])) { + $current = $this->content[$name]; + unset($this->content[$name]); + $this->content[$name][0] = $current; + } + $this->content[$name][] = $prev; + } else + $this->content[$name] = $prev; } function content($parser, $data) { diff --git a/include/client/open.inc.php b/include/client/open.inc.php index 2fd076100388888765970d1a561dc3c5235a8b90..275e856dea07f8726f06b3fa6641392e459bb2e4 100644 --- a/include/client/open.inc.php +++ b/include/client/open.inc.php @@ -57,7 +57,7 @@ $info=($_POST && $errors)?Format::htmlchars($_POST):$info; <td class="required">Help Topic:</td> <td> <select id="topicId" name="topicId"> - <option value="" selected="selected">— Select a Help Topics —</option> + <option value="" selected="selected">— Select a Help Topic —</option> <?php if($topics=Topic::getPublicHelpTopics()) { foreach($topics as $id =>$name) { @@ -82,7 +82,7 @@ $info=($_POST && $errors)?Format::htmlchars($_POST):$info; <tr> <td class="required">Message:</td> <td> - <div><em>Please provide as much details as possible so we can best assist you.</em> <font class="error">* <?php echo $errors['message']; ?></font></div> + <div><em>Please provide as much detail as possible so we can best assist you.</em> <font class="error">* <?php echo $errors['message']; ?></font></div> <textarea id="message" cols="60" rows="8" name="message"><?php echo $info['message']; ?></textarea> </td> </tr> 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/footer.inc.php b/include/staff/footer.inc.php index d789c14474ef63caf5c06f61d60cdfb6df881bcc..90373479f3b1270bc0375826953a92c6657db403 100644 --- a/include/staff/footer.inc.php +++ b/include/staff/footer.inc.php @@ -13,5 +13,9 @@ if(is_object($thisstaff) && $thisstaff->isStaff()) { ?> } ?> </div> <div id="overlay"></div> +<div id="loading"> + <h4>Please Wait!</h4> + <p>Please wait... it will take a second!</p> +</div> </body> </html> 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/header.inc.php b/include/staff/header.inc.php index a349cb3c6ecb662f2d82f64086fc4984e295c263..8475f328606a9e70892a97daef6ebb0e96aa7352 100644 --- a/include/staff/header.inc.php +++ b/include/staff/header.inc.php @@ -31,6 +31,14 @@ </head> <body> <div id="container"> + <?php + if($ost->getError()) + echo sprintf('<div id="error_bar">%s</div>', $ost->getError()); + elseif($ost->getWarning()) + echo sprintf('<div id="warning_bar">%s</div>', $ost->getWarning()); + elseif($ost->getNotice()) + echo sprintf('<div id="notice_bar">%s</div>', $ost->getNotice()); + ?> <div id="header"> <a href="index.php" id="logo">osTicket - Customer Support System</a> <p id="info">Howdy, <strong><?php echo $thisstaff->getUserName(); ?></strong> diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php index c2cb0bd1067684f7a012aa4f1a44cc73dc8ee904..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 @@ -507,7 +514,7 @@ if(!$cfg->showNotesInline()) { ?> <span class="error">* <?php echo $errors['note']; ?></span></div> <textarea name="note" id="internal_note" cols="80" rows="9" wrap="soft"><?php echo $info['note']; ?></textarea><br> <div> - <span class="faded">Note title - sumarry of the note (optional)</span> + <span class="faded">Note title - summarry of the note (optional)</span> <span class="error" <?php echo $errors['title']; ?></span> </div> <input type="text" name="title" id="title" size="60" value="<?php echo $info['title']; ?>" > diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php index 534a358bfcc01bbb771340f78db2b5fcd7924cbd..d6fab690cdcd76cc9a8c7dd9e590a6c60231a6b4 100644 --- a/include/staff/tickets.inc.php +++ b/include/staff/tickets.inc.php @@ -190,35 +190,40 @@ $sortOptions=array('date'=>'ticket.created','ID'=>'ticketID','pri'=>'priority_ur $orderWays=array('DESC'=>'DESC','ASC'=>'ASC'); //Sorting options... +$queue = isset($_REQUEST['status'])?strtolower($_REQUEST['status']):$status; $order_by=$order=null; if($_REQUEST['sort'] && $sortOptions[$_REQUEST['sort']]) $order_by =$sortOptions[$_REQUEST['sort']]; -elseif(!strcasecmp($status, 'open') && !$showanswered && $sortOptions[$_SESSION['tickets']['sort']]) { - $_REQUEST['sort'] = $_SESSION['tickets']['sort']; - $order_by = $sortOptions[$_SESSION['tickets']['sort']]; - $order = $_SESSION['tickets']['order']; +elseif($sortOptions[$_SESSION[$queue.'_tickets']['sort']]) { + $_REQUEST['sort'] = $_SESSION[$queue.'_tickets']['sort']; + $_REQUEST['order'] = $_SESSION[$queue.'_tickets']['order']; + + $order_by = $sortOptions[$_SESSION[$queue.'_tickets']['sort']]; + $order = $_SESSION[$queue.'_tickets']['order']; } if($_REQUEST['order'] && $orderWays[strtoupper($_REQUEST['order'])]) $order=$orderWays[strtoupper($_REQUEST['order'])]; //Save sort order for sticky sorting. -if(!strcasecmp($status, 'open') && $_REQUEST['sort']) { - $_SESSION['tickets']['sort'] = $_REQUEST['sort']; - $_SESSION['tickets']['order'] = $_REQUEST['order']; +if($_REQUEST['sort'] && $queue) { + $_SESSION[$queue.'_tickets']['sort'] = $_REQUEST['sort']; + $_SESSION[$queue.'_tickets']['order'] = $_REQUEST['order']; } -if(!$order_by && $showanswered) { - $order_by='ticket.lastresponse, ticket.created'; //No priority sorting for answered tickets. -}elseif(!$order_by && !strcasecmp($status,'closed')){ - $order_by='ticket.closed, ticket.created'; //No priority sorting for closed tickets. +//Set default sort by columns. +if(!$order_by ) { + if($showanswered) + $order_by='ticket.lastresponse, ticket.created'; //No priority sorting for answered tickets. + elseif(!strcasecmp($status,'closed')) + $order_by='ticket.closed, ticket.created'; //No priority sorting for closed tickets. + else + $order_by='priority_urgency ASC, effective_date, ticket.created'; } -$order_by =$order_by?$order_by:'priority_urgency, effective_date, ticket.created'; -$order=$order?$order:'ASC'; - -if($order_by && strpos($order_by,',')) - $order_by=str_replace(','," $order,",$order_by); +$order=$order?$order:'DESC'; +if($order_by && strpos($order_by,',') && $order) + $order_by=preg_replace('/(?<!ASC|DESC),/', " $order,", $order_by); $sort=$_REQUEST['sort']?strtolower($_REQUEST['sort']):'urgency'; //Urgency is not on display table. $x=$sort.'_sort'; @@ -302,7 +307,7 @@ $negorder=$order=='DESC'?'ASC':'DESC'; //Negate the sorting.. <a class="refresh" href="<?php echo $_SERVER['REQUEST_URI']; ?>">Refresh</a> <input type="hidden" name="a" value="mass_process" > <input type="hidden" name="do" id="action" value="" > - <input type="hidden" name="status" value="<?php echo $status; ?>" > + <input type="hidden" name="status" value="<?php echo $_REQUEST['status']; ?>" > <table class="list" border="0" cellspacing="1" cellpadding="2" width="940"> <caption><?php echo $showing; ?> <?php echo $results_type; ?></caption> <thead> @@ -468,6 +473,11 @@ $negorder=$order=='DESC'?'ASC':'DESC'; //Negate the sorting.. <input class="button" type="submit" name="close" value="Close"> <?php break; + case 'overdue': + ?> + <input class="button" type="submit" name="close" value="Close"> + <?php + break; default: //search?? ?> <input class="button" type="submit" name="close" value="Close" > 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/include/upgrader/upgrade.inc.php b/include/upgrader/upgrade.inc.php index 7c8a8aae47e692d9337a51c46a46f2114c4a2848..fae6947d94849c6cfbd4a7df849df708e470f1de 100644 --- a/include/upgrader/upgrade.inc.php +++ b/include/upgrader/upgrade.inc.php @@ -32,7 +32,7 @@ $action=$upgrader->getNextAction(); <p>3. We can help, feel free to <a href="http://osticket.com/support/" target="_blank">contact us </a> for professional help.</p> </div> <div class="clear"></div> - <div id="loading"> + <div id="upgrading"> <h4><?php echo $action; ?></h4> Please wait... while we upgrade your osTicket installation! <div id="msg" style="font-weight: bold;padding-top:10px;">Smile!</div> diff --git a/l.php b/l.php index 5e605c73cb3d48c32409c8ff89f727199bcbbba3..286a17299cd1e51850b3760ed4e6d264244f7974 100644 --- a/l.php +++ b/l.php @@ -21,7 +21,7 @@ if (!$url || !Validator::is_url($url)) exit('Invalid url'); <html> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8"/> - <meta http-equiv="refresh" content="0;<?php echo $url; ?>"/> + <meta http-equiv="refresh" content="0;URL=<?php echo $url; ?>"/> </head> <body/> </html> diff --git a/main.inc.php b/main.inc.php index 4a775b233bb00f45331b891e5f6fe1d4f202268d..43aeca4d617c5d5eec310df9899611073ddf9250 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-RC3'); //Shown on admin panel - define('SCHEMA_SIGNATURE','00ff231f2ade8797a0e7f2a7fccd52f4'); //MD5 signature of the db schema. (used to trigger upgrades) + define('THIS_VERSION','1.7-RC4'); //Shown on admin panel + 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/admin.inc.php b/scp/admin.inc.php index 8a41c54db7f690b3caec9ee7728345bba1570000..66fca5d976bb3b9c4a76eed99b71657ab28f0d64 100644 --- a/scp/admin.inc.php +++ b/scp/admin.inc.php @@ -25,6 +25,7 @@ define('OSTADMININC',TRUE); //checked by admin include files define('ADMINPAGE',TRUE); //Used by the header to swap menus. //Some security related warnings - bitch until fixed!!! :) +$sysnotice= ''; if($ost->isUpgradePending()) { $errors['err']=$sysnotice='System upgrade is pending <a href="upgrade.php">Upgrade Now</a>'; if(!in_array(basename($_SERVER['SCRIPT_NAME']), array('upgrade.php', 'logs.php'))) { @@ -57,6 +58,9 @@ if($ost->isUpgradePending()) { $sysnotice='Please consider turning off register globals if possible'; } +//System notice displayed as a warning (if any). +$ost->setWarning($sysnotice); + //Admin navigation - overwrites what was set in staff.inc.php $nav = new AdminNav($thisstaff); diff --git a/scp/css/scp.css b/scp/css/scp.css index 59964d4e61561ba78bc704e3e6ddb2d1a73af964..04bd90d604e5a6265b159e49279792db58fbe4ef 100644 --- a/scp/css/scp.css +++ b/scp/css/scp.css @@ -43,12 +43,18 @@ a { color: #555; } -#msg_notice { margin: 0; padding: 5px 10px 5px 36px; height: 16px; line-height: 16px; margin-bottom: 10px; border: 1px solid #0a0; background: url('../images/icons/ok.png?1300763726') 10px 50% no-repeat #e0ffe0; } +#msg_notice { margin: 0; padding: 5px 10px 5px 36px; height: 16px; line-height: 16px; margin-bottom: 10px; border: 1px solid #0a0; background: url('../images/icons/ok.png') 10px 50% no-repeat #e0ffe0; } -#msg_warning { margin: 0; padding: 5px 10px 5px 36px; height: 16px; line-height: 16px; margin-bottom: 10px; border: 1px solid #f26522; background: url('../images/icons/alert.png?1307823786') 10px 50% no-repeat #ffffdd; } +#msg_warning { margin: 0; padding: 5px 10px 5px 36px; height: 16px; line-height: 16px; margin-bottom: 10px; border: 1px solid #f26522; background: url('../images/icons/alert.png') 10px 50% no-repeat #ffffdd; } #msg_error { margin: 0; padding: 5px 10px 5px 36px; height: 16px; line-height: 16px; margin-bottom: 10px; border: 1px solid #a00; background: url('../images/icons/error.png') 10px 50% no-repeat #fff0f0; } +#notice_bar { margin: 0; padding: 5px 10px 5px 36px; height: 16px; line-height: 16px; border: 1px solid #0a0; background: url('../images/icons/ok.png') 10px 50% no-repeat #e0ffe0; } + +#warning_bar { margin: 0; padding: 5px 10px 5px 36px; height: 16px; line-height: 16px; border: 1px solid #f26522; background: url('../images/icons/alert.png') 10px 50% no-repeat #ffffdd; } + +#error_bar { margin: 0; padding: 5px 10px 5px 36px; height: 16px; line-height: 16px; border: 1px solid #a00; background: url('../images/icons/error.png') 10px 50% no-repeat #fff0f0; } + #container { width:960px; @@ -311,6 +317,7 @@ a.Icon:hover { .Icon.webTicket { background:url(../images/icons/ticket_source_web.gif) 0 0 no-repeat; } .Icon.emailTicket { background:url(../images/icons/ticket_source_email.gif) 0 0 no-repeat; } .Icon.phoneTicket { background:url(../images/icons/ticket_source_phone.gif) 0 0 no-repeat; } +.Icon.apiTicket { background:url(../images/icons/ticket_source_other.gif) 0 0 no-repeat; } .Icon.otherTicket { background:url(../images/icons/ticket_source_other.gif) 0 0 no-repeat; } .Icon.overdueTicket { background:url(../images/icons/overdue_ticket.gif) 0 0 no-repeat; } .Icon.assignedTicket { background:url(../images/icons/assigned_ticket.gif) 0 0 no-repeat; } @@ -1372,8 +1379,28 @@ ul.progress li.no small {color:red;} #bar.error { background: #ffd; text-align: center; color: #a00; font-weight: bold; } /* Overlay */ -#overlay { display: none; position: fixed; background: #000; z-index: 1000; } +#overlay { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: #000; + z-index: 1000; + -webkit-transform: translate3d(0,0,0); +} + +#loading, #upgrading { + border:1px solid #2a67ac; + padding: 10px 10px 10px 60px; + width: 300px; + height: 100px; + background: rgb( 255, 255, 255) url('../images/FhHRx-Spinner.gif') 10px 50% no-repeat; + position: fixed; + display: none; + z-index: 3000; +} -#loading { padding: 10px 10px 10px 60px; width: 300px; height: 100px; background: url('../images/ajax-loader.gif?1312925608') 10px 50% no-repeat white; position: fixed; display: none; z-index: 3000; } -#loading h4 { margin: 3px 0 0 0; padding: 0; color: #d80; } +#loading h4, #upgrading h4 { margin: 3px 0 0 0; padding: 0; color: #d80; } diff --git a/scp/emailtest.php b/scp/emailtest.php index 0ca1d7308cc8c62f0f9852a57f45d4e8802e805a..b39e5c7edaf82004217ad39f6c9bf8fe6b99e995 100644 --- a/scp/emailtest.php +++ b/scp/emailtest.php @@ -47,7 +47,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); $nav->setTabActive('emails'); require(STAFFINC_DIR.'header.inc.php'); ?> -<form action="emailtest.php" method="post" id="emailtest"> +<form action="emailtest.php" method="post" id="save"> <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <h2>Test Outgoing Email</h2> diff --git a/scp/images/FhHRx-Spinner.gif b/scp/images/FhHRx-Spinner.gif new file mode 100644 index 0000000000000000000000000000000000000000..574b1d4a161707c6c7f0ba9d2bc531bead4a3028 Binary files /dev/null and b/scp/images/FhHRx-Spinner.gif differ diff --git a/scp/js/dashboard.inc.js b/scp/js/dashboard.inc.js index 0f9e31a54dccdff96388d7304b0187f2d8dc9fad..ba19235f12ec5198cb2c074bc083f37b1344ed4d 100644 --- a/scp/js/dashboard.inc.js +++ b/scp/js/dashboard.inc.js @@ -9,9 +9,7 @@ $.ajax({ method: 'GET', url: 'ajax.php/report/overview/graph', - data: ((this.start && this.start.value) ? { - 'start': this.start.value, - 'stop': this.period.value} : {}), + data: $(this).serialize(), dataType: 'json', success: function(json) { var times = [], @@ -136,6 +134,7 @@ start = this.start.value || 'last month'; stop = this.period.value || 'now'; } + var group = current_tab.attr('table-group'); $.ajax({ method: 'GET', @@ -250,7 +249,7 @@ // ------------------------> Export <----------------------- $('<a>').attr({'href':'ajax.php/report/overview/table/export?group=' - +group}).append('Export') + +group+'&start='+start+'&stop='+stop}).append('Export') .appendTo($('<li>') .appendTo(p)); @@ -259,6 +258,10 @@ }); return false; } - $(refresh); - $(function() { $('#timeframe-form').submit(refresh); }); + + $(function() { + $('#timeframe-form').submit(refresh); + //Trigger submit now...init. + $('#timeframe-form').submit(); + }); })(window.jQuery); diff --git a/scp/js/scp.js b/scp/js/scp.js index 71d0e69a823e0869f38ec3e9a5562bec296a7baa..cf32e42d8b638ff1b465cb23eb9a196e72be31ba 100644 --- a/scp/js/scp.js +++ b/scp/js/scp.js @@ -103,6 +103,14 @@ $(document).ready(function(){ return false; }); + $(window).scroll(function () { + + $('.dialog').css({ + top : (($(this).height() /5)+$(this).scrollTop()), + left : ($(this).width() / 2 - 300) + }); + }); + if($.browser.msie) { $('.inactive').mouseenter(function() { var elem = $(this); @@ -137,9 +145,9 @@ $(document).ready(function(){ } }); - $('form#save').submit(function() { + $('form#save, form:has(table.list)').submit(function() { $(window).unbind('beforeunload'); - + $('#overlay, #loading').show(); return true; }); @@ -316,9 +324,7 @@ $(document).ready(function(){ $('#overlay').css({ opacity : 0.3, top : 0, - left : 0, - width : $(window).width(), - height : $(window).height() + left : 0 }); //Dialog @@ -341,6 +347,12 @@ $(document).ready(function(){ left : ($(window).width() / 2 - 300) }); + /* loading ... */ + $("#loading").css({ + top : ($(window).height() / 3), + left : ($(window).width() / 2 - 160) + }); + $('#go-advanced').click(function(e) { e.preventDefault(); $('#result-count').html(''); 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 09bde7766c6f39e9f95e0b50ccf3c0efc3bbb2fb..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) }); @@ -17,7 +17,7 @@ jQuery(function($) { e.preventDefault(); var form = $(this); $('input[type=submit]', this).attr('disabled', 'disabled'); - $('#overlay, #loading').show(); + $('#overlay, #upgrading').show(); doTasks('upgrade.php',form.serialize()); return false; diff --git a/scp/l.php b/scp/l.php index 93fff3a24894612017f53ff0bb0a119b656f8b9d..dec8c0a6a52dd62c0d02f3edebfeeeaa79c0c8fa 100644 --- a/scp/l.php +++ b/scp/l.php @@ -21,7 +21,7 @@ if (!$url || !Validator::is_url($url)) exit('Invalid url'); <html> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8"/> - <meta http-equiv="refresh" content="0;<?php echo $url; ?>"/> + <meta http-equiv="refresh" content="0; URL=<?php echo $url; ?>"/> </head> <body/> </html> diff --git a/scp/staff.inc.php b/scp/staff.inc.php index 8553a8bac1fef78482532de7789723af43e6c20d..8a91e4774803886deed8abce8e62bab01ffaff87 100644 --- a/scp/staff.inc.php +++ b/scp/staff.inc.php @@ -119,10 +119,11 @@ $nav = new StaffNav($thisstaff); if($thisstaff->forcePasswdChange() && !$exempt) { # XXX: Call staffLoginPage() for AJAX and API requests _not_ to honor # the request + $sysnotice = 'Password change required to continue'; require('profile.php'); //profile.php must request this file as require_once to avoid problems. exit; } - +$ost->setWarning($sysnotice); $ost->setPageTitle('osTicket :: Staff Control Panel'); ?> diff --git a/scp/tickets.php b/scp/tickets.php index 47395d592c2da9cb505daec7d037fb1349711dfa..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. @@ -341,7 +346,7 @@ if($_POST && !$errors): foreach($_POST['tids'] as $k=>$v) { if(($t=Ticket::lookup($v)) && $t->isClosed() && @$t->reopen()) { $i++; - $t->logNote('Ticket Reopened', $note); + $t->logNote('Ticket Reopened', $note, $thisstaff); } } @@ -361,7 +366,7 @@ if($_POST && !$errors): foreach($_POST['tids'] as $k=>$v) { if(($t=Ticket::lookup($v)) && $t->isOpen() && @$t->close()) { $i++; - $t->logNote('Ticket Closed', $note); + $t->logNote('Ticket Closed', $note, $thisstaff); } } @@ -380,7 +385,7 @@ if($_POST && !$errors): foreach($_POST['tids'] as $k=>$v) { if(($t=Ticket::lookup($v)) && !$t->isOverdue() && $t->markOverdue()) { $i++; - $t->logNote('Ticket Marked Overdue', $note); + $t->logNote('Ticket Marked Overdue', $note, $thisstaff); } } @@ -470,8 +475,8 @@ if($cfg->showAnsweredTickets()) { } if($stats['assigned']) { - if(!$sysnotice && $stats['assigned']>10) - $sysnotice=$stats['assigned'].' assigned to you!'; + 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'].')', 'title'=>'Assigned Tickets', diff --git a/setup/doc/api.md b/setup/doc/api.md new file mode 100644 index 0000000000000000000000000000000000000000..7509616aa2406b842f56cacd7a6c0693b23415ca --- /dev/null +++ b/setup/doc/api.md @@ -0,0 +1,29 @@ +osTicket API +============ + +The osTicket API is implemented as (somewhat) simple XML or JSON over HTTP. +For now, only ticket creation is supported, but eventually, all resources +inside osTicket will be accessible and modifiable via the API. + +Authentication +-------------- + +Authentication via the API is done via API keys configured inside the +osTicket admin panel. API keys are created and tied to a source IP address, +which will be checked against the source IP of requests to the HTTP API. + +API keys can be created and managed via the admin panel. Navigate to Manage +-> API keys. Use *Add New API Key* to create a new API key. Currently, no +special configuration is required to allow the API key to be used for the +HTTP API. All API keys are valid for the HTTP API. + +Wrappers +-------- + +Currently, there are no wrappers for the API. If you've written one and +would like it on the list, submit a pull request to add your wrapper. + +Resources +--------- + +- [Tickets](api/tickets.md) diff --git a/setup/doc/api/tickets.md b/setup/doc/api/tickets.md new file mode 100644 index 0000000000000000000000000000000000000000..fc79b1aead833a9f4cf9fd3a214635dcba9e52fc --- /dev/null +++ b/setup/doc/api/tickets.md @@ -0,0 +1,128 @@ +Tickets +======= +The API supports ticket creation via the HTTP API (as well as via email, +etc.). Currently, the API support creation of tickets only -- so no +modifications and deletions of existing tickets is possible via the API for +now. + +Create a Ticket +--------------- + +Tickets can be created in the osTicket system by sending an HTTP POST to +`api/tickets.xml` or `api/tickets.json` depending on the format of the +request content. + +### Fields ###### + +* __email__: *required* Email address of the submitter +* __name__: *required* Name of the submitter +* __subject__: *required* Subject of the ticket +* __message__: *required* Initial message for the ticket thread +* __alert__: If unset, disable alerts to staff. Default is `true` +* __autorespond__: If unset, disable autoresponses. Default is `true` +* __ip__: IP address of the submitter +* __phone__: Phone number of the submitter +* __phone_ext__: Phone number extension -- can also be embedded in *phone* +* __priorityId__: Priority *id* for the new ticket to assume +* __source__: Source of the ticket, default is `API` +* __topicId__: Help topic *id* associated with the ticket +* __attachments__: An array of files to attach to the initial message. + Each attachment must have some content and also the + following fields: + * __name__: *required* name of the file to be attached. Multiple files + with the same name are allowable + * __type__: Mime type of the file. Default is `text/plain` + * __encoding__: Set to `base64` if content is base64 encoded + +### XML Payload Example ###### + +* `POST /api/tickets.xml` + +The XML data format is extremely lax. Content can be either sent as an +attribute or a named element if it has no sub-content. + +In the example below, the simple element could also be replaced as +attributes on the root `<ticket>` element; however, if a `CDATA` element is +necessary to hold special content, or difficult content such as double +quotes is to be embedded, simple sub-elements are also supported. + +Notice that the phone extension can be sent as the `@ext` attribute of the +`phone` sub-element. + +``` xml +<?xml version="1.0" encoding="UTF-8"?> +<ticket alert="true" autorespond="true" source="API"> + <name>Angry User</name> + <email>api@osticket.com</email> + <subject>Testing API</subject> + <phone ext="123">318-555-8634</phone> + <message><![CDATA[Message content here]]></message> + <attachments> + <file name="file.txt" type="text/plain"><![CDATA[ + File content is here and is automatically trimmed + ]]></file> + <file name="image.gif" type="image/gif" encoding="base64"> + R0lGODdhMAAwAPAAAAAAAP///ywAAAAAMAAwAAAC8IyPqcvt3wCcDkiLc7C0qwy + GHhSWpjQu5yqmCYsapyuvUUlvONmOZtfzgFzByTB10QgxOR0TqBQejhRNzOfkVJ + +5YiUqrXF5Y5lKh/DeuNcP5yLWGsEbtLiOSpa/TPg7JpJHxyendzWTBfX0cxOnK + PjgBzi4diinWGdkF8kjdfnycQZXZeYGejmJlZeGl9i2icVqaNVailT6F5iJ90m6 + mvuTS4OK05M0vDk0Q4XUtwvKOzrcd3iq9uisF81M1OIcR7lEewwcLp7tuNNkM3u + Nna3F2JQFo97Vriy/Xl4/f1cf5VWzXyym7PHhhx4dbgYKAAA7 + </file> + </attachments> + <ip>123.211.233.122</ip> +</ticket> +``` + +### JSON Payload Example ### + +* `POST /api/tickets.json` + +Attachment data for the JSON content uses the [RFC 2397][] data URL format. +As described above, the content-type and base64 encoding hints are optional. +Furthermore, a character set can be optionally declared for each attachment +and will be automatically converted to UTF-8 for database storage. + +Notice that the phone number extension can be embedded in the `phone` value +denoted with a capital `X` + +Do also note that the JSON format forbids a comma after the last element in +an object or array definition, and newlines are not allowed inside strings. + +``` json +{ + "alert": true, + "autorespond": true, + "source": "API", + "name": "Angry User", + "email": "api@osticket.com", + "phone": "3185558634X123", + "subject": "Testing API", + "ip": "123.211.233.122", + "message": "MESSAGE HERE", + "attachments": [ + {"file.txt": "data:text/plain;charset=utf-8,content"}, + {"image.png": "data:image/png;base64,R0lGODdhMAA..."}, + ] +} +``` + +[rfc 2397]: http://www.ietf.org/rfc/rfc2397.txt "Data URLs" + +### Response ###### + +If successful, the server will send `HTTP/201 Created`. Otherwise, it will +send an appropriate HTTP status with the content being the error +description. Most likely offenders are + +* Required field not included +* Data type mismatch (text send for numeric field) +* Incorrectly encoded base64 data +* Unsupported field sent +* Incorrectly formatted content (bad JSON or XML) + +Upon success, the content of the response will be the external ticket id of +the newly-created ticket. + + Status: 201 Created + 123456 diff --git a/setup/inc/class.installer.php b/setup/inc/class.installer.php index 2d284ecedca1e48b25a2fdfd25ad912ec449912a..f355fbe04cf8a55aaa50bac9e9672131b3c3354a 100644 --- a/setup/inc/class.installer.php +++ b/setup/inc/class.installer.php @@ -89,6 +89,13 @@ class Installer extends SetupWizard { $this->errors['db']='Unable to create the database.'; } elseif(!db_select_database($vars['dbname'])) { $this->errors['dbname']='Unable to select the database'; + } else { + //Abort if we have another installation (or table) with same prefix. + $sql = 'SELECT * FROM `'.$vars['prefix'].'config` LIMIT 1'; + if(mysql_query($sql)) { + $this->errors['err'] = 'We have a problem - another installation with same table prefix exists!'; + $this->errors['prefix'] = 'Prefix already in-use'; + } } } diff --git a/setup/inc/file-perm.inc.php b/setup/inc/file-perm.inc.php index 7f93a302c266cabeb4a466e3f3951e14b508c90e..6a158082ddecadbb7552f52f43503cf29f7ac3bb 100644 --- a/setup/inc/file-perm.inc.php +++ b/setup/inc/file-perm.inc.php @@ -9,9 +9,9 @@ if(!defined('SETUPINC')) die('Kwaheri!'); </p> </div> <h3>Solution: <font color="red"><?php echo $errors['err']; ?></font></h3> - Please follow the instructions below to give read and write access to the web server. + Please follow the instructions below to give read and write access to the web server user. <ul> - <li><b>CLI</b>:<br><i>chmod 0777 include/ost-config.php</i></li> + <li><b>CLI</b>:<br><i>chmod 0666 include/ost-config.php</i></li> <li><b>FTP</b>:<br>Using WS_FTP this would be right hand clicking on the fil, selecting chmod, and then giving all permissions to the file.</li> <li><b>Cpanel</b>:<br>Click on the file, select change permission, and then giving all permissions to the file.</li> </ul> diff --git a/setup/inc/file-unclean.inc.php b/setup/inc/file-unclean.inc.php index 309fad10bc150d3beac23c937df1ebd448d42238..3c194b60ac2f078ecaef85cb3811ac9ed0741a6d 100644 --- a/setup/inc/file-unclean.inc.php +++ b/setup/inc/file-unclean.inc.php @@ -4,7 +4,7 @@ if(!defined('SETUPINC')) die('Kwaheri!'); <div id="main"> <h1 style="color:#FF7700;">osTicket is already installed?</h1> <div id="intro"> - <p>Configuration file already changed - which could mean osTicket is already installed or the config file is currupted. If you are trying to upgrade osTicket, then go to <a href="../scp/" >Admin Panel</a>.</p> + <p>Configuration file already changed - which could mean osTicket is already installed or the config file is currupted. If you are trying to upgrade osTicket, then go to <a href="../scp/admin.php" >Admin Panel</a>.</p> <p>If you believe this is in error, please try replacing the config file with a unchanged template copy and try again or get technical help.</p> <p>Refer to the <a target="_blank" href="http://osticket.com/wiki/Installation">Installation Guide</a> on the wiki for more information.</p> diff --git a/setup/inc/install.inc.php b/setup/inc/install.inc.php index 7f050baef479582a393d41e452b0adb3e42b4b8b..cb5f2d82bfd15b2fcb9e95087b443844d0f3a02a 100644 --- a/setup/inc/install.inc.php +++ b/setup/inc/install.inc.php @@ -16,13 +16,13 @@ $info=($_POST && $errors)?Format::htmlchars($_POST):array('prefix'=>'ost_','dbho </div> <div class="row"> <label>Helpdesk Name:</label> - <input type="text" name="name" size="30" tabindex="1" value="<?php echo $info['name']; ?>"> + <input type="text" name="name" size="45" tabindex="1" value="<?php echo $info['name']; ?>"> <a class="tip" href="#t1">?</a> <font class="error"><?php echo $errors['name']; ?></font> </div> <div class="row"> <label>Default Email:</label> - <input type="text" name="email" size="30" tabindex="2" value="<?php echo $info['email']; ?>"> + <input type="text" name="email" size="45" tabindex="2" value="<?php echo $info['email']; ?>"> <a class="tip" href="#t2">?</a> <font class="error"><?php echo $errors['email']; ?></font> </div> @@ -31,37 +31,37 @@ $info=($_POST && $errors)?Format::htmlchars($_POST):array('prefix'=>'ost_','dbho <span class="subhead">Your primary administrator account - you can add more users later.</span> <div class="row"> <label>First Name:</label> - <input type="text" name="fname" size="30" tabindex="3" value="<?php echo $info['fname']; ?>"> + <input type="text" name="fname" size="45" tabindex="3" value="<?php echo $info['fname']; ?>"> <a class="tip" href="#t3">?</a> <font class="error"><?php echo $errors['fname']; ?></font> </div> <div class="row"> <label>Last Name:</label> - <input type="text" name="lname" size="30" tabindex="4" value="<?php echo $info['lname']; ?>"> + <input type="text" name="lname" size="45" tabindex="4" value="<?php echo $info['lname']; ?>"> <a class="tip" href="#t4">?</a> <font class="error"><?php echo $errors['lname']; ?></font> </div> <div class="row"> <label>Email Address:</label> - <input type="text" name="admin_email" size="30" tabindex="5" value="<?php echo $info['admin_email']; ?>"> + <input type="text" name="admin_email" size="45" tabindex="5" value="<?php echo $info['admin_email']; ?>"> <a class="tip" href="#t5">?</a> <font class="error"><?php echo $errors['admin_email']; ?></font> </div> <div class="row"> <label>Username:</label> - <input type="text" name="username" size="30" tabindex="6" value="<?php echo $info['username']; ?>" autocomplete="off"> + <input type="text" name="username" size="45" tabindex="6" value="<?php echo $info['username']; ?>" autocomplete="off"> <a class="tip" href="#t6">?</a> <font class="error"><?php echo $errors['username']; ?></font> </div> <div class="row"> <label> Password:</label> - <input type="password" name="passwd" size="30" tabindex="7" value="<?php echo $info['passwd']; ?>" autocomplete="off"> + <input type="password" name="passwd" size="45" tabindex="7" value="<?php echo $info['passwd']; ?>" autocomplete="off"> <a class="tip" href="#t7">?</a> <font class="error"><?php echo $errors['passwd']; ?></font> </div> <div class="row"> <label>Retype Password:</label> - <input type="password" name="passwd2" size="30" tabindex="8" value="<?php echo $info['passwd2']; ?>"> + <input type="password" name="passwd2" size="45" tabindex="8" value="<?php echo $info['passwd2']; ?>"> <a class="tip" href="#t8">?</a> <font class="error"><?php echo $errors['passwd2']; ?></font> </div> @@ -70,31 +70,31 @@ $info=($_POST && $errors)?Format::htmlchars($_POST):array('prefix'=>'ost_','dbho <span class="subhead">Database connection information <font class="error"><?php echo $errors['db']; ?></font></span> <div class="row"> <label>MySQL Table Prefix:</label> - <input type="text" name="prefix" size="30" tabindex="9" value="<?php echo $info['prefix']; ?>"> + <input type="text" name="prefix" size="45" tabindex="9" value="<?php echo $info['prefix']; ?>"> <a class="tip" href="#t9">?</a> <font class="error"><?php echo $errors['prefix']; ?></font> </div> <div class="row"> <label>MySQL Hostname:</label> - <input type="text" name="dbhost" size="30" tabindex="10" value="<?php echo $info['dbhost']; ?>"> + <input type="text" name="dbhost" size="45" tabindex="10" value="<?php echo $info['dbhost']; ?>"> <a class="tip" href="#t10">?</a> <font class="error"><?php echo $errors['dbhost']; ?></font> </div> <div class="row"> <label>MySQL Database:</label> - <input type="text" name="dbname" size="30" tabindex="11" value="<?php echo $info['dbname']; ?>"> + <input type="text" name="dbname" size="45" tabindex="11" value="<?php echo $info['dbname']; ?>"> <a class="tip" href="#t11">?</a> <font class="error"><?php echo $errors['dbname']; ?></font> </div> <div class="row"> <label>MySQL Username:</label> - <input type="text" name="dbuser" size="30" tabindex="12" value="<?php echo $info['dbuser']; ?>"> + <input type="text" name="dbuser" size="45" tabindex="12" value="<?php echo $info['dbuser']; ?>"> <a class="tip" href="#t12">?</a> <font class="error"><?php echo $errors['dbuser']; ?></font> </div> <div class="row"> <label>MySQL Password:</label> - <input type="password" name="dbpass" size="30" tabindex="13" value="<?php echo $info['dbpass']; ?>"> + <input type="password" name="dbpass" size="45" tabindex="13" value="<?php echo $info['dbpass']; ?>"> <a class="tip" href="#t13">?</a> <font class="error"><?php echo $errors['dbpass']; ?></font> </div> 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 d019598e55de7099c9dbb517b4a9aa1663886ad0..130983a692155869064ae08b8ad47e88f4a6b2f2 100644 --- a/setup/install.php +++ b/setup/install.php @@ -80,7 +80,6 @@ if($_POST && $_POST['s']) { $_SESSION['ost_installer']['s']='done'; } - switch(strtolower($_SESSION['ost_installer']['s'])) { case 'config': case 'install': @@ -105,7 +104,16 @@ switch(strtolower($_SESSION['ost_installer']['s'])) { $inc='install-prereq.inc.php'; break; default: - $inc='install-prereq.inc.php'; + //Fail IF any of the old config files exists. + 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'; } require(INC_DIR.'header.inc.php'); diff --git a/setup/setup.inc.php b/setup/setup.inc.php index a79c93c7e55ed3aab9a220ea54f1d558258e3fc6..8eeb792feebca0c57bcf8d6c7e8ee24e98474985 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-RC3'); +define('THIS_VERSION', '1.7-RC4'); #inits - error reporting. $error_reporting = E_ALL & ~E_NOTICE; 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'); +?>