diff --git a/bootstrap.php b/bootstrap.php index d726e8c0452c7ee2141a02d6065e01b36f308285..a82796ebd2d7ecba1eb453884502f10a193ded53 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -177,7 +177,7 @@ class Bootstrap { require(INCLUDE_DIR.'class.log.php'); require(INCLUDE_DIR.'class.crypto.php'); require(INCLUDE_DIR.'class.timezone.php'); - require(INCLUDE_DIR.'class.signal.php'); + require_once(INCLUDE_DIR.'class.signal.php'); require(INCLUDE_DIR.'class.nav.php'); require(INCLUDE_DIR.'class.page.php'); require_once(INCLUDE_DIR.'class.format.php'); //format helpers diff --git a/include/ajax.tickets.php b/include/ajax.tickets.php index e6c0fdb36a4aa6268f89feb437c53e713d2ce350..f52a4341205f6c0e66e12ffff07ebff15d14767f 100644 --- a/include/ajax.tickets.php +++ b/include/ajax.tickets.php @@ -103,12 +103,10 @@ class TicketsAjaxAPI extends AjaxController { global $thisstaff, $cfg; $result=array(); - $select = 'SELECT DISTINCT ticket.ticket_id'; + $select = 'SELECT ticket.ticket_id'; $from = ' FROM '.TICKET_TABLE.' ticket '; - $where = ' WHERE 1 '; - //Access control. - $where.=' AND ( ticket.staff_id='.db_input($thisstaff->getId()); + $where = ' WHERE ( ticket.staff_id='.db_input($thisstaff->getId()); if(($teams=$thisstaff->getTeams()) && count(array_filter($teams))) $where.=' OR ticket.team_id IN('.implode(',', db_input(array_filter($teams))).')'; @@ -179,57 +177,65 @@ class TicketsAjaxAPI extends AjaxController { $where.=' AND ticket.created<=FROM_UNIXTIME('.$endTime.')'; //Query + $joins = array(); if($req['query']) { $queryterm=db_real_escape($req['query'], false); - $from.=' LEFT JOIN '.TICKET_THREAD_TABLE.' thread ON (ticket.ticket_id=thread.ticket_id )' - .' LEFT JOIN '.FORM_ENTRY_TABLE.' tentry ON (tentry.object_id = ticket.ticket_id - AND tentry.object_type="T") - LEFT JOIN '.FORM_ANSWER_TABLE.' tans ON (tans.entry_id = tentry.id - AND tans.value_id IS NULL) - LEFT JOIN '.FORM_ENTRY_TABLE.' uentry ON (uentry.object_id = ticket.user_id + // Setup sets of joins and queries + $joins[] = array( + 'from' => + 'LEFT JOIN '.TICKET_THREAD_TABLE.' thread ON (ticket.ticket_id=thread.ticket_id )', + 'where' => "thread.title LIKE '%$queryterm%' OR thread.body LIKE '%$queryterm%'" + ); + $joins[] = array( + 'from' => + 'LEFT JOIN '.FORM_ENTRY_TABLE.' tentry ON (tentry.object_id = ticket.ticket_id AND tentry.object_type="T") + LEFT JOIN '.FORM_ANSWER_TABLE.' tans ON (tans.entry_id = tentry.id AND tans.value_id IS NULL)', + 'where' => "tans.value LIKE '%$queryterm%'" + ); + $joins[] = array( + 'from' => + 'LEFT JOIN '.FORM_ENTRY_TABLE.' uentry ON (uentry.object_id = ticket.user_id AND uentry.object_type="U") LEFT JOIN '.FORM_ANSWER_TABLE.' uans ON (uans.entry_id = uentry.id AND uans.value_id IS NULL) LEFT JOIN '.USER_TABLE.' user ON (ticket.user_id = user.id) - LEFT JOIN '.USER_EMAIL_TABLE.' uemail ON (user.id = uemail.user_id)'; - - $where.=" AND ( uemail.address LIKE '%$queryterm%'" - ." OR user.name LIKE '%$queryterm%'" - ." OR tans.value LIKE '%$queryterm%'" - ." OR uans.value LIKE '%$queryterm%'" - ." OR thread.title LIKE '%$queryterm%'" - ." OR thread.body LIKE '%$queryterm%'" - .' )'; + LEFT JOIN '.USER_EMAIL_TABLE.' uemail ON (user.id = uemail.user_id)', + 'where' => + "uemail.address LIKE '%$queryterm%' OR user.name LIKE '%$queryterm%' OR uans.value LIKE '%$queryterm%'", + ); } // Dynamic fields - $dynfields='(SELECT entry.object_id, %s '. - 'FROM '.FORM_ANSWER_TABLE.' ans '. - 'JOIN '.FORM_ENTRY_TABLE.' entry ON entry.id=ans.entry_id '. - 'JOIN '.FORM_FIELD_TABLE.' field ON field.id=ans.field_id '. - 'WHERE entry.object_type="T" GROUP BY entry.object_id)'; - $vals = array(); + $cdata_search = false; foreach (TicketForm::getInstance()->getFields() as $f) { if (isset($req[$f->getFormName()]) && ($val = $req[$f->getFormName()])) { - $id = $f->get('id'); - $vals[] = "MAX(IF(field.id = '$id', ans.value_id, NULL)) as `f_{$id}_id`"; - $vals[] = "MAX(IF(field.id = '$id', ans.value, NULL)) as `f_$id`"; - $where .= " AND (dyn.`f_{$id}_id` = ".db_input($val) - . " OR dyn.`f_$id` LIKE '%".db_real_escape($val)."%')"; + $name = $f->get('name') ? $f->get('name') : 'field_'.$f->get('id'); + $cwhere = "cdata.`$name` LIKE '%".db_real_escape($val)."%'"; + if ($f->getImpl()->hasIdValue() && is_numeric($val)) + $cwhere .= " OR cdata.`{$name}_id` = ".db_input($val); + $where .= ' AND ('.$cwhere.')'; + $cdata_search = true; } } - if ($vals) - $from .= ' LEFT JOIN '.sprintf($dynfields, implode(',', $vals)) - ." dyn ON (dyn.object_id = ticket.ticket_id)"; + if ($cdata_search) + $from .= 'LEFT JOIN '.TABLE_PREFIX.'ticket__cdata ' + ." cdata ON (cdata.ticket_id = ticket.ticket_id)"; + + $sections = array(); + foreach ($joins as $j) { + $sections[] = "$select $from {$j['from']} $where AND ({$j['where']})"; + } + if (!$joins) + $sections[] = "$select $from $where"; - $sql="$select $from $where"; + $sql=implode(' union ', $sections); $res = db_query($sql); $tickets = array(); - while (list($tickets[]) = db_fetch_row($res)); - $tickets = array_filter($tickets); + while ($row = db_fetch_row($res)) + $tickets[] = $row[0]; return $tickets; } diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php index 52e3109356db760e6a9d412c7782b5697b9891ec..f164138994cc1846eec67baccc16b99f1fe49c97 100644 --- a/include/class.dynamic_forms.php +++ b/include/class.dynamic_forms.php @@ -18,6 +18,7 @@ **********************************************************************/ require_once(INCLUDE_DIR . 'class.orm.php'); require_once(INCLUDE_DIR . 'class.forms.php'); +require_once(INCLUDE_DIR . 'class.signal.php'); /** * Form template, used for designing the custom form and for entering custom @@ -130,6 +131,7 @@ class DynamicForm extends VerySimpleModel { foreach ($ht['fields'] as $f) { $f = DynamicFormField::create($f); $f->form_id = $inst->id; + $f->setForm($inst); $f->save(); } } @@ -196,6 +198,89 @@ class TicketForm extends DynamicForm { static::$instance = $o[0]->instanciate(); return static::$instance; } + + // Materialized View for Ticket custom data (MySQL FlexViews would be + // nice) + // + // @see http://code.google.com/p/flexviews/ + static function getDynamicDataViewFields() { + $fields = array(); + foreach (self::getInstance()->getFields() as $f) { + $impl = $f->getImpl(); + if (!$impl->hasData() || $impl->isPresentationOnly()) + continue; + + $name = ($f->get('name')) ? $f->get('name') + : 'field_'.$f->get('id'); + + $fields[] = sprintf( + 'MAX(IF(field.name=\'%1$s\',ans.value,NULL)) as `%1$s`', + $name); + if ($impl->hasIdValue()) { + $fields[] = sprintf( + 'MAX(IF(field.name=\'%1$s\',ans.value_id,NULL)) as `%1$s_id`', + $name); + } + } + return $fields; + } + + static function ensureDynamicDataView() { + $sql = 'SHOW TABLES LIKE \''.TABLE_PREFIX.'ticket__cdata\''; + if (!db_num_rows(db_query($sql))) + return static::buildDynamicDataView(); + } + + static function buildDynamicDataView() { + // create table __cdata (primary key (ticket_id)) as select + // entry.object_id as ticket_id, MAX(IF(field.name = 'subject', + // ans.value, NULL)) as `subject`,MAX(IF(field.name = 'priority', + // ans.value, NULL)) as `priority_desc`,MAX(IF(field.name = + // 'priority', ans.value_id, NULL)) as `priority_id` + // FROM ost_form_entry entry LEFT JOIN ost_form_entry_values ans ON + // ans.entry_id = entry.id LEFT JOIN ost_form_field field ON + // field.id=ans.field_id + // where entry.object_type='T' group by entry.object_id; + $fields = static::getDynamicDataViewFields(); + $sql = 'CREATE TABLE `'.TABLE_PREFIX.'ticket__cdata` (PRIMARY KEY (ticket_id)) AS + SELECT entry.`object_id` AS ticket_id, '.implode(',', $fields) + .' FROM ost_form_entry entry + JOIN ost_form_entry_values ans ON ans.entry_id = entry.id + JOIN ost_form_field field ON field.id=ans.field_id + WHERE entry.object_type=\'T\' GROUP BY entry.object_id'; + db_query($sql); + } + + static function dropDynamicDataView() { + db_query('DROP TABLE IF EXISTS `'.TABLE_PREFIX.'ticket__cdata`'); + } + + static function updateDynamicDataView($answer, $data) { + // TODO: Detect $data['dirty'] for value and value_id + // We're chiefly concerned with Ticket form answers + if (!($e = $answer->getEntry()) || $e->get('object_type') != 'T') + return; + + // If the `name` column is in the dirty list, we would be renaming a + // column. Delete the view instead. + if (isset($data['dirty']) && isset($data['dirty']['name'])) + return self::dropDynamicDataView(); + + // $record = array(); + // $record[$f] = $answer->value' + // TicketFormData::objects()->filter(array('ticket_id'=>$a)) + // ->merge($record); + $f = $answer->getField(); + $name = $f->get('name') ? $f->get('name') : 'field_'.$f->get('id'); + $ids = $f->hasIdValue(); + $fields = sprintf('`%s`=', $name) . db_input($answer->get('value')); + if ($f->hasIdValue()) + $fields .= sprintf(',`%s_id`=', $name) . db_input($answer->getIdValue()); + $sql = 'INSERT INTO `'.TABLE_PREFIX.'ticket__cdata` SET '.$fields + .', `ticket_id`='.db_input($answer->getEntry()->get('object_id')) + .' ON DUPLICATE KEY UPDATE '.$fields; + db_query($sql); + } } // Add fields from the standard ticket form to the ticket filterable fields Filter::addSupportedMatches('Custom Fields', function() { @@ -207,6 +292,23 @@ Filter::addSupportedMatches('Custom Fields', function() { } return $matches; }); +// Manage materialized view on custom data updates +Signal::connect('model.created', + array('TicketForm', 'updateDynamicDataView'), + 'DynamicFormEntryAnswer'); +Signal::connect('model.updated', + array('TicketForm', 'updateDynamicDataView'), + 'DynamicFormEntryAnswer'); +// Recreate the dynamic view after new or removed fields to the ticket +// details form +Signal::connect('model.created', + array('TicketForm', 'dropDynamicDataView'), + 'DynamicFormField', + function($o) { return $o->getForm()->get('type') == 'T'; }); +Signal::connect('model.deleted', + array('TicketForm', 'dropDynamicDataView'), + 'DynamicFormField', + function($o) { return $o->getForm()->get('type') == 'T'; }); require_once(INCLUDE_DIR . "class.json.php"); @@ -555,6 +657,7 @@ class DynamicFormEntry extends VerySimpleModel { array('field_id'=>$f->get('id'))); $a->field = $f; $a->field->setAnswer($a); + $a->entry = $inst; $inst->_values[] = $a; } return $inst; diff --git a/include/class.forms.php b/include/class.forms.php index b5a7802408378eac68b0d41efc302b7af944ef75..9d0bdc87e460396ee901a708b6e1ff31948fb767 100644 --- a/include/class.forms.php +++ b/include/class.forms.php @@ -440,6 +440,14 @@ class FormField { return $this->presentation_only; } + /** + * Indicates if the field places data in the `value_id` column. This + * is currently used by the materialized view system + */ + function hasIdValue() { + return false; + } + function getConfigurationForm() { if (!$this->_cform) { $type = static::getFieldType($this->get('type')); @@ -806,6 +814,10 @@ class PriorityField extends ChoiceField { return $widget; } + function hasIdValue() { + return true; + } + function getChoices() { $this->ht['default'] = 0; @@ -1140,6 +1152,7 @@ class ThreadEntryWidget extends Widget { <input type="file" class="multifile" name="attachments[]" id="attachments" size="30" value="" /> </div> <font class="error"> <?php echo $errors['attachments']; ?></font> + </div> <hr/> <?php } diff --git a/include/class.http.php b/include/class.http.php index 3aea32b6e93029e541bf0d0c9fd28bef15b20dc3..ef917845d76a1f943e022167304eb7a2b0d131c5 100644 --- a/include/class.http.php +++ b/include/class.http.php @@ -43,9 +43,14 @@ class Http { exit; } - function redirect($url,$delay=0,$msg='') { + function redirect($url,$delay=0,$msg='') { - if(strstr($_SERVER['SERVER_SOFTWARE'], 'IIS')){ + $iis = strpos($_SERVER['SERVER_SOFTWARE'], 'IIS') !== false; + @list($name, $version) = explode('/', $_SERVER['SERVER_SOFTWARE']); + // Legacy code for older versions of IIS that would not emit the + // correct HTTP status and headers when using the `Location` + // header alone + if ($iis && version_compare($version, '7.0', '<')) { header("Refresh: $delay; URL=$url"); }else{ header("Location: $url"); diff --git a/include/class.mailer.php b/include/class.mailer.php index a57f8d7be1ac5b7e15f79fd3d8430cbfd2d58819..21a7d157d68a28191b88e798dade0633d3c15725 100644 --- a/include/class.mailer.php +++ b/include/class.mailer.php @@ -139,12 +139,11 @@ class Mailer { $mime = new Mail_mime(); + // If the message is not explicitly declared to be a text message, + // then assume that it needs html processing to create a valid text + // body $isHtml = true; - // Ensure that the 'text' option / hint is not set to true and that - // the message appears to be HTML -- that is, the first - // non-whitespace char is a '<' character - if (!(isset($options['text']) && $options['text']) - && (!$cfg || $cfg->isHtmlThreadEnabled())) { + if (!(isset($options['text']) && $options['text'])) { // Make sure nothing unsafe has creeped into the message $message = Format::safe_html($message); //XXX?? $mime->setTXTBody(Format::html2text($message, 90, false)); diff --git a/include/class.orm.php b/include/class.orm.php index 38d3482daf79c91fddeb9ef0f9b81240d4a4ed42..dee287cea77adc73d323db573e79a83151a998de 100644 --- a/include/class.orm.php +++ b/include/class.orm.php @@ -147,7 +147,10 @@ class VerySimpleModel { foreach ($pk as $p) $filter[] = $p.' = '.db_input($this->get($p)); $sql .= ' WHERE '.implode(' AND ', $filter).' LIMIT 1'; - return db_query($sql) && db_affected_rows() == 1; + if (!db_query($sql) || db_affected_rows() != 1) + throw new Exception(db_error()); + Signal::send('model.deleted', $this); + return true; } function save($refetch=false) { @@ -183,6 +186,11 @@ class VerySimpleModel { $this->__new__ = false; // Setup lists again $this->__setupForeignLists(); + Signal::send('model.created', $this); + } + else { + $data = array('dirty' => $this->dirty); + Signal::send('model.updated', $this, $data); } # Refetch row from database # XXX: Too much voodoo diff --git a/include/class.signal.php b/include/class.signal.php index f931acebc50c8beaf020859279c2ba0cba56f1de..928c15c4d2392ae767a0c465b6730d859f3dc85f 100644 --- a/include/class.signal.php +++ b/include/class.signal.php @@ -93,7 +93,7 @@ class Signal { list($s, $callable, $check) = $sub; if ($s && !is_a($object, $s)) continue; - elseif ($check && !call_user_func($check, $data)) + elseif ($check && !call_user_func($check, $object, $data)) continue; call_user_func($callable, $object, $data); } diff --git a/include/class.thread.php b/include/class.thread.php index 3ebdc83a6729e9130134c256040d246b589512bb..5e6c88ea7a9c9ccdb6a4074d0c550729136662f0 100644 --- a/include/class.thread.php +++ b/include/class.thread.php @@ -746,7 +746,7 @@ Class ThreadEntry { $subject = $mailinfo['subject']; $match = array(); if ($subject && $mailinfo['email'] - && preg_match("/\[#([0-9]{1,10})\]/", $subject, $match) + && preg_match("/#[\p{L}-]+?([0-9]{1,10})/u", $subject, $match) && ($tid = Ticket::getIdByExtId((int)$match[1], $mailinfo['email'])) ) // Return last message for the thread diff --git a/include/class.ticket.php b/include/class.ticket.php index bb07895ae17cb779e64ccad3a0c71aa8452350ca..97d83f293029e7c916afafabe510b414431fe5c7 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -1486,7 +1486,6 @@ class Ticket { && ($tpl = $dept->getTemplate()) && ($msg = $tpl->getNewMessageAlertMsgTemplate())) { - $attachments = $message->getAttachments(); $msg = $this->replaceVars($msg->asArray(), $variables); //Build list of recipients and fire the alerts. @@ -1508,7 +1507,7 @@ class Ticket { foreach( $recipients as $k=>$staff) { if(!$staff || !$staff->getEmail() || !$staff->isAvailable() || in_array($staff->getEmail(), $sentlist)) continue; $alert = $this->replaceVars($msg, array('recipient' => $staff)); - $email->sendAlert($staff->getEmail(), $alert['subj'], $alert['body'], $attachments, $options); + $email->sendAlert($staff->getEmail(), $alert['subj'], $alert['body'], null, $options); $sentlist[] = $staff->getEmail(); } } @@ -1557,8 +1556,6 @@ class Ticket { else $signature=''; - $attachments =($cfg->emailAttachments() && $files)?$response->getAttachments():array(); - $msg = $this->replaceVars($msg->asArray(), array('response' => $response, 'signature' => $signature)); @@ -1718,8 +1715,6 @@ class Ticket { && ($tpl = $dept->getTemplate()) && ($msg=$tpl->getNoteAlertMsgTemplate())) { - $attachments = $note->getAttachments(); - $msg = $this->replaceVars($msg->asArray(), array('note' => $note)); @@ -1738,7 +1733,6 @@ class Ticket { if($cfg->alertDeptManagerONNewNote() && $dept && $dept->getManagerId()) $recipients[]=$dept->getManager(); - $attachments = $note->getAttachments(); $options = array( 'inreplyto'=>$note->getEmailMessageId(), 'references'=>$note->getEmailReferences()); @@ -1750,7 +1744,7 @@ class Ticket { || $note->getStaffId() == $staff->getId()) //No need to alert the poster! continue; $alert = $this->replaceVars($msg, array('recipient' => $staff)); - $email->sendAlert($staff->getEmail(), $alert['subj'], $alert['body'], $attachments, $options); + $email->sendAlert($staff->getEmail(), $alert['subj'], $alert['body'], null, $options); $sentlist[] = $staff->getEmail(); } } diff --git a/include/client/header.inc.php b/include/client/header.inc.php index 7266060fbf2f50160e54b1a275f3b4e90fafc87e..9f20098b4b1b4fcfc2d891973717c42876296fde 100644 --- a/include/client/header.inc.php +++ b/include/client/header.inc.php @@ -3,6 +3,7 @@ $title=($cfg && is_object($cfg) && $cfg->getTitle())?$cfg->getTitle():'osTicket header("Content-Type: text/html; charset=UTF-8\r\n"); ?> <!DOCTYPE html> +<html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> diff --git a/include/mysqli.php b/include/mysqli.php index 34fa57479b64ebcda7e1ac381f4c70a6069773aa..7245d9ec20eeab75197500a8b9f5d8f06af8e567 100644 --- a/include/mysqli.php +++ b/include/mysqli.php @@ -39,18 +39,24 @@ function db_connect($host, $user, $passwd, $options = array()) { return NULL; $port = ini_get("mysqli.default_port"); + $socket = ini_get("mysqli.default_socket"); if (strpos($host, ':') !== false) { - list($host, $port) = explode(':', $host); + list($host, $portspec) = explode(':', $host); // PHP may not honor the port number if connecting to 'localhost' - if (!strcasecmp($host, 'localhost')) - // XXX: Looks like PHP gethostbyname() is IPv4 only - $host = gethostbyname($host); - $port = (int) $port; + if ($portspec && is_numeric($portspec)) { + if (!strcasecmp($host, 'localhost')) + // XXX: Looks like PHP gethostbyname() is IPv4 only + $host = gethostbyname($host); + $port = (int) $portspec; + } + elseif ($portspec) { + $socket = $portspec; + } } // Connect $start = microtime(true); - if (!@$__db->real_connect($host, $user, $passwd, null, $port)) + if (!@$__db->real_connect($host, $user, $passwd, null, $port, $socket)) return NULL; //Select the database, if any. diff --git a/include/staff/header.inc.php b/include/staff/header.inc.php index ded810d8996c70fed985b75c690abdb5f9e8974b..d2a3e43b82277bb13eb38718e06bfd84340457f9 100644 --- a/include/staff/header.inc.php +++ b/include/staff/header.inc.php @@ -2,6 +2,7 @@ <html> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta http-equiv="cache-control" content="no-cache" /> <meta http-equiv="pragma" content="no-cache" /> <title><?php echo ($ost && ($title=$ost->getPageTitle()))?$title:'osTicket :: Staff Control Panel'; ?></title> diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php index e463e515d88a0afa7c7e013f57adf9f411d0dc9a..1415ce76d059cb8617c26f7f66456e51ba9bf4b9 100644 --- a/include/staff/tickets.inc.php +++ b/include/staff/tickets.inc.php @@ -129,6 +129,9 @@ if($search): if (count($tickets)) $qwhere .= ' AND ticket.ticket_id IN ('. implode(',',db_input($tickets)).')'; + else + // No hits -- there should be an empty list of results + $qwhere .= ' AND false'; } } @@ -192,10 +195,7 @@ $$x=' class="'.strtolower($order).'" '; if($_GET['limit']) $qstr.='&limit='.urlencode($_GET['limit']); -$qselect ='SELECT DISTINCT ticket.ticket_id,lock_id,ticketID,ticket.dept_id,ticket.staff_id,ticket.team_id ' - .',MAX(IF(field.name = \'subject\', ans.value, NULL)) as `subject`' - .',MAX(IF(field.name = \'priority\', ans.value, NULL)) as `priority_desc`' - .',MAX(IF(field.name = \'priority\', ans.value_id, NULL)) as `priority_id`' +$qselect ='SELECT ticket.ticket_id,lock_id,ticketID,ticket.dept_id,ticket.staff_id,ticket.team_id ' .' ,user.name' .' ,email.address as email, dept_name ' .' ,ticket.status,ticket.source,isoverdue,isanswered,ticket.created '; @@ -203,18 +203,13 @@ $qselect ='SELECT DISTINCT ticket.ticket_id,lock_id,ticketID,ticket.dept_id,tick $qfrom=' FROM '.TICKET_TABLE.' ticket '. ' LEFT JOIN '.USER_TABLE.' user ON user.id = ticket.user_id'. ' LEFT JOIN '.USER_EMAIL_TABLE.' email ON user.id = email.user_id'. - ' LEFT JOIN '.DEPT_TABLE.' dept ON ticket.dept_id=dept.dept_id '. - ' LEFT JOIN '.FORM_ENTRY_TABLE.' entry ON entry.object_type=\'T\' - and entry.object_id=ticket.ticket_id'. - ' LEFT JOIN '.FORM_ANSWER_TABLE.' ans ON ans.entry_id = entry.id'. - ' LEFT JOIN '.FORM_FIELD_TABLE.' field ON field.id=ans.field_id'; + ' LEFT JOIN '.DEPT_TABLE.' dept ON ticket.dept_id=dept.dept_id '; $sjoin=''; if($search && $deep_search) { $sjoin=' LEFT JOIN '.TICKET_THREAD_TABLE.' thread ON (ticket.ticket_id=thread.ticket_id )'; } -$qgroup=' GROUP BY ticket.ticket_id'; //get ticket count based on the query so far.. $total=db_count("SELECT count(DISTINCT ticket.ticket_id) $qfrom $sjoin $qwhere"); //pagenate @@ -224,23 +219,23 @@ $pageNav=new Pagenate($total,$page,$pagelimit); $pageNav->setURL('tickets.php',$qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order'])); //ADD attachment,priorities, lock and other crap -$qselect.=' ,count(attach.attach_id) as attachments ' - .' ,count(DISTINCT thread.id) as thread_count ' - .' ,IF(ticket.duedate IS NULL,IF(sla.id IS NULL, NULL, DATE_ADD(ticket.created, INTERVAL sla.grace_period HOUR)), ticket.duedate) as duedate ' +$qselect.=' ,IF(ticket.duedate IS NULL,IF(sla.id IS NULL, NULL, DATE_ADD(ticket.created, INTERVAL sla.grace_period HOUR)), ticket.duedate) as duedate ' .' ,CAST(GREATEST(IFNULL(ticket.lastmessage, 0), IFNULL(ticket.reopened, 0), ticket.created) as datetime) as effective_date ' .' ,CONCAT_WS(" ", staff.firstname, staff.lastname) as staff, team.name as team ' .' ,IF(staff.staff_id IS NULL,team.name,CONCAT_WS(" ", staff.lastname, staff.firstname)) as assigned ' - .' ,IF(ptopic.topic_pid IS NULL, topic.topic, CONCAT_WS(" / ", ptopic.topic, topic.topic)) as helptopic '; + .' ,IF(ptopic.topic_pid IS NULL, topic.topic, CONCAT_WS(" / ", ptopic.topic, topic.topic)) as helptopic ' + .' ,cdata.priority_id, cdata.subject'; $qfrom.=' LEFT JOIN '.TICKET_LOCK_TABLE.' tlock ON (ticket.ticket_id=tlock.ticket_id AND tlock.expire>NOW() AND tlock.staff_id!='.db_input($thisstaff->getId()).') ' - .' LEFT JOIN '.TICKET_ATTACHMENT_TABLE.' attach ON (ticket.ticket_id=attach.ticket_id) ' - .' LEFT JOIN '.TICKET_THREAD_TABLE.' thread ON ( ticket.ticket_id=thread.ticket_id) ' .' LEFT JOIN '.STAFF_TABLE.' staff ON (ticket.staff_id=staff.staff_id) ' .' LEFT JOIN '.TEAM_TABLE.' team ON (ticket.team_id=team.team_id) ' .' LEFT JOIN '.SLA_TABLE.' sla ON (ticket.sla_id=sla.id AND sla.isactive=1) ' .' LEFT JOIN '.TOPIC_TABLE.' topic ON (ticket.topic_id=topic.topic_id) ' - .' LEFT JOIN '.TOPIC_TABLE.' ptopic ON (ptopic.topic_id=topic.topic_pid) '; + .' LEFT JOIN '.TOPIC_TABLE.' ptopic ON (ptopic.topic_id=topic.topic_pid) ' + .' LEFT JOIN '.TABLE_PREFIX.'ticket__cdata cdata ON (cdata.ticket_id = ticket.ticket_id) '; + +TicketForm::ensureDynamicDataView(); // Fetch priority information $res = db_query('select * from '.PRIORITY_TABLE); @@ -248,7 +243,7 @@ $prios = array(); while ($row = db_fetch_array($res)) $prios[$row['priority_id']] = $row; -$query="$qselect $qfrom $qwhere $qgroup ORDER BY $order_by $order LIMIT ".$pageNav->getStart().",".$pageNav->getLimit(); +$query="$qselect $qfrom $qwhere ORDER BY $order_by $order LIMIT ".$pageNav->getStart().",".$pageNav->getLimit(); //echo $query; $hash = md5($query); $_SESSION['search_'.$hash] = $query; @@ -262,6 +257,27 @@ if($search) $negorder=$order=='DESC'?'ASC':'DESC'; //Negate the sorting.. +// Fetch the results +$results = array(); +while ($row = db_fetch_array($res)) { + $results[$row['ticket_id']] = $row; +} + +// Fetch attachment and thread entry counts +if ($results) { + $counts_sql = 'SELECT ticket.ticket_id, count(attach.attach_id) as attachments, + count(DISTINCT thread.id) as thread_count + FROM '.TICKET_TABLE.' ticket + LEFT JOIN '.TICKET_ATTACHMENT_TABLE.' attach ON (ticket.ticket_id=attach.ticket_id) ' + .' LEFT JOIN '.TICKET_THREAD_TABLE.' thread ON ( ticket.ticket_id=thread.ticket_id) ' + .' WHERE ticket.ticket_id IN ('.implode(',', db_input(array_keys($results))).') + GROUP BY ticket.ticket_id'; + $ids_res = db_query($counts_sql); + while ($row = db_fetch_array($ids_res)) { + $results[$row['ticket_id']] += $row; + } +} + //YOU BREAK IT YOU FIX IT. ?> <!-- SEARCH FORM START --> @@ -345,9 +361,9 @@ $negorder=$order=='DESC'?'ASC':'DESC'; //Negate the sorting.. <?php $class = "row1"; $total=0; - if($res && ($num=db_num_rows($res))): + if($res && ($num=count($results))): $ids=($errors && $_POST['tids'] && is_array($_POST['tids']))?$_POST['tids']:null; - while ($row = db_fetch_array($res)) { + foreach ($results as $row) { $tag=$row['staff_id']?'assigned':'openticket'; $flag=null; if($row['lock_id']) diff --git a/include/upgrader/streams/core/c00511c7-7be60a84.patch.sql b/include/upgrader/streams/core/c00511c7-7be60a84.patch.sql index e565227249787174009fddfb6e7f06ed82f01d52..97c2bf97b45d6e5fa1368e8011d05e07d48c545e 100644 --- a/include/upgrader/streams/core/c00511c7-7be60a84.patch.sql +++ b/include/upgrader/streams/core/c00511c7-7be60a84.patch.sql @@ -224,7 +224,7 @@ CREATE TABLE `%TABLE_PREFIX%email_filter_rule` ( -- SYSTEM BAN LIST was the first filter created, with ID of '1' INSERT INTO `%TABLE_PREFIX%email_filter_rule` (`filter_id`, `what`, `how`, `val`) - SELECT LAST_INSERT_ID(), 'email', 'equals', email FROM `%TABLE_PREFIX%email_banlist`; + SELECT LAST_INSERT_ID(), 'email', 'equal', email FROM `%TABLE_PREFIX%email_banlist`; -- Create table session DROP TABLE IF EXISTS `%TABLE_PREFIX%session`; diff --git a/js/redactor-osticket.js b/js/redactor-osticket.js index 0fed815da1831b9425a22d6f2702766e1ca81362..337d525a5d1cbc5a85601313cf2e941b242d00d4 100644 --- a/js/redactor-osticket.js +++ b/js/redactor-osticket.js @@ -140,7 +140,12 @@ $(function() { var el = $(el), options = { 'air': el.hasClass('no-bar'), - 'airButtons': ['formatting', '|', 'bold', 'italic', 'deleted', '|', 'unorderedlist', 'orderedlist', 'outdent', 'indent', '|', 'image'], + 'airButtons': ['formatting', '|', 'bold', 'italic', 'underline', 'deleted', '|', 'unorderedlist', 'orderedlist', 'outdent', 'indent', '|', 'image'], + 'buttons': ['html', '|', 'formatting', '|', 'bold', + 'italic', 'underline', 'deleted', '|', 'unorderedlist', + 'orderedlist', 'outdent', 'indent', '|', 'image', 'video', + 'file', 'table', 'link', '|', 'alignment', '|', + 'horizontalrule'], 'autoresize': !el.hasClass('no-bar'), 'minHeight': el.hasClass('small') ? 75 : 150, 'focus': false, diff --git a/scp/forms.php b/scp/forms.php index 5af2dd327781d18adca740b46ad654eb272929db..6f14be6c1a2bec06aea87c6f1c4c97d9db177efd 100644 --- a/scp/forms.php +++ b/scp/forms.php @@ -99,6 +99,7 @@ if($_POST) { 'private'=>$_POST["private-new-$i"] == 'on' ? 1 : 0, 'required'=>$_POST["required-new-$i"] == 'on' ? 1 : 0 )); + $field->setForm($form); if ($field->isValid()) $field->save(); else diff --git a/setup/inc/class.installer.php b/setup/inc/class.installer.php index 4ec86eee5479e8902871bc1081f407e19deb3bda..3912a3a99a8d66049adb9de64b51b49e837c248d 100644 --- a/setup/inc/class.installer.php +++ b/setup/inc/class.installer.php @@ -82,9 +82,7 @@ class Installer extends SetupWizard { // Support port number specified in the hostname with a colon (:) list($host, $port) = explode(':', $vars['dbhost']); - if ($port && (!is_numeric($port) || !((int)$port))) - $this->errors['db'] = 'Database port number must be a number'; - elseif ($port && ($port < 1 || $port > 65535)) + if ($port && is_numeric($port) && ($port < 1 || $port > 65535)) $this->errors['db'] = 'Invalid database port number'; //MYSQL: Connect to the DB and check the version & database (create database if it doesn't exist!)