diff --git a/ajax.php b/ajax.php new file mode 100644 index 0000000000000000000000000000000000000000..b40869292b81680ad136b9365793c616e0b0c3bb --- /dev/null +++ b/ajax.php @@ -0,0 +1,34 @@ +<?php +/********************************************************************* + ajax.php + + Ajax utils for client interface. + + Peter Rotich <peter@osticket.com> + Copyright (c) 2006-2012 osTicket + http://www.osticket.com + + Released under the GNU General Public License WITHOUT ANY WARRANTY. + See LICENSE.TXT for details. + + vim: expandtab sw=4 ts=4 sts=4: +**********************************************************************/ + +function clientLoginPage($msg='Unauthorized') { + Http::response(403,'Must login: '.Format::htmlchars($msg)); + exit; +} + +require('client.inc.php'); + +if(!defined('INCLUDE_DIR')) Http::response(500, 'Server configuration error'); +require_once INCLUDE_DIR.'/class.dispatcher.php'; +require_once INCLUDE_DIR.'/class.ajax.php'; + +$dispatcher = patterns('', + url('^/config/', patterns('ajax.config.php:ConfigAjaxAPI', + url_get('^client', 'client') + )) +); +print $dispatcher->resolve($_SERVER['PATH_INFO']); +?> diff --git a/api/pipe.php b/api/pipe.php index a35d2af2099ec60ae77edece8a1b2d0416f42773..d7d4b33be96644c484c846d47bf36179829ccdc2 100644 --- a/api/pipe.php +++ b/api/pipe.php @@ -100,8 +100,6 @@ if(preg_match ("[[#][0-9]{1,10}]",$var['subject'],$regs)) { $errors=array(); $msgid=0; if(!$ticket){ //New tickets... - # Apply filters against the new ticket - $ef = new EmailFilter($var); $ef->apply($var); $ticket=Ticket::create($var,$errors,'email'); if(!is_object($ticket) || $errors){ api_exit(EX_DATAERR,'Ticket create Failed '.implode("\n",$errors)."\n\n"); @@ -118,14 +116,10 @@ if(!$ticket){ //New tickets... } } //Ticket created...save attachments if enabled. -if($cfg->allowEmailAttachments()) { - if($attachments=$parser->getAttachments()){ - //print_r($attachments); - foreach($attachments as $k=>$attachment){ - if($attachment['filename'] && $cfg->canUploadFileType($attachment['filename'])) { - $ticket->saveAttachment(array('name' => $attachment['filename'], 'data' => $attachment['body']),$msgid,'M'); - } - } +if($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'); } } api_exit(EX_SUCCESS); diff --git a/assets/default/css/theme.css b/assets/default/css/theme.css index 88c4898a80b62ae5be6ede550948a3e217fd3a8a..b59f6af8955857d2008dff647e8e4d83d4924e72 100644 --- a/assets/default/css/theme.css +++ b/assets/default/css/theme.css @@ -106,10 +106,12 @@ table { border-spacing: 0; } -td { +th, td { vertical-align: top; } +th { text-align: left; font-weight: normal; } + h1, h2, h3, h4, h5, h6, form, fieldset { margin: 0; padding: 0; @@ -516,17 +518,23 @@ body { display: block; float: left; } + +#ticketForm div input[type=file] { + border: 0; +} + #ticketForm div select, #clientLogin div select { display: block; float: left; } -#ticketForm div textarea, #clientLogin div textarea { +#ticketForm td textarea, #clientLogin div textarea { width: 600px; } -#ticketForm div em, #clientLogin div em { + +#ticketForm td em, #clientLogin div em { color: #777; } -#ticketForm div .captcha, #clientLogin div .captcha { +#ticketForm td .captcha, #clientLogin div .captcha { width: 88px; height: 31px; background: #000; @@ -534,20 +542,33 @@ body { float: left; margin-right: 20px; } -#ticketForm div label.inline, #clientLogin div label.inline { +#ticketForm td label.inline, #clientLogin div label.inline { width: auto; padding: 0 10px; } -#ticketForm div label.required, #clientLogin div label.required { + +#ticketTable table tr th { + width: 160px; + font-weight: normal; + text-align: left; +} + +#ticketForm table th.required, #ticketForm table td.required, #clientLogin div label.required { font-weight: bold; + text-align: left; } -#ticketForm div.captchaRow, #clientLogin div.captchaRow { + + + +#ticketForm tr.captchaRow, #clientLogin div.captchaRow { line-height: 31px; } -#ticketForm div.captchaRow input, #clientLogin div.captchaRow input { + +.captchaRow td input, #clientLogin div.captchaRow input { position: relative; top: 6px; } + #ticketForm div.error input, #clientLogin div.error input { border: 1px solid #a00; } @@ -797,6 +818,14 @@ a.refresh { font-family:helvetica, arial, sans-serif; } + +.uploads { + display:inline-block; + padding-right:20px; +} + +.uploads label { padding:3px; padding-right:10px; width: auto !important } + .button:hover { background-color: #111; color: #fff; } .button:active { top: 1px; box-shadow:none; -moz-box-shadow:none; -webkit-box-shadow:none; } .button, .button:visited, diff --git a/include/ajax.config.php b/include/ajax.config.php index fc9fb2c3fc8622108054052828b7e6bb9a42dcd6..feb1eb4b0646f75762c554e73e55b7d7701b9680 100644 --- a/include/ajax.config.php +++ b/include/ajax.config.php @@ -19,12 +19,26 @@ if(!defined('INCLUDE_DIR')) die('!'); class ConfigAjaxAPI extends AjaxController { //config info UI might need. - function scp_ui() { - global $thisstaff, $cfg; + function scp() { + global $cfg; + + $config=array( + 'lock_time' => ($cfg->getLockTime()*3600), + 'file_types' => $cfg->getAllowedFileTypes(), + 'max_file_size' => (int) $cfg->getMaxFileSize(), + 'max_file_uploads'=> (int) $cfg->getStaffMaxFileUploads() + ); + return $this->json_encode($config); + } + + function client() { + global $cfg; - $config=array('ticket_lock_time'=>($cfg->getLockTime()*3600), - 'max_attachments'=>$cfg->getMaxFileUploads(), - 'max_file_size'=>$cfg->getMaxFileSize()); + $config=array( + 'file_types' => $cfg->getAllowedFileTypes(), + 'max_file_size' => (int) $cfg->getMaxFileSize(), + 'max_file_uploads'=> (int) $cfg->getClientMaxFileUploads() + ); return $this->json_encode($config); } diff --git a/include/class.ajax.php b/include/class.ajax.php index 0240d91f83521393f31f73261562e1367b89e618..5bd26a67e13812966bd90164462c37083f52cd5f 100644 --- a/include/class.ajax.php +++ b/include/class.ajax.php @@ -26,11 +26,7 @@ require_once (INCLUDE_DIR.'class.api.php'); */ class AjaxController extends ApiController { function AjaxController() { - # Security checks first - # --> It is assumed that all AJAX calls will require a login. And - # for now, since client logins are not yet supported, a staff - # login will be required for AJAX calls. - $this->staffOnly(); + } function staffOnly() { global $thisstaff; diff --git a/include/class.config.php b/include/class.config.php index e293908d61637c923ba1c9cb4120b210065b0cc2..2c9afb2448b276dc9364a36b3f16e5c62bb22f45 100644 --- a/include/class.config.php +++ b/include/class.config.php @@ -295,10 +295,15 @@ class Config { return $this->config['max_file_size']; } - function getMaxFileUploads() { + function getStaffMaxFileUploads() { return $this->config['max_staff_file_uploads']; } + function getClientMaxFileUploads() { + //TODO: change max_user_file_uploads to max_client_file_uploads + return $this->config['max_user_file_uploads']; + } + function getLogLevel() { return $this->config['log_level']; } @@ -509,6 +514,9 @@ class Config { /* Attachments */ + function getAllowedFileTypes() { + return trim($this->config['allowed_filetypes']); + } function emailAttachments() { return ($this->config['email_attachments']); @@ -530,22 +538,11 @@ class Config { return ($this->allowAttachments() && $this->config['allow_email_attachments']); } + /* Needed by upgrader on 1.6 and older releases upgrade - not not remove */ function getUploadDir() { return $this->config['upload_dir']; } - //simply checking if destination dir is usable..nothing to do with permission to upload! - function canUploadFiles() { - $dir=$this->config['upload_dir']; - return ($dir && is_writable($dir))?TRUE:FALSE; - } - - function canUploadFileType($filename) { - $ext = strtolower(preg_replace("/.*\.(.{3,4})$/", "$1", $filename)); - $allowed=$this->config['allowed_filetypes']?array_map('trim',explode(',',strtolower($this->config['allowed_filetypes']))):null; - return ($ext && is_array($allowed) && (in_array(".$ext",$allowed) || in_array(".*",$allowed)))?TRUE:FALSE; - } - function updateSettings($vars,&$errors) { if(!$vars || $errors) @@ -736,10 +733,10 @@ class Config { $maxfileuploads=DEFAULT_MAX_FILE_UPLOADS; if(!$vars['max_user_file_uploads'] || $vars['max_user_file_uploads']>$maxfileuploads) - $errors['max_user_file_uploads']='Invalid selection'; + $errors['max_user_file_uploads']='Invalid selection. Must be less than '.$maxfileuploads; if(!$vars['max_staff_file_uploads'] || $vars['max_staff_file_uploads']>$maxfileuploads) - $errors['max_staff_file_uploads']='Invalid selection'; + $errors['max_staff_file_uploads']='Invalid selection. Must be less than '.$maxfileuploads; } if($errors) return false; diff --git a/include/class.faq.php b/include/class.faq.php index 78b1a799e021f46ed4f44ba159f98568328eb4cd..2197bbac102604d334d2f59b71d7658a3b18b39b 100644 --- a/include/class.faq.php +++ b/include/class.faq.php @@ -19,11 +19,11 @@ class FAQ { var $id; var $ht; + var $category; + var $attachments; function FAQ($id) { - - $this->id=0; $this->ht = array(); $this->load($id); @@ -44,6 +44,7 @@ class FAQ { $this->ht = db_fetch_array($res); $this->ht['id'] = $this->id = $this->ht['faq_id']; $this->category = null; + $this->attachments = array(); return true; } @@ -134,7 +135,7 @@ class FAQ { if($ids) { $topics = $this->getHelpTopicsIds(); - foreach($ids as $k=>$id) { + foreach($ids as $id) { if($topics && in_array($id,$topics)) continue; $sql='INSERT IGNORE INTO '.FAQ_TOPIC_TABLE .' SET faq_id='.db_input($this->getId()) @@ -158,6 +159,20 @@ class FAQ { return false; $this->updateTopics($vars['topics']); + + //Delete removed attachments. + $keepers = $vars['files']?$vars['files']:array(); + if(($attachments = $this->getAttachments())) { + foreach($attachments as $file) { + if($file['id'] && !in_array($file['id'], $keepers)) + $this->deleteAttachment($file['id']); + } + } + + //Upload new attachments IF any. + if($_FILES['attachments'] && ($files=Format::files($_FILES['attachments']))) + $this->uploadAttachments($files); + $this->reload(); return true; @@ -261,10 +276,19 @@ class FAQ { /* ------------------> Static methods <--------------------- */ function add($vars, &$errors) { - if(($id=self::create($vars, $errors)) && ($faq=self::lookup($id))) + if(!($id=self::create($vars, $errors))) + return false; + + if(($faq=self::lookup($id))) { $faq->updateTopics($vars['topics']); + + if($_FILES['attachments'] && ($files=Format::files($_FILES['attachments']))) + $faq->uploadAttachments($files); - return$faq; + $faq->reload(); + } + + return $faq; } function create($vars, &$errors) { diff --git a/include/class.file.php b/include/class.file.php index 891a8b5bfb851415c1ab3cbe804284ae5217f229..c27c9fdd07b5aa7477336c009af2fdb75605233c 100644 --- a/include/class.file.php +++ b/include/class.file.php @@ -141,7 +141,7 @@ class AttachmentFile { /* Function assumes the files types have been validated */ function upload($file) { - if(!$file['name'] || !is_uploaded_file($file['tmp_name'])) + if(!$file['name'] || $file['error'] || !is_uploaded_file($file['tmp_name'])) return false; $info=array('type'=>$file['type'], @@ -160,13 +160,6 @@ class AttachmentFile { $file['hash']=MD5(MD5($file['data']).time()); if(!$file['size']) $file['size']=strlen($file['data']); - - - - //TODO: Do chunked INSERTs - - if(($mps=db_get_variable('max_allowed_packet')) && $file['size']>($mps*0.7)) { - @db_set_variable('max_allowed_packet',$file['size']+$mps); - } $sql='INSERT INTO '.FILE_TABLE.' SET created=NOW() ' .',type='.db_input($file['type']) @@ -178,11 +171,15 @@ class AttachmentFile { return false; foreach (str_split($file['data'], 1024*100) as $chunk) { - if (!db_query('UPDATE '.FILE_TABLE.' SET filedata = CONCAT(filedata,' - .db_input($chunk).') WHERE id='.db_input($id))) - # Remove partially uploaded file contents + $sql='UPDATE '.FILE_TABLE + .' SET filedata = CONCAT(filedata,'.db_input($chunk).')' + .' WHERE id='.db_input($id); + if(!db_query($sql)) { + db_query('DELETE FROM '.FILE_TABLE.' WHERE id='.db_input($id).' LIMIT 1'); return false; + } } + return $id; } diff --git a/include/class.mailfetch.php b/include/class.mailfetch.php index 248a6ba81b6f52d9397414c1a08db92e5b88336e..fb17d8a26ac719854a683ba778c287321ddefea9 100644 --- a/include/class.mailfetch.php +++ b/include/class.mailfetch.php @@ -323,11 +323,13 @@ class MailFetcher { } function saveAttachments($ticket,$mid,$part,$index=0) { - global $cfg; + global $ost; if($part && $part->ifdparameters && ($filename=$part->dparameters[0]->value)){ //attachment $index=$index?$index:1; - if($ticket && $cfg->canUploadFileType($filename) && $cfg->getMaxFileSize()>=$part->bytes) { + if($ticket + && $ost->isFileTypeAllowed($filename) + && $ost->getConfig()->getMaxFileSize()>=$part->bytes) { //extract the attachments...and do the magic. $data=$this->decode($part->encoding, imap_fetchbody($this->mbox,$mid,$index)); $ticket->saveAttachment(array('name'=>$filename, 'data'=>$data),$ticket->getLastMsgId(),'M'); diff --git a/include/class.osticket.php b/include/class.osticket.php index 5f75d0fd1fe6ac28feedeea227ef3ad6dc3e7429..033ec0f82accf22a97b855b39c12c4166fe580af 100644 --- a/include/class.osticket.php +++ b/include/class.osticket.php @@ -109,6 +109,45 @@ class osTicket { return false; } + + function isFileTypeAllowed($file, $mimeType='') { + + if(!$file || !($allowedFileTypes=$this->getConfig()->getAllowedFileTypes())) + return false; + + //Return true if all file types are allowed (.*) + if(trim($allowedFileTypes)=='.*') return true; + + $allowed = array_map('trim', explode(',', strtolower($allowedFileTypes))); + $filename = is_array($file)?$file['name']:$file; + + $ext = strtolower(preg_replace("/.*\.(.{3,4})$/", "$1", $filename)); + + //TODO: Check MIME type - file ext. shouldn't be solely trusted. + + return ($ext && is_array($allowed) && in_array(".$ext", $allowed)); + } + + /* Function expects a well formatted array - see Format::files() + It's up to the caller to reject the upload on error. + */ + function validateFileUploads(&$files) { + + $errors=0; + foreach($files as &$file) { + if(!$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['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++; + } + + return (!$errors); + } function addExtraHeader($header) { $this->headers[md5($header)] = $header; diff --git a/include/class.ticket.php b/include/class.ticket.php index 42bb1737840d4c489f92ec128b918732d2a1ac77..071dda44f77a7ab4624c7408fa59ac85b737fb40 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -624,7 +624,7 @@ class Ticket{ /* The has here can be changed but must match validation in attachment.php */ $hash=md5($attachment['file_id'].session_id().$attachment['file_hash']); if($attachment['size']) - $size=sprintf('(<i>%s</i>)',Format::file_size($attachment['size'])); + $size=sprintf('<em>(%s)</em>', Format::file_size($attachment['size'])); $str.=sprintf('<a class="Icon file" href="attachment.php?id=%d&h=%s" target="%s">%s</a>%s %s', $attachment['attach_id'], $hash, $target, Format::htmlchars($attachment['name']), $size, $separator); @@ -1372,7 +1372,7 @@ class Ticket{ } /* public */ - function postReply($vars, $files, $errors, $alert = true) { + function postReply($vars, $errors, $alert = true) { global $thisstaff,$cfg; if(!$thisstaff || !$thisstaff->isStaff() || !$cfg) return 0; @@ -1380,7 +1380,7 @@ class Ticket{ if(!$vars['msgId']) $errors['msgId'] ='Missing messageId - internal error'; if(!$vars['response']) - $errors['response'] = 'Resonse message required'; + $errors['response'] = 'Response message required'; if($errors) return 0; @@ -1401,20 +1401,17 @@ class Ticket{ $this->setStatus($vars['reply_ticket_status']); /* We can NOT recover from attachment related failures at this point */ - //upload files. - $attachments = $uploads = array(); - //Web based upload.. - if($files && is_array($files) && ($files=Format::files($files))) - $attachments=array_merge($attachments,$files); + $attachments = array(); + //Web based upload.. note that we're not "validating" attachments on response. + if($_FILES['attachments'] && ($files=Format::files($_FILES['attachments']))) + $attachments=$this->uploadAttachments($files, $respId, 'R'); //Canned attachments... - if($vars['cannedattachments'] && is_array($vars['cannedattachments'])) - $attachments=array_merge($attachments,$vars['cannedattachments']); - - - //Upload attachments -ids used on outgoing emails are returned. - if($attachments) - $uploads = $this->uploadAttachments($attachments, $respId,'R'); + if($vars['cannedattachments'] && is_array($vars['cannedattachments'])) { + foreach($vars['cannedattachments'] as $fileId) + if($fileId && $this->saveAttachment($fileId, $respId, 'R')) + $attachments[] = $fileId; + } $this->onResponse(); //do house cleaning.. $this->reload(); @@ -1448,7 +1445,7 @@ class Ticket{ $body ="\n$tag\n\n".$body; //Set attachments if emailing. - $attachments =($cfg->emailAttachments() && $uploads)?$this->getAttachments($respId,'R'):array(); + $attachments =($cfg->emailAttachments() && $attachments)?$this->getAttachments($respId,'R'):array(); //TODO: setup 5 param (options... e.g mid trackable on replies) $email->send($this->getEmail(), $subj, $body, $attachments); } @@ -1566,14 +1563,29 @@ class Ticket{ //online based attached files. function uploadAttachments($files, $refid, $type) { + global $ost; $uploaded=array(); foreach($files as $file) { - if(($fileId=is_numeric($file)?$file:AttachmentFile::upload($file)) && is_numeric($fileId)) - if($this->saveAttachment($fileId, $refid, $type)) - $uploaded[]=$fileId; - } + if(!$file['error'] + && ($id=AttachmentFile::upload($file)) + && $this->saveAttachment($id, $refid, $type)) + $uploaded[]=$id; + elseif($file['error']!=UPLOAD_ERR_NO_FILE) { + + // log file upload errors as interal notes + syslog debug. + if($file['error'] && gettype($file['error'])=='string') + $error = $file['error']; + else + $error ='Error #'.$file['error']; + $this->postNote('File Upload Error', $error, false); + + $ost->logDebug('File Upload Error (Ticket #'.$this->getExtId().')', $error); + } + + } + return $uploaded; } @@ -1964,16 +1976,15 @@ class Ticket{ return null; /* -------------------- POST CREATE ------------------------ */ - $dept = $ticket->getDept(); - + if(!$cfg->useRandomIds()){ //Sequential ticketIDs support really..really suck arse. $extId=$id; //To make things really easy we are going to use autoincrement ticket_id. db_query('UPDATE '.TICKET_TABLE.' SET ticketID='.db_input($extId).' WHERE ticket_id='.$id.' LIMIT 1'); //TODO: RETHING what happens if this fails?? [At the moment on failure random ID is used...making stuff usable] - } - + } + $dept = $ticket->getDept(); //post the message. $msgid=$ticket->postMessage($vars['message'],$source,$vars['mid'],$vars['header'],true); @@ -2008,29 +2019,28 @@ class Ticket{ $autorespond=false; } - // If a canned-response is immediately queued for this ticket, - // disable the autoresponse - if ($vars['cannedResponseId']) - $autorespond=false; - - /***** See if we need to send some alerts ****/ - - $ticket->onNewTicket($vars['message'], $autorespond, $alertstaff); - if ($vars['cannedResponseId'] && ($canned = Canned::lookup($vars['cannedResponseId'])) && $canned->isEnabled()) { $files = array(); foreach ($canned->getAttachments() as $file) $files[] = $file['id']; - $ticket->postReply(array( - 'msgId' => $msgid, - 'response' => - $ticket->replaceTemplateVars($canned->getResponse()), - 'cannedattachments' => $files - ), null, $errors, true); + $ticket->postReply( + array( + 'msgId' => $msgid, + 'response' => + $ticket->replaceTemplateVars($canned->getResponse()), + 'cannedattachments' => $files + ),$errors, true); + + // If a canned-response is immediately queued for this ticket, + // disable the autoresponse + $autorespond=false; } + /***** See if we need to send some alerts ****/ + $ticket->onNewTicket($vars['message'], $autorespond, $alertstaff); + /************ check if the user JUST reached the max. open tickets limit **********/ if($cfg->getMaxOpenTickets()>0 && ($client=$ticket->getClient()) @@ -2046,7 +2056,7 @@ class Ticket{ return $ticket; } - function open($vars, $files, &$errors) { + function open($vars, &$errors) { global $thisstaff,$cfg; if(!$thisstaff || !$thisstaff->canCreateTickets()) return false; @@ -2068,7 +2078,7 @@ class Ticket{ // post response - if any if($vars['response']) { $vars['response']=$ticket->replaceTemplateVars($vars['response']); - if(($respId=$ticket->postReply($vars, $files, $errors, false))) { + if(($respId=$ticket->postReply($vars, $errors, false))) { //Only state supported is closed on response if(isset($vars['ticket_state']) && $thisstaff->canCloseTickets()) $ticket->setState($vars['ticket_state']); diff --git a/include/client/header.inc.php b/include/client/header.inc.php index 969e9e66b8d977256e8087fc8cba2dadebb8fc62..da763a7bbf7b1e5b9481b8d26a0dfca2c79a9997 100644 --- a/include/client/header.inc.php +++ b/include/client/header.inc.php @@ -12,8 +12,9 @@ header("Content-Type: text/html; charset=UTF-8\r\n"); <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <link rel="stylesheet" href="<?php echo ASSETS_PATH; ?>css/theme.css" media="screen"> <link rel="stylesheet" href="<?php echo ASSETS_PATH; ?>css/print.css" media="print"> - <script src="./js/jquery-1.7.2.min.js"></script> - <script src="./js/osticket.js"></script> + <script src="<?php echo ROOT_PATH; ?>js/jquery-1.7.2.min.js"></script> + <script src="<?php echo ROOT_PATH; ?>js/jquery.multifile.js"></script> + <script src="<?php echo ROOT_PATH; ?>js/osticket.js"></script> </head> <body> <div id="container"> diff --git a/include/client/open.inc.php b/include/client/open.inc.php index b85c1e337eac2894c2246a94c623cea6717b42d2..42cf10c92d6eb3a45fdbf3775fc3cde7103d247b 100644 --- a/include/client/open.inc.php +++ b/include/client/open.inc.php @@ -13,99 +13,111 @@ $info=($_POST && $errors)?Format::htmlchars($_POST):$info; <h1>Open a New Ticket</h1> <p>Please fill in the form below to open a new ticket.</p> <form id="ticketForm" method="post" action="open.php" enctype="multipart/form-data"> - <?php csrf_token(); ?> - <input type="hidden" name="a" value="open"> - <div> - <label for="name" class="required">Full Name:</label> - <?php - if($thisclient && $thisclient->isValid()) { - echo $thisclient->getName(); - } else { ?> - <input id="name" type="text" name="name" size="30" value="<?php echo $info['name']; ?>"> - <font class="error">* <?php echo $errors['name']; ?></font> - <?php - } ?> - </div> - <div> - <label for="email" class="required">Email Address:</label> - <?php - if($thisclient && $thisclient->isValid()) { - echo $thisclient->getEmail(); - } else { ?> - <input id="email" type="text" name="email" size="30" value="<?php echo $info['email']; ?>"> - <font class="error">* <?php echo $errors['email']; ?></font> - <?php - } ?> - </div> - <div> - <label for="phone">Telephone:</label> - <input id="phone" type="text" name="phone" size="17" value="<?php echo $info['phone']; ?>"> - <label for="ext" class="inline">Ext.:</label> - <input id="ext" type="text" name="phone_ext" size="3" value="<?php echo $info['phone_ext']; ?>"> - <font class="error"> <?php echo $errors['phone']; ?> <?php echo $errors['phone_ext']; ?></font> - </div> - <br> - <div> - <label for="topicId" class="required">Help Topic:</label> - <select id="topicId" name="topicId"> - <option value="" selected="selected">— Select a Help Topics —</option> + <?php csrf_token(); ?> + <input type="hidden" name="a" value="open"> + <table width="800" cellpadding="1" cellspacing="0" border="0"> + <tr> + <th class="required" width="160">Full Name:</th> + <td> <?php - if($topics=Topic::getPublicHelpTopics()) { - foreach($topics as $id =>$name) { - echo sprintf('<option value="%d" %s>%s</option>', - $id, ($info['topicId']==$id)?'selected="selected"':'', $name); - } + if($thisclient && $thisclient->isValid()) { + echo $thisclient->getName(); } else { ?> - <option value="0" >General Inquiry</option> - <?php } ?> - </select> - <font class="error">* <?php echo $errors['topicId']; ?></font> - </div> - <div> - <label for="subject" class="required">Subject:</label> - <input id="subject" type="text" name="subject" size="40" value="<?php echo $info['subject']; ?>"> - <font class="error">* <?php echo $errors['subject']; ?></font> - </div> - <div> - <label for="msg" class="required">Message:</label> - <span id="msg"> - <em>Please provide as much details as possible so we can best assist you.</em> <font class="error">* <?php echo $errors['message']; ?></font></span> - </div> - <div> - <label for="message" class="required"> </label> - <textarea id="message" cols="60" rows="8" name="message"><?php echo $info['message']; ?></textarea> - </div> + <input id="name" type="text" name="name" size="30" value="<?php echo $info['name']; ?>"> + <font class="error">* <?php echo $errors['name']; ?></font> + <?php + } ?> + </td> + </tr> + <tr> + <th class="required" width="160">Email Address:</th> + <td> + <?php + if($thisclient && $thisclient->isValid()) { + echo $thisclient->getEmail(); + } else { ?> + <input id="email" type="text" name="email" size="30" value="<?php echo $info['email']; ?>"> + <font class="error">* <?php echo $errors['email']; ?></font> + <?php + } ?> + </td> + </tr> + <tr> + <th>Telephone:</th> + <td> + + <input id="phone" type="text" name="phone" size="17" value="<?php echo $info['phone']; ?>"> + <label for="ext" class="inline">Ext.:</label> + <input id="ext" type="text" name="phone_ext" size="3" value="<?php echo $info['phone_ext']; ?>"> + <font class="error"> <?php echo $errors['phone']; ?> <?php echo $errors['phone_ext']; ?></font> + </td> + </tr> + <tr><td colspan=2> </td></tr> + <tr> + <td class="required">Help Topic:</td> + <td> + <select id="topicId" name="topicId"> + <option value="" selected="selected">— Select a Help Topics —</option> + <?php + if($topics=Topic::getPublicHelpTopics()) { + foreach($topics as $id =>$name) { + echo sprintf('<option value="%d" %s>%s</option>', + $id, ($info['topicId']==$id)?'selected="selected"':'', $name); + } + } else { ?> + <option value="0" >General Inquiry</option> + <?php + } ?> + </select> + <font class="error">* <?php echo $errors['topicId']; ?></font> + </td> + </tr> + <tr> + <td class="required">Subject:</td> + <td> + <input id="subject" type="text" name="subject" size="40" value="<?php echo $info['subject']; ?>"> + <font class="error">* <?php echo $errors['subject']; ?></font> + </td> + </tr> + <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> + <textarea id="message" cols="60" rows="8" name="message"><?php echo $info['message']; ?></textarea> + </td> + </tr> + <?php if(($cfg->allowOnlineAttachments() && !$cfg->allowAttachmentsOnlogin()) || ($cfg->allowAttachmentsOnlogin() && ($thisclient && $thisclient->isValid()))) { ?> - <div> - <label for="attachments">Attachments:</label> - <span id="uploads"></span> - <input type="file" class="multifile" name="attachments[]" id="attachments" size="30" value="" /> - <font class="error"> <?php echo $errors['attachments']; ?></font> - </div> + <tr> + <td>Attachments:</td> + <td> + <div class="uploads"></div><br> + <input type="file" class="multifile" name="attachments[]" id="attachments" size="30" value="" /> + <font class="error"> <?php echo $errors['attachments']; ?></font> + </td> + </tr> + <tr><td colspan=2> </td></tr> <?php } ?> <?php if($cfg->allowPriorityChange() && ($priorities=Priority::getPriorities())) { ?> - <div> - <label for="priority">Ticket Priority:</label> - <select id="priority" name="priorityId"> - <?php - if(!$info['priorityId']) - $info['priorityId'] = $cfg->getDefaultPriorityId(); //System default. - foreach($priorities as $id =>$name) { - echo sprintf('<option value="%d" %s>%s</option>', - $id, ($info['priorityId']==$id)?'selected="selected"':'', $name); + <tr> + <td>Ticket Priority:</td> + <td> + <select id="priority" name="priorityId"> + <?php + if(!$info['priorityId']) + $info['priorityId'] = $cfg->getDefaultPriorityId(); //System default. + foreach($priorities as $id =>$name) { + echo sprintf('<option value="%d" %s>%s</option>', + $id, ($info['priorityId']==$id)?'selected="selected"':'', $name); - } - ?> - - - - </select> - - <font class="error"> <?php echo $errors['priorityId']; ?></font> - - </div> + } + ?> + </select> + <font class="error"> <?php echo $errors['priorityId']; ?></font> + </td> + </tr> <?php } ?> @@ -114,20 +126,23 @@ $info=($_POST && $errors)?Format::htmlchars($_POST):$info; if($_POST && $errors && !$errors['captcha']) $errors['captcha']='Please re-enter the text again'; ?> - <br> - <div class="captchaRow"> - <label for="captcha" class="required">CAPTCHA Text:</label> - <span class="captcha"><img src="captcha.php" border="0" align="left"></span> - <input id="captcha" type="text" name="captcha" size="6"> - <em>Enter the text shown on the image.</em> - <font class="error">* <?php echo $errors['captcha']; ?></font> - </div> + <tr class="captchaRow"> + <td class="required">CAPTCHA Text:</td> + <td> + <span class="captcha"><img src="captcha.php" border="0" align="left"></span> + + <input id="captcha" type="text" name="captcha" size="6"> + <em>Enter the text shown on the image.</em> + <font class="error">* <?php echo $errors['captcha']; ?></font> + </td> + </tr> <?php } ?> - <br> - <p style="padding-left:150px;"> + <tr><td colspan=2> </td></tr> + </table> + <p style="padding-left:150px;"> <input type="submit" value="Create Ticket"> <input type="reset" value="Reset"> <input type="button" value="Cancel" onClick='window.location.href="index.php"'> - </p> + </p> </form> diff --git a/include/client/view.inc.php b/include/client/view.inc.php index 5aba461fc8f8a129138ed406d1319bb66d1ca9cb..ccea4c927cfdf31793ae325f79572a743fa35f26 100644 --- a/include/client/view.inc.php +++ b/include/client/view.inc.php @@ -122,7 +122,7 @@ if($ticket->getThreadCount() && ($thread=$ticket->getClientThread())) { <div class="uploads"> </div> <div class="file_input"> - <input type="file" name="attachments[]" size="30" value="" /> + <input class="multifile" type="file" name="attachments[]" size="30" value="" /> </div> </td> </tr> diff --git a/include/staff/department.inc.php b/include/staff/department.inc.php index 2c1ff86ca75f3b6467e44fa3b86fe98652a808e7..b900087331721347ee1b1c57dd37f43e274ed2d2 100644 --- a/include/staff/department.inc.php +++ b/include/staff/department.inc.php @@ -206,10 +206,10 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); </tr> <tr> <th colspan="2"> - <em><strong>Department Access</strong>: Check all groups allowed to access department.</em> + <em><strong>Department Access</strong>: Check all groups allowed to access this department.</em> </th> </tr> - <tr><td colspan=2><em>Primary department members and manager will always have access regarless of group selection or assignment.</em></td></tr> + <tr><td colspan=2><em>Department manager and primary members will always have access independent of group selection or assignment.</em></td></tr> <?php $sql='SELECT group_id, group_name, count(staff.staff_id) as members ' .' FROM '.GROUP_TABLE.' grp ' diff --git a/include/staff/faq.inc.php b/include/staff/faq.inc.php index 8882da2fa817cf1e5000d4700fab0cc2b9bb0a19..fb87156b97d5f33254efcce57b24b7cfef638c7d 100644 --- a/include/staff/faq.inc.php +++ b/include/staff/faq.inc.php @@ -23,6 +23,7 @@ if($faq){ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); ?> <form action="faq.php?<?php echo $qstr; ?>" method="post" id="save" enctype="multipart/form-data"> + <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> <input type="hidden" name="id" value="<?php echo $info['id']; ?>"> @@ -90,7 +91,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <div><b>Attachments</b> (optional) <font class="error"> <?php echo $errors['files']; ?></font></div> <?php if($faq && ($files=$faq->getAttachments())) { - echo '<div id="faq_attachments"><span class="faded">Uncheck to delete the attachment on submit</span><br>'; + echo '<div class="faq_attachments"><span class="faded">Uncheck to delete the attachment on submit</span><br>'; foreach($files as $file) { $hash=$file['hash'].md5($file['id'].session_id().$file['hash']); echo sprintf('<label><input type="checkbox" name="files[]" id="f%d" value="%d" checked="checked"> @@ -99,14 +100,12 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); } echo '</div><br>'; } - //TODO: add a setting on admin panel - if(count($files)<5) { ?> - <div> - <input type="file" name="attachments[]" value=""/> + <div class="faded">Select files to upload.</div> + <div class="uploads"></div> + <div class="file_input"> + <input type="file" class="multifile" name="attachments[]" size="30" value="" /> </div> - <?}?> - <div class="faded">You can upload up to 5 attachments.</div> </td> </tr> <?php diff --git a/include/staff/header.inc.php b/include/staff/header.inc.php index 251de2b4f8ec4cdd8822668b806bde81b6a7e57e..1f4a3da546c99b645d81c73597cc1c47cc7927c6 100644 --- a/include/staff/header.inc.php +++ b/include/staff/header.inc.php @@ -12,6 +12,7 @@ <![endif]--> <script type="text/javascript" src="../js/jquery-1.7.2.min.js"></script> <script type="text/javascript" src="../js/jquery-ui-1.8.18.custom.min.js"></script> + <script type="text/javascript" src="../js/jquery.multifile.js"></script> <script type="text/javascript" src="./js/tips.js"></script> <script type="text/javascript" src="./js/nicEdit.js"></script> <script type="text/javascript" src="./js/bootstrap-typeahead.js"></script> diff --git a/include/staff/ticket-open.inc.php b/include/staff/ticket-open.inc.php index a61c7bfcbd2396bfe5e20d6bc0f2abb827d4b009..db3aca84018b1d0744a46363243a6f111f3bfdba 100644 --- a/include/staff/ticket-open.inc.php +++ b/include/staff/ticket-open.inc.php @@ -162,7 +162,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <?php if($cfg->allowAttachments()) { ?> <br><em><b>Attachments:</b> Response required when files are attached.</em> - <div id="canned_attachments"> + <div class="canned_attachments"> <?php if($info['cannedattachments']) { foreach($info['cannedattachments'] as $k=>$id) { @@ -175,7 +175,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); } ?> </div> - <div id="uploads"></div> + <div class="uploads"></div> <div class="file_input"> <input type="file" class="multifile" name="attachments[]" size="30" value="" /> </div> diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php index 633fe2f27d0eea17965c758b932c8650c7c9b768..a58206963acf4deb36fd48bf17d80c2ea63020aa 100644 --- a/include/staff/ticket-view.inc.php +++ b/include/staff/ticket-view.inc.php @@ -334,9 +334,9 @@ if(!$cfg->showNotesInline()) { ?> <label for="attachment">Attachments:</label> </td> <td width="765" id="reply_form_attachments" class="attachments"> - <div id="canned_attachments"> + <div class="canned_attachments"> </div> - <div id="uploads"> + <div class="uploads"> </div> <div class="file_input"> <input type="file" class="multifile" name="attachments[]" size="30" value="" /> diff --git a/js/jquery.multifile.js b/js/jquery.multifile.js new file mode 100644 index 0000000000000000000000000000000000000000..3ae6835d2f233f71073709e25560163d7550f733 --- /dev/null +++ b/js/jquery.multifile.js @@ -0,0 +1,178 @@ +/********************************************************************* + jquery.multifile.js + + Multifile plugin that allows users to upload multiple files at once in unobstructive manner - cleaner interface. + + Allows limiting number of files and file type(s) using file extension. + + NOTE: + * Files are not uploaded until the form is submitted + * Server side file type validation is a MUST + * Plugin doesn't take into account PHP related limitations e.g max uploads + max size. + + Peter Rotich <peter@osticket.com> + Copyright (c) 2006-2012 osTicket + http://www.osticket.com + + Credits: + The plugin borrows heavily from a plugin by Rocky Meza @ fusionbox + https://github.com/fusionbox/jquery-multifile + + vim: expandtab sw=4 ts=4 sts=4: +**********************************************************************/ + +;(function($, global, undefined) { + + $.fn.multifile = function(options) { + var container = null; + var options = $.extend({}, $.fn.multifile.defaults, options); + + options.allowedFileTypes = $.map(options.file_types.toLowerCase().split(','), $.trim); + options.inputTemplate = options.inputTemplate || $.fn.multifile.inputTemplate; + + container = options.container || null; + + + return this.each(function() { + + var settings = options; + var $container + + , addInput = function(event) { + + var $this = $(this) + , fObj = $(this).closest('form') + , new_input = $.fn.multifile.cloneInput($this) + , file = $.fn.multifile.getFileObject(this); + + if(fObj.data('files') == undefined) + fObj.data('files', 0); + + if(fObj.data('files')>=settings.max_uploads || (fObj.data('files')+file.count)>settings.max_uploads) { + alert('You have reached the maximum number of files ('+ settings.max_uploads+') allowed per upload'); + } else if($.fn.multifile.checkFileTypes(file, settings.allowedFileTypes)) { + $this.hide(); + + settings + .inputTemplate(file) + .appendTo($container) + .on('click', 'input', bindRemoveInput($this, file)); + + fObj.data('files', fObj.data('files')+file.count); + if(fObj.data('files')<settings.max_uploads) + $this.after(new_input); + + } else { + var msg = 'Selected file type is NOT allowed'; + if(file.count>1) + msg = 'File type of one or more of the selected files is NOT allowed'; + + alert('Error: '+msg); + + $this.replaceWith(new_input); + } + + } + + , bindRemoveInput = function($input, file) { + + return function(event) { + + event.preventDefault(); + + if(confirm('Are you sure you want to remove '+file.name+'?')) { + var fObj = $(this).closest('form'); + + fObj.data('files', fObj.data('files')-file.count); + if(fObj.data('files')<settings.max_uploads && (fObj.data('files')+file.count)>=settings.max_uploads) + $input.after($.fn.multifile.cloneInput($input).show()); + + $input.remove(); + $(this).parent().remove(); + } + + return false; + }; + + }; + + if ( container ) { + if ( typeof container == 'string' ) + $container = $(container, $(this).closest('form')); + else + $container = container; + } else { + $container = $('<div class="uploads" />'); + $(this).after($container); + } + + $(this).bind('change.multifile', addInput); + + }); + }; + + $.fn.multifile.inputTemplate = function(file) { + return $('<label style="padding-right:5px;"><input type="checkbox" name="uploads[]" value="' + file.name + '" checked="checked"> ' + file.name + '</label>'); + }; + + $.fn.multifile.checkFileTypes = function(file, allowedFileTypes) { + + //Wildcard. + if(allowedFileTypes[0]=='.*') + return true; + + var filenames = $.map(file.name.toLowerCase().split(','), $.trim); + for (var i = 0, _len = filenames.length; i < _len; i++) + if(filenames[i] && $.inArray('.'+filenames[i].split('.').pop(), allowedFileTypes) == -1) + return false; + + return true; + }; + + //Clone file input and clear the value without triggering a warning! + $.fn.multifile.cloneInput = function(input) { + + var $clone = input.clone(true); + + if ($.browser.msie) { + $clone.replaceWith(function () { return $(this).clone(true); }); + } else { + $clone.val(''); + } + + return $clone; + } + + //Get file object + $.fn.multifile.getFileObject = function(input) { + var file = {}; + + file.count = 1; + // check for HTML5 FileList support + if ( !!global.FileList ) { + if ( input.files.length == 1 ) + file.name = input.files[0].name; + else { //Multi-select + // We do this in order to support `multiple` files. + // You can't display them separately because they + // belong to only one file input. It is impossible + // to remove just one of the files. + file.name = input.files[0].name; + for (var i = 1, _len = input.files.length; i < _len; i++) + file.name += ', ' + input.files[i].name; + + file.count = i; + } + } else { + file.name = input.value; + } + + return file; + }; + + //Default options + $.fn.multifile.defaults = { + max_uploads: 1, + file_types: '.*' + }; +})(jQuery, this); diff --git a/js/osticket.js b/js/osticket.js index a809e6f2f794a0534096c69b2a210428424d703d..4057b04b84a25e0cc7d2e249e17cac5efaa5c9e5 100644 --- a/js/osticket.js +++ b/js/osticket.js @@ -1 +1,55 @@ -//Nothing for now... +/* + osticket.js + Copyright (c) osTicket.com + */ + +$(document).ready(function(){ + + $("input:not(.dp):visible:enabled:first").focus(); + $('table.list tbody tr:odd').addClass('odd'); + + $("form#save :input").change(function() { + var fObj = $(this).closest('form'); + if(!fObj.data('changed')){ + fObj.data('changed', true); + $('input[type=submit]', fObj).css('color', 'red'); + $(window).bind('beforeunload', function(e) { + return 'Are you sure you want to leave? Any changes or info you\'ve entered will be discarded!'; + }); + } + }); + + $("form#save :input[type=reset]").click(function() { + var fObj = $(this).closest('form'); + if(fObj.data('changed')){ + $('input[type=submit]', fObj).removeAttr('style'); + $('label', fObj).removeAttr('style'); + $('label', fObj).removeClass('strike'); + fObj.data('changed', false); + $(window).unbind('beforeunload'); + } + }); + + $('form#save').submit(function() { + $(window).unbind('beforeunload'); + return true; + }); + + /* Get config settings from the backend */ + var $config = null; + $.ajax({ + url: "ajax.php/config/client", + dataType: 'json', + async: false, + success: function (config) { + $config = config; + } + }); + + /* Multifile uploads */ + $('.multifile').multifile({ + container: '.uploads', + max_uploads: ($config && $config.max_file_uploads)?$config.max_file_uploads:1, + file_types: ($config && $config.file_types)?$config.file_types:".*" + }); +}); diff --git a/open.php b/open.php index 11d3a0a9d3397d64941886169a5fa2892ea663a1..7bdc8fc8ffccfee37516c5ec60c0e1d16119df75 100644 --- a/open.php +++ b/open.php @@ -32,6 +32,14 @@ if($_POST): //Ticket::create...checks for errors.. if(($ticket=Ticket::create($_POST,$errors,SOURCE))){ $msg='Support ticket request created'; + //Upload attachments... + if($cfg->allowOnlineAttachments() + && $_FILES['attachments'] + && ($files=Format::files($_FILES['attachments']))) { + $ost->validateFileUploads($files); //Validator sets errors - if any. + $ticket->uploadAttachments($files, $ticket->getLastMsgId(), 'M'); + } + //Logged in...simply view the newly created ticket. if($thisclient && $thisclient->isValid()) { if(!$cfg->showRelatedTickets()) diff --git a/scp/ajax.php b/scp/ajax.php index 8ffa515402f907b6525b1f74786bf6e1a0378c98..259413dab83034c23c52ca5e6017bf9b5d9b9719 100644 --- a/scp/ajax.php +++ b/scp/ajax.php @@ -28,7 +28,7 @@ ini_set('display_errors','0'); //Disable error display ini_set('display_startup_errors','0'); //TODO: disable direct access via the browser? i,e All request must have REFER? -if(!defined('INCLUDE_DIR')) Http::response(500,'config error'); +if(!defined('INCLUDE_DIR')) Http::response(500, 'Server configuration error'); require_once INCLUDE_DIR.'/class.dispatcher.php'; require_once INCLUDE_DIR.'/class.ajax.php'; @@ -43,7 +43,7 @@ $dispatcher = patterns('', url_get('^ticket_variables', 'ticket_variables') )), url('^/config/', patterns('ajax.config.php:ConfigAjaxAPI', - url_get('^ui', 'scp_ui') + url_get('^scp', 'scp') )), url('^/report/overview/', patterns('ajax.reports.php:OverviewReportAjaxAPI', # Send diff --git a/scp/css/scp.css b/scp/css/scp.css index 899d9caa4726ab6009a89a8c3ef52196d425c8a9..3c7b83e12f3fc85001b1fd451c6fcc3e586e2752 100644 --- a/scp/css/scp.css +++ b/scp/css/scp.css @@ -30,7 +30,8 @@ a { .strike { text-decoration:line-through; color:red; } -#canned_attachments label { padding:3px; padding-right:10px; } +.canned_attachments label, .canned_attachments span .uploads label { padding:3px; padding-right:10px; } +.canned_attachments label { padding-right:3px; } #breadcrumbs { diff --git a/scp/faq.php b/scp/faq.php index f668be9f233fe5f2678bebceda1f6fda94dfdd13..6e3d422a1669d8b09c02735d3115879e0fb3edbd 100644 --- a/scp/faq.php +++ b/scp/faq.php @@ -40,19 +40,7 @@ if($_POST): elseif($faq->update($_POST,$errors)) { $msg='FAQ updated successfully'; $_REQUEST['a']=null; //Go back to view - //Delete removed attachments. - $keepers = $_POST['files']?$_POST['files']:array(); - if(($attachments = $faq->getAttachments())) { - foreach($attachments as $k=>$file) { - if($file['id'] && !in_array($file['id'], $keepers)) { - $faq->deleteAttachment($file['id']); - } - } - } - //Upload NEW attachments IF ANY - TODO: validate attachment types?? - if($_FILES['attachments'] && ($files=Format::files($_FILES['attachments']))) - $faq->uploadAttachments($files); - + $faq->reload(); } elseif(!$errors['err']) $errors['err'] = 'Unable to update FAQ. Try again!'; break; diff --git a/scp/js/scp.js b/scp/js/scp.js index c46c624d49ca97127b9bca3485c6dd313c53497b..8730685c0e2134d18d6a29e9f9f74cf66daaffa8 100644 --- a/scp/js/scp.js +++ b/scp/js/scp.js @@ -121,7 +121,7 @@ $(document).ready(function(){ //Canned attachments. - $('#canned_attachments, #faq_attachments').delegate('input:checkbox', 'click', function(e) { + $('.canned_attachments, .faq_attachments').delegate('input:checkbox', 'click', function(e) { var elem = $(this); if(!$(this).is(':checked') && confirm("Are you sure you want to remove this attachment?")==true) { elem.parent().addClass('strike'); @@ -154,12 +154,13 @@ $(document).ready(function(){ $('#response',fObj).val(canned.response); } //Canned attachments. - if(canned.files && $('#canned_attachments',fObj).length) { + if(canned.files && $('.canned_attachments',fObj).length) { $.each(canned.files,function(i, j) { - if(!$('#canned_attachments #f'+j.id,fObj).length) { - var file='<label><input type="checkbox" name="cannedattachments[]" value="' + j.id+'" id="f'+j.id+'" checked="checked">'; - file+= '<a href="file.php?h=' + j.hash + j.key+ '">'+ j.name +'</a></label>'; - $('#canned_attachments', fObj).append(file); + if(!$('.canned_attachments #f'+j.id,fObj).length) { + var file='<span><label><input type="checkbox" name="cannedattachments[]" value="' + j.id+'" id="f'+j.id+'" checked="checked">'; + file+= ' '+ j.name + '</label>'; + file+= ' (<a href="file.php?h=' + j.hash + j.key+ '">view</a>) </span>'; + $('.canned_attachments', fObj).append(file); } }); @@ -203,17 +204,24 @@ $(document).ready(function(){ }); /* Get config settings from the backend */ - $.get('ajax.php/config/ui.json', - function(config){ - /* - if(config && config.max_attachments) - alert(config.max_attachments); - */ - }, - 'json') - .error( function() {}); - /* Datepicker */ + var $config = null; + $.ajax({ + url: "ajax.php/config/scp", + dataType: 'json', + async: false, + success: function (config) { + $config = config; + } + }); + + /* Multifile uploads */ + $('.multifile').multifile({ + container: '.uploads', + max_uploads: ($config && $config.max_file_uploads)?$config.max_file_uploads:1, + file_types: ($config && $config.file_types)?$config.file_types:".*" + }); + /* Datepicker */ $('.dp').datepicker({ numberOfMonths: 2, showButtonPanel: true, diff --git a/scp/tickets.php b/scp/tickets.php index 0feacf86429846326f98430c4c465c38262295c4..66af215ea732a492b5f9e99cf5b0d99c16ebdb6c 100644 --- a/scp/tickets.php +++ b/scp/tickets.php @@ -57,7 +57,7 @@ if($_POST && !$errors): $wasOpen =($ticket->isOpen()); //If no error...do the do. - if(!$errors && ($respId=$ticket->postReply($_POST,$_FILES['attachments'],$errors))) { + if(!$errors && ($respId=$ticket->postReply($_POST, $errors))) { $msg='Reply posted successfully'; $ticket->reload(); if($ticket->isClosed() && $wasOpen) @@ -360,7 +360,7 @@ if($_POST && !$errors): $ticket=null; if(!$thisstaff || !$thisstaff->canCreateTickets()) { $errors['err']='You do not have permission to create tickets. Contact admin for such access'; - }elseif(($ticket=Ticket::open($_POST, $_FILES['attachments'], $errors))) { + }elseif(($ticket=Ticket::open($_POST, $errors))) { $msg='Ticket created successfully'; $_REQUEST['a']=null; if(!$ticket->checkStaffAccess($thisstaff) || $ticket->isClosed()) diff --git a/secure.inc.php b/secure.inc.php index 3096b3503036e8746a58cea56aa2e85f977b4896..45010979855f9f377e802b569a1d9edc8eb2af91 100644 --- a/secure.inc.php +++ b/secure.inc.php @@ -16,9 +16,18 @@ if(!strcasecmp(basename($_SERVER['SCRIPT_NAME']),basename(__FILE__))) die('Kwaheri!'); if(!file_exists('client.inc.php')) die('Fatal Error.'); require_once('client.inc.php'); + +//Client Login page: Ajax interface can pre-declare the function to trap logins. +if(!function_exists('clientLoginPage')) { + function clientLoginPage($msg ='') { + require('./login.php'); + exit; + } +} + //User must be logged in! if(!$thisclient || !$thisclient->getId() || !$thisclient->isValid()){ - require('./login.php'); + clientLoginPage(); exit; } $thisclient->refreshSession(); diff --git a/tickets.php b/tickets.php index 81b8ed68f153b551c4b063d2fa6860ea84070ac1..4ee69a8ef51f5b2b274df224fac4ed195330da5a 100644 --- a/tickets.php +++ b/tickets.php @@ -38,26 +38,15 @@ if($_POST && is_object($ticket) && $ticket->getId()): if(!$_POST['message']) $errors['message']='Message required'; - //check attachment..if any is set - $files=($cfg->allowOnlineAttachments() && $_FILES['attachments'])?Format::files($_FILES['attachments']):array(); - if($files) { - - foreach($files as $file) { - if(!$file['name']) continue; - - if(!$cfg->canUploadFileType($file['name'])) - $errors['attachment']='Invalid file type [ '.$file['name'].' ]'; - elseif($file['size']>$cfg->getMaxFileSize()) - $errors['attachment']='File '.$file['name'].'is too big. Max '.$cfg->getMaxFileSize().' bytes allowed'; - } - } - if(!$errors) { //Everything checked out...do the magic. if(($msgid=$ticket->postMessage($_POST['message'],'Web'))) { - if($files && $cfg->allowOnlineAttachments()) - $ticket->uploadAttachments($files,$msgid,'M'); - + if($cfg->allowOnlineAttachments() + && $_FILES['attachments'] + && ($files=Format::files($_FILES['attachments']))) { + $ost->validateFileUploads($files); //Validator sets errors - if any. + $ticket->uploadAttachments($files, $msgid, 'M'); + } $msg='Message Posted Successfully'; } else { $errors['err']='Unable to post the message. Try again'; @@ -68,7 +57,7 @@ if($_POST && is_object($ticket) && $ticket->getId()): } break; default: - $errors['err']='Uknown action'; + $errors['err']='Unknown action'; } $ticket->reload(); endif;