diff --git a/api/cron.php b/api/cron.php index 0ca641b52062d6a817f67d49297e9e062d63f31b..f5d47e43e2af43e1d26a1f8a5e9af698bc7e0b8f 100644 --- a/api/cron.php +++ b/api/cron.php @@ -19,8 +19,6 @@ require('api.inc.php'); if (!osTicket::is_cli()) die('cron.php only supports local cron calls - use http -> api/tasks/cron'); -@chdir(realpath(dirname(__FILE__)).'/'); //Change dir. -require('api.inc.php'); require_once(INCLUDE_DIR.'api.cron.php'); LocalCronApiController::call(); ?> diff --git a/bootstrap.php b/bootstrap.php index e99b1e339c1216ea3e5f03dcd78eb090465f1bbe..d050960ed4ffdeb0443e5878029d3539e625901f 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -43,6 +43,9 @@ class Bootstrap { ini_set('date.timezone', 'America/New_York'); } } + + if (!isset($_SERVER['REMOTE_ADDR'])) + $_SERVER['REMOTE_ADDR'] = ''; } function https() { diff --git a/css/redactor.css b/css/redactor.css index 6a5c8df81fb6f5a2c929221d6bbc92a66d84790a..b179cfd42fe90e378a3ed32c0340f07d811a0cbe 100644 --- a/css/redactor.css +++ b/css/redactor.css @@ -82,7 +82,7 @@ body .redactor_box_fullscreen { .redactor_editor blockquote, .redactor_editor pre { font-size: 15px; - line-height: 1.5rem; + line-height: 1.25rem; } .redactor_editor, diff --git a/css/thread.css b/css/thread.css index 9623bcf25466d22155c67ae03b2b6e2a6dddfbdc..cd3ad3d3679ecd816401570ea9ca0264855657fb 100644 --- a/css/thread.css +++ b/css/thread.css @@ -306,7 +306,6 @@ border-left: 5px solid #eeeeee; } .thread-body blockquote p { - font-size: 17.5px; font-weight: 300; line-height: 1.25; } @@ -410,3 +409,9 @@ float: none; display: table-cell; } + +/* Additional style for the mighty Microsoft Office emails "standard" style */ +p.MsoNormal, li.MsoNormal, div.MsoNormal, +p.MsoPlainText, li.MsoPlainText, div.MsoPlainText + {margin:0cm; + margin-bottom:.0001pt;} diff --git a/include/ajax.kbase.php b/include/ajax.kbase.php index ba0cf1d1cc523f3030017b5cbbc821aed8185933..1e642847bba383d986bafeadadee6370bcec54ed 100644 --- a/include/ajax.kbase.php +++ b/include/ajax.kbase.php @@ -32,6 +32,7 @@ class KbaseAjaxAPI extends AjaxController { $ticket = Ticket::lookup($_GET['tid']); } + $resp = array(); switch($format) { case 'json': $resp['id'] = $canned->getId(); @@ -42,7 +43,7 @@ class KbaseAjaxAPI extends AjaxController { $resp['files'] = $canned->attachments->getSeparates(); if (!$cfg->isHtmlThreadEnabled()) { - $resp['response'] = convert_html_to_text($resp['response'], 90); + $resp['response'] = Format::html2text($resp['response'], 90); $resp['files'] += $canned->attachments->getInlines(); } @@ -54,7 +55,7 @@ class KbaseAjaxAPI extends AjaxController { $response =$ticket?$ticket->replaceVars($canned->getResponse()):$canned->getResponse(); if (!$cfg->isHtmlThreadEnabled()) - $response = convert_html_to_text($response, 90); + $response = Format::html2text($response, 90); } return $response; diff --git a/include/ajax.tickets.php b/include/ajax.tickets.php index c0f69e8a95c35c8546b6f9d67f9f1a7c4f314a5f..a142fc1056deab841b3cbf5f590a19295313ac80 100644 --- a/include/ajax.tickets.php +++ b/include/ajax.tickets.php @@ -214,8 +214,8 @@ class TicketsAjaxAPI extends AjaxController { if ($f->get('name') && isset($req[$f->getFormName()]) && ($val = $req[$f->getFormName()])) { $name = $f->get('name'); - $vals[] = "MAX(IF(field.name = '$name', ans.value_id, NULL)) as `{$name}_id`"; # nolint - $vals[] = "MAX(IF(field.name = '$name', ans.value, NULL)) as `$name`"; # nolint + $vals[] = "MAX(IF(field.name = '$name', ans.value_id, NULL)) as `{$name}_id`"; + $vals[] = "MAX(IF(field.name = '$name', ans.value, NULL)) as `$name`"; $where .= " AND (dyn.`{$name}_id` = ".db_input($val) . " OR dyn.`$name` LIKE '%".db_real_escape($val)."%')"; } @@ -236,6 +236,7 @@ class TicketsAjaxAPI extends AjaxController { function search() { $tickets = self::_search($_REQUEST); + $result = array(); if (count($tickets)) { $uid = md5($_SERVER['QUERY_STRING']); @@ -414,6 +415,8 @@ class TicketsAjaxAPI extends AjaxController { $ticket->getEmail()); echo ' </table>'; + + $options = array(); $options[]=array('action'=>'Thread ('.$ticket->getThreadCount().')','url'=>"tickets.php?id=$tid"); if($ticket->getNumNotes()) $options[]=array('action'=>'Notes ('.$ticket->getNumNotes().')','url'=>"tickets.php?id=$tid#notes"); diff --git a/include/class.api.php b/include/class.api.php index 6dec23efbc71969df796ecb8d9fc206f090b4129..23ae8a589e9192a08394134929ff9429695b2f30 100644 --- a/include/class.api.php +++ b/include/class.api.php @@ -396,7 +396,7 @@ class ApiJsonDataParser extends JsonDataParser { "name" => key($info), ); } - unset($value); + unset($info); } if (is_array($value)) { $value = $this->fixup($value); diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php index 79a092cbbc98dcb7c26e8771a419c7e06be0207e..e0a2f1e8619299de4cb6198d749d4f33613d0451 100644 --- a/include/class.dynamic_forms.php +++ b/include/class.dynamic_forms.php @@ -708,6 +708,8 @@ class DynamicListItem extends VerySimpleModel { } class SelectionField extends FormField { + static $widget = 'SelectionWidget'; + function getListId() { list(,$list_id) = explode('-', $this->get('type')); return $list_id; @@ -719,10 +721,6 @@ class SelectionField extends FormField { return $this->_list; } - function getWidget() { - return new SelectionWidget($this); - } - function parse($value) { return $this->to_php($value); } diff --git a/include/class.faq.php b/include/class.faq.php index 7cb3c9a950ee9e2461fabf79d5f8b3ec57c07f62..70a7e2540dd91335c91e470c163abf430d0b63f1 100644 --- a/include/class.faq.php +++ b/include/class.faq.php @@ -112,7 +112,7 @@ class FAQ { function setKeywords($words) { $this->ht['keywords'] = $words; } function setNotes($text) { $this->ht['notes'] = $text; } - /* For ->attach() and ->detach(), use $this->attachments() */ + /* For ->attach() and ->detach(), use $this->attachments() (nolint) */ function attach($file) { return $this->_attachments->add($file); } function detach($file) { return $this->_attachments->remove($file); } @@ -130,8 +130,9 @@ class FAQ { /* Same as update - but mainly called after one or more setters are changed. */ function apply() { + $errors = array(); //XXX: set errors and add ->getErrors() & ->getError() - return $this->update($this->ht, $errors); # nolint + return $this->update($this->ht, $errors); } function updateTopics($ids){ diff --git a/include/class.file.php b/include/class.file.php index 5083c76f9fc7b9a53dc6c2275597f18b003dab0e..8acd7e42949f0c097bcb75d11c35823a224b013c 100644 --- a/include/class.file.php +++ b/include/class.file.php @@ -254,12 +254,14 @@ class AttachmentFile { function save($file) { - if(!$file['hash']) - $file['hash']=MD5(MD5($file['data']).time()); if (is_callable($file['data'])) $file['data'] = $file['data'](); + + if(!$file['hash']) + $file['hash'] = MD5(MD5($file['data']).time()); + if(!$file['size']) - $file['size']=strlen($file['data']); + $file['size'] = strlen($file['data']); $sql='INSERT INTO '.FILE_TABLE.' SET created=NOW() ' .',type='.db_input(strtolower($file['type'])) diff --git a/include/class.filter.php b/include/class.filter.php index 81c6a39fe17a86e8e5981385a0dd417d6e47955f..15c68cc3170596abef12fb44e1eaf4365b7c447c 100644 --- a/include/class.filter.php +++ b/include/class.filter.php @@ -175,11 +175,12 @@ class Filter { } function addRule($what, $how, $val,$extra=array()) { + $errors = array(); $rule= array_merge($extra,array('what'=>$what, 'how'=>$how, 'val'=>$val)); $rule['filter_id']=$this->getId(); - return FilterRule::create($rule,$errors); # nolint + return FilterRule::create($rule,$errors); } function removeRule($what, $how, $val) { @@ -512,7 +513,8 @@ class Filter { //Success with update/create...save the rules. We can't recover from any errors at this point. # Don't care about errors stashed in $xerrors - self::save_rules($id,$vars,$xerrors); # nolint + $xerrors = array(); + self::save_rules($id,$vars,$xerrors); return true; } diff --git a/include/class.format.php b/include/class.format.php index 843033ae205875af379cff31c7c26026df1a2855..fd51d95867d76918bb85e5a34d1eb524ee2066ff 100644 --- a/include/class.format.php +++ b/include/class.format.php @@ -128,8 +128,8 @@ class Format { return is_array($var)?array_map(array('Format','strip_slashes'),$var):stripslashes($var); } - function wrap($text,$len=75) { - return wordwrap($text,$len,"\n",true); + function wrap($text, $len=75) { + return $len ? wordwrap($text, $len, "\n", true) : $text; } function html($html, $config=array('balance'=>1)) { @@ -137,13 +137,64 @@ class Format { return htmLawed($html, $config); } + function html2text($html, $width=74, $tidy=true) { + + + # Tidy html: decode, balance, sanitize tags + if($tidy) + $html = Format::html(Format::htmldecode($html), array('balance' => 1)); + + # See if advanced html2text is available (requires xml extension) + if (function_exists('convert_html_to_text') + && extension_loaded('xml')) + return convert_html_to_text($html, $width); + + # Try simple html2text - insert line breaks after new line tags. + $html = preg_replace( + array(':<br ?/?\>:i', ':(</div>)\s*:i', ':(</p>)\s*:i'), + array("\n", "$1\n", "$1\n\n"), + $html); + + # Strip tags, decode html chars and wrap resulting text. + return Format::wrap( + Format::htmldecode( Format::striptags($html, false)), + $width); + } + function safe_html($html) { + // Remove HEAD and STYLE sections + $html = preg_replace(':<(head|style).+</\1>:is','', $html); $config = array( - 'safe' => 1, //Exclude applet, embed, iframe, object and script tags. - 'balance' => 1, //balance and close unclosed tags. - 'comment' => 1, //Remove html comments (OUTLOOK LOVE THEM) - 'schemes' => 'href: aim, feed, file, ftp, gopher, http, https, irc, mailto, news, nntp, sftp, ssh, telnet; *:file, http, https; src: cid, http, https, data' - ); + 'safe' => 1, //Exclude applet, embed, iframe, object and script tags. + 'balance' => 1, //balance and close unclosed tags. + 'comment' => 1, //Remove html comments (OUTLOOK LOVE THEM) + 'deny_attribute' => 'id', + 'schemes' => 'href: aim, feed, file, ftp, gopher, http, https, irc, mailto, news, nntp, sftp, ssh, telnet; *:file, http, https; src: cid, http, https, data', + 'hook_tag' => function ($el, $attributes=0) { + static $eE = array('area'=>1, 'br'=>1, 'col'=>1, 'embed'=>1, + 'hr'=>1, 'img'=>1, 'input'=>1, 'isindex'=>1, 'param'=>1); + if (isset($attributes['class'])) { + $classes = explode(' ', $attributes['class']); + foreach ($classes as $i=>$a) + // Unset all unsupported style classes -- anything by M$ + if (strpos($a, 'Mso') !== 0) + unset($classes[$i]); + if ($classes) + $attributes['class'] = implode(' ', $classes); + else + unset($attributes['class']); + } + $at = ''; + if (is_array($attributes)) { + foreach ($attributes as $k=>$v) + $at .= " $k=\"$v\""; + return "<{$el}{$at}".(isset($eE[$el])?" /":"").">"; + } + else { + return "</{$el}>"; + } + } + ); if (!preg_match('/style="[^"]*white-space:\s*pre/i', $html) !== false) $config['tidy'] = -1; // Clean extra whitspace @@ -209,8 +260,8 @@ class Format { //Wrap long words... #$text=preg_replace_callback('/\w{75,}/', # create_function( - # '$matches', # nolint - # 'return wordwrap($matches[0],70,"\n",true);'), # nolint + # '$matches', + # 'return wordwrap($matches[0],70,"\n",true);'), # $text); // Make showing offsite images optional @@ -244,14 +295,14 @@ class Format { $token = $ost->getLinkToken(); //Not perfect but it works - please help improve it. $text=preg_replace_callback('/(?<!"|>)(((f|ht)tp(s?):\/\/)[-a-zA-Z0-9@:%_\+.~#?&;\/\/=]+)/', - create_function('$matches', # nolint - sprintf('return "<a href=\"l.php?url=".urlencode($matches[1])."&auth=%s\" target=\"_blank\">".$matches[1]."</a>";', # nolint + create_function('$matches', + sprintf('return "<a href=\"l.php?url=".urlencode($matches[1])."&auth=%s\" target=\"_blank\">".$matches[1]."</a>";', $token)), $text); $text=preg_replace_callback("/(^|[ \\n\\r\\t])(www\.([a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+)(\/[^\/ \\n\\r]*)*)/", - create_function('$matches', # nolint - sprintf('return "<a href=\"l.php?url=".urlencode("http://".$matches[2])."&auth=%s\" target=\"_blank\">".$matches[2]."</a>";', # nolint + create_function('$matches', + sprintf('return "<a href=\"l.php?url=".urlencode("http://".$matches[2])."&auth=%s\" target=\"_blank\">".$matches[2]."</a>";', $token)), $text); diff --git a/include/class.mailer.php b/include/class.mailer.php index e13eff86e450589c82253468e14864d0b97d4c1b..61dda2c001b1895ed1ad9643e55c0c5bde4db1e4 100644 --- a/include/class.mailer.php +++ b/include/class.mailer.php @@ -143,8 +143,8 @@ class Mailer { if (!(isset($options['text']) && $options['text']) && preg_match('/^\s*</', $message)) { // Make sure nothing unsafe has creeped into the message - $message = Format::safe_html($message); - $mime->setTXTBody(convert_html_to_text($message)); + $message = Format::safe_html($message); //XXX?? + $mime->setTXTBody(Format::html2text($message), 90, false); } else { $mime->setTXTBody($message); diff --git a/include/class.mailfetch.php b/include/class.mailfetch.php index b9c9602fd9aa741bb9d955621ae5286f59523b36..f71c002517911d3fc3769086130eba35f8f622eb 100644 --- a/include/class.mailfetch.php +++ b/include/class.mailfetch.php @@ -422,24 +422,31 @@ class MailFetcher { function getBody($mid) { global $cfg; - if ($body = $this->getPart($mid,'TEXT/HTML', $this->charset)) { - //Convert tags of interest before we striptags - //$body=str_replace("</DIV><DIV>", "\n", $body); - //$body=str_replace(array("<br>", "<br />", "<BR>", "<BR />"), "\n", $body); - $body=Format::safe_html($body); //Balance html tags & neutralize unsafe tags. - if (!$cfg->isHtmlThreadEnabled()) - $body = convert_html_to_text($body); + if ($cfg->isHtmlThreadEnabled()) { + if ($body=$this->getPart($mid, 'text/html', $this->charset)) { + //Cleanup the html. + $body = (trim($body, " <>br/\t\n\r")) + ? Format::safe_html($body) + : '--'; + } + elseif ($body=$this->getPart($mid, 'text/plain', $this->charset)) { + $body = trim($body) + ? sprintf('<div style="white-space:pre-wrap">%s</div>', + Format::htmlchars($body)) + : '--'; + } } - elseif ($body = $this->getPart($mid,'TEXT/PLAIN', $this->charset)) { - // Escape anything that looks like HTML chars since what's in - // the database will be considered HTML - // TODO: Consider the reverse of the above edits (replace \n - // <br/> - $body=Format::htmlchars($body); - if ($cfg->isHtmlThreadEnabled()) { - $body = wordwrap($body, 90); - $body = "<div style=\"white-space:pre\">$body</div>"; + else { + if ($body=$this->getPart($mid, 'text/plain', $this->charset)) { + $body = Format::htmlchars($body); + } + elseif ($body=$this->getPart($mid, 'text/html', $this->charset)) { + $body = Format::html2text(Format::safe_html($body), 100, false); } + $body = trim($body) + ? sprintf('<div style="white-space:pre-wrap">%s</div>', + $body) + : '--'; } return $body; } diff --git a/include/class.mailparse.php b/include/class.mailparse.php index fa88c393d342136af381dc9f39a4a5b372fd90e5..15247689ca10f3ea5c059c11efd26956002ae70f 100644 --- a/include/class.mailparse.php +++ b/include/class.mailparse.php @@ -60,11 +60,11 @@ class Mail_Parse { } function splitBodyHeader() { - + $match = array(); if (preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $this->mime_message, - $match)) { # nolint - $this->header=$match[1]; # nolint + $match)) { + $this->header=$match[1]; } } /** @@ -172,17 +172,31 @@ class Mail_Parse { function getBody(){ global $cfg; - if ($body=$this->getPart($this->struct,'text/html')) { - //Cleanup the html. - $body=Format::safe_html($body); //Balance html tags & neutralize unsafe tags. - if (!$cfg->isHtmlThreadEnabled()) { - $body = convert_html_to_text($body, 120); - $body = "<div style=\"white-space:pre-wrap\">$body</div>"; + if ($cfg->isHtmlThreadEnabled()) { + if ($body=$this->getPart($this->struct,'text/html')) { + // Cleanup the html -- Balance html tags & neutralize unsafe tags. + $body = (trim($body, " <>br/\t\n\r")) + ? Format::safe_html($body) + : '--'; + } + elseif ($body=$this->getPart($this->struct,'text/plain')) { + $body = trim($body) + ? sprintf('<div style="white-space:pre-wrap">%s</div>', + Format::htmlchars($body)) + : '--'; } } - elseif ($body=$this->getPart($this->struct,'text/plain')) { - $body = Format::htmlchars($body); - $body = "<div style=\"white-space:pre-wrap\">$body</div>"; + else { + if ($body=$this->getPart($this->struct,'text/plain')) { + $body = Format::htmlchars($body); + } + elseif ($body=$this->getPart($this->struct,'text/html')) { + $body = Format::html2text(Format::safe_html($body), 100, false); + } + $body = trim($body) + ? sprintf('<div style="white-space:pre-wrap">%s</div>', + $body) + : '--'; } return $body; } diff --git a/include/class.orm.php b/include/class.orm.php index 90557fa7677df8c44dc68125c97ad8bfd72a9b86..05201c6682bc38cb4579b14492e6f62c3d7c406e 100644 --- a/include/class.orm.php +++ b/include/class.orm.php @@ -139,6 +139,7 @@ class VerySimpleModel { function delete($pk=false) { $table = static::$meta['table']; $sql = 'DELETE FROM '.$table; + $filter = array(); if (!$pk) $pk = static::$meta['pk']; if (!is_array($pk)) $pk=array($pk); @@ -626,7 +627,7 @@ class SqlCompiler { } function compileWhere($where, $model) { - $constrints = array(); + $constraints = array(); foreach ($where as $constraint) { $filter = array(); foreach ($constraint as $field=>$value) { diff --git a/include/class.osticket.php b/include/class.osticket.php index 467831d5b6e4a10720010d3a5f2a223a91257087..10f16bf95c370273799e0c5dc85b063671a07db1 100644 --- a/include/class.osticket.php +++ b/include/class.osticket.php @@ -367,7 +367,7 @@ class osTicket { * Secondly, if the directory of main.inc.php is the same as the * document root, the the ROOT path truly is '/' */ - if(!$_SERVER['DOCUMENT_ROOT'] + if(!isset($_SERVER['DOCUMENT_ROOT']) || !strcasecmp($_SERVER['DOCUMENT_ROOT'], $dir)) return '/'; @@ -411,7 +411,9 @@ class osTicket { /* returns true if script is being executed via commandline */ function is_cli() { return (!strcasecmp(substr(php_sapi_name(), 0, 3), 'cli') - || (!$_SERVER['REQUEST_METHOD'] && !$_SERVER['HTTP_HOST']) //Fallback when php-cgi binary is used via cli + || (!isset($_SERVER['REQUEST_METHOD']) && + !isset($_SERVER['HTTP_HOST'])) + //Fallback when php-cgi binary is used via cli ); } diff --git a/include/class.thread.php b/include/class.thread.php index eca16c58e595f57d597c4535658e36fb13e8e163..ef1d24ed8fdd15dc79d9e0013aadfca541ffc816 100644 --- a/include/class.thread.php +++ b/include/class.thread.php @@ -403,9 +403,7 @@ Class ThreadEntry { return null; $id=0; - if (!$attachment['error'] && ($id=$this->saveAttachment($attachment))) - $files[] = $id; - else { + if ($attachment['error'] || !($id=$this->saveAttachment($attachment))) { $error = $attachment['error']; if(!$error) diff --git a/include/class.variable.php b/include/class.variable.php index 3a71bd66de4239c47439cbd196061d1557ebf0f6..36d49e6a39accdcdee68aaa06cd8059779474e00 100644 --- a/include/class.variable.php +++ b/include/class.variable.php @@ -121,7 +121,9 @@ class VariableReplacer { function _parse($text) { $input = $text; - if(!preg_match_all('/'.$this->start_delim.'([A-Za-z_][\w._]+)'.$this->end_delim.'/', $input, $result)) + $result = array(); + if(!preg_match_all('/'.$this->start_delim.'([A-Za-z_][\w._]+)'.$this->end_delim.'/', + $input, $result)) return null; $vars = array(); diff --git a/include/htmLawed.php b/include/htmLawed.php index 28d234824fb4765e4e60544774c64732ef0d4e67..6d25f1f98a714269c3b65d87ca6f3dcb5db96dcb 100644 --- a/include/htmLawed.php +++ b/include/htmLawed.php @@ -1,9 +1,9 @@ <?php /* -htmLawed 1.1.10, 22 October 2011 +htmLawed 1.1.16, 29 August 2013 Copyright Santosh Patnaik -LGPL v3 license +Dual licensed with LGPL 3 and GPL 2+ A PHP Labware internal utility; www.bioinformatics.org/phplabware/internal_utilities/htmLawed See htmLawed_README.txt/htm @@ -68,7 +68,7 @@ $C['css_expression'] = empty($C['css_expression']) ? 0 : 1; $C['direct_list_nest'] = empty($C['direct_list_nest']) ? 0 : 1; $C['hexdec_entity'] = isset($C['hexdec_entity']) ? $C['hexdec_entity'] : 1; $C['hook'] = (!empty($C['hook']) && function_exists($C['hook'])) ? $C['hook'] : 0; -$C['hook_tag'] = (!empty($C['hook_tag']) && function_exists($C['hook_tag'])) ? $C['hook_tag'] : 0; +$C['hook_tag'] = (!empty($C['hook_tag']) && is_callable($C['hook_tag'])) ? $C['hook_tag'] : 0; $C['keep_bad'] = isset($C['keep_bad']) ? $C['keep_bad'] : 6; $C['lc_std_val'] = isset($C['lc_std_val']) ? (bool)$C['lc_std_val'] : 1; $C['make_tag_strict'] = isset($C['make_tag_strict']) ? $C['make_tag_strict'] : 1; @@ -194,7 +194,10 @@ for($i=-1, $ci=count($t); ++$i<$ci;){ echo '<', $s, $e, $a, '>'; } if(isset($x[0])){ - if($do < 3 or isset($ok['#pcdata'])){echo $x;} + if(strlen(trim($x)) && (($ql && isset($cB[$p])) or (isset($cB[$in]) && !$ql))){ + echo '<div>', $x, '</div>'; + } + elseif($do < 3 or isset($ok['#pcdata'])){echo $x;} elseif(strpos($x, "\x02\x04")){ foreach(preg_split('`(\x01\x02[^\x01\x02]+\x02\x01)`', $x, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY) as $v){ echo (substr($v, 0, 2) == "\x01\x02" ? $v : ($do > 4 ? preg_replace('`\S`', '', $v) : '')); @@ -202,14 +205,14 @@ for($i=-1, $ci=count($t); ++$i<$ci;){ }elseif($do > 4){echo preg_replace('`\S`', '', $x);} } // get markup - if(!preg_match('`^(/?)([a-zA-Z1-6]+)([^>]*)>(.*)`sm', $t[$i], $r)){$x = $t[$i]; continue;} + if(!preg_match('`^(/?)([a-z1-6]+)([^>]*)>(.*)`sm', $t[$i], $r)){$x = $t[$i]; continue;} $s = null; $e = null; $a = null; $x = null; list($all, $s, $e, $a, $x) = $r; // close tag if($s){ if(isset($cE[$e]) or !in_array($e, $q)){continue;} // Empty/unopen if($p == $e){array_pop($q); echo '</', $e, '>'; unset($e); continue;} // Last open $add = ''; // Nesting - close open tags that need to be - for($j=-1, $cj=count($q); ++$j<$cj;){ + for($j=-1, $cj=count($q); ++$j<$cj;){ if(($d = array_pop($q)) == $e){break;} else{$add .= "</{$d}>";} } @@ -333,7 +336,7 @@ $c = isset($C['schemes'][$c]) ? $C['schemes'][$c] : $C['schemes']['*']; static $d = 'denied:'; if(isset($c['!']) && substr($p, 0, 7) != $d){$p = "$d$p";} if(isset($c['*']) or !strcspn($p, '#?;') or (substr($p, 0, 7) == $d)){return "{$b}{$p}{$a}";} // All ok, frag, query, param -if(preg_match('`^([a-z\d\-+.&#; ]+?)(:|&#(58|x3a);|%3a|\\\\0{0,4}3a).`i', $p, $m) && !isset($c[strtolower($m[1])])){ // Denied prot +if(preg_match('`^([^:?[@!$()*,=/\'\]]+?)(:|&#(58|x3a);|%3a|\\\\0{0,4}3a).`i', $p, $m) && !isset($c[strtolower($m[1])])){ // Denied prot return "{$b}{$d}{$p}{$a}"; } if($C['abs_url']){ @@ -376,7 +379,7 @@ return $r; function hl_spec($t){ // final $spec $s = array(); -$t = str_replace(array("\t", "\r", "\n", ' '), '', preg_replace('/"(?>(`.|[^"])*)"/sme', 'substr(str_replace(array(";", "|", "~", " ", ",", "/", "(", ")", \'`"\'), array("\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", "\x08", "\""), "$0"), 1, -1)', trim($t))); +$t = str_replace(array("\t", "\r", "\n", ' '), '', preg_replace('/"(?>(`.|[^"])*)"/sme', 'substr(str_replace(array(";", "|", "~", " ", ",", "/", "(", ")", \'`"\'), array("\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", "\x08", "\""), "$0"), 1, -1)', trim($t))); for($i = count(($t = explode(';', $t))); --$i>=0;){ $w = $t[$i]; if(empty($w) or ($e = strpos($w, '=')) === false or !strlen(($a = substr($w, $e+1)))){continue;} @@ -427,7 +430,7 @@ if($C['make_tag_strict'] && isset($eD[$e])){ // close tag static $eE = array('area'=>1, 'br'=>1, 'col'=>1, 'embed'=>1, 'hr'=>1, 'img'=>1, 'input'=>1, 'isindex'=>1, 'param'=>1); // Empty ele if(!empty($m[1])){ - return (!isset($eE[$e]) ? "</$e>" : (($C['keep_bad'])%2 ? str_replace(array('<', '>'), array('<', '>'), $t) : '')); + return (!isset($eE[$e]) ? (empty($C['hook_tag']) ? "</$e>" : $C['hook_tag']($e)) : (($C['keep_bad'])%2 ? str_replace(array('<', '>'), array('<', '>'), $t) : '')); } // open tag & attr @@ -470,8 +473,8 @@ while(strlen($a)){ $aA[$nm] = ''; } break; case 2: // Val - if(preg_match('`^"[^"]*"`', $a, $m) or preg_match("`^'[^']*'`", $a, $m) or preg_match("`^\s*[^\s\"']+`", $a, $m)){ - $m = $m[0]; $w = 1; $mode = 0; $a = ltrim(substr_replace($a, '', 0, strlen($m))); + if(preg_match('`^((?:"[^"]*")|(?:\'[^\']*\')|(?:\s*[^\s"\']+))(.*)`', $a, $m)){ + $a = ltrim($m[2]); $m = $m[1]; $w = 1; $mode = 0; $aA[$nm] = trim(($m[0] == '"' or $m[0] == '\'') ? substr($m, 1, -1) : $m); } break; @@ -488,7 +491,7 @@ global $S; $rl = isset($S[$e]) ? $S[$e] : array(); $a = array(); $nfr = 0; foreach($aA as $k=>$v){ - if(((isset($C['deny_attribute']['*']) ? isset($C['deny_attribute'][$k]) : !isset($C['deny_attribute'][$k])) or isset($rl[$k])) && ((!isset($rl['n'][$k]) && !isset($rl['n']['*'])) or isset($rl[$k])) && (isset($aN[$k][$e]) or (isset($aNU[$k]) && !isset($aNU[$k][$e])))){ + if(((isset($C['deny_attribute']['*']) ? isset($C['deny_attribute'][$k]) : !isset($C['deny_attribute'][$k])) && (isset($aN[$k][$e]) or (isset($aNU[$k]) && !isset($aNU[$k][$e]))) && !isset($rl['n'][$k]) && !isset($rl['n']['*'])) or isset($rl[$k])){ if(isset($aNE[$k])){$v = $k;} elseif(!empty($lcase) && (($e != 'button' or $e != 'input') or $k == 'type')){ // Rather loose but ?not cause issues $v = (isset($aNL[($v2 = strtolower($v))])) ? $v2 : $v; @@ -622,7 +625,7 @@ if($e == 'u'){$e = 'span'; return 'text-decoration: underline;';} static $fs = array('0'=>'xx-small', '1'=>'xx-small', '2'=>'small', '3'=>'medium', '4'=>'large', '5'=>'x-large', '6'=>'xx-large', '7'=>'300%', '-1'=>'smaller', '-2'=>'60%', '+1'=>'larger', '+2'=>'150%', '+3'=>'200%', '+4'=>'300%'); if($e == 'font'){ $a2 = ''; - if(preg_match('`face\s*=\s*(\'|")([^=]+?)\\1`i', $a, $m) or preg_match('`face\s*=\s*([^"])(\S+)`i', $a, $m)){ + if(preg_match('`face\s*=\s*(\'|")([^=]+?)\\1`i', $a, $m) or preg_match('`face\s*=(\s*)(\S+)`i', $a, $m)){ $a2 .= ' font-family: '. str_replace('"', '\'', trim($m[2])). ';'; } if(preg_match('`color\s*=\s*(\'|")?(.+?)(\\1|\s|$)`i', $a, $m)){ @@ -641,41 +644,50 @@ return ''; function hl_tidy($t, $w, $p){ // Tidy/compact HTM if(strpos(' pre,script,textarea', "$p,")){return $t;} -$t = str_replace(' </', '</', preg_replace(array('`(<\w[^>]*(?<!/)>)\s+`', '`\s+`', '`(<\w[^>]*(?<!/)>) `'), array(' $1', ' ', '$1'), preg_replace_callback(array('`(<(!\[CDATA\[))(.+?)(\]\]>)`sm', '`(<(!--))(.+?)(-->)`sm', '`(<(pre|script|textarea)[^>]*?>)(.+?)(</\2>)`sm'), create_function('$m', 'return $m[1]. str_replace(array("<", ">", "\n", "\r", "\t", " "), array("\x01", "\x02", "\x03", "\x04", "\x05", "\x07"), $m[3]). $m[4];'), $t))); +$t = preg_replace('`\s+`', ' ', preg_replace_callback(array('`(<(!\[CDATA\[))(.+?)(\]\]>)`sm', '`(<(!--))(.+?)(-->)`sm', '`(<(pre|script|textarea)[^>]*?>)(.+?)(</\2>)`sm'), create_function('$m', 'return $m[1]. str_replace(array("<", ">", "\n", "\r", "\t", " "), array("\x01", "\x02", "\x03", "\x04", "\x05", "\x07"), $m[3]). $m[4];'), $t)); if(($w = strtolower($w)) == -1){ return str_replace(array("\x01", "\x02", "\x03", "\x04", "\x05", "\x07"), array('<', '>', "\n", "\r", "\t", ' '), $t); } $s = strpos(" $w", 't') ? "\t" : ' '; $s = preg_match('`\d`', $w, $m) ? str_repeat($s, $m[0]) : str_repeat($s, ($s == "\t" ? 1 : 2)); -$n = preg_match('`[ts]([1-9])`', $w, $m) ? $m[1] : 0; +$N = preg_match('`[ts]([1-9])`', $w, $m) ? $m[1] : 0; $a = array('br'=>1); -$b = array('button'=>1, 'input'=>1, 'option'=>1); +$b = array('button'=>1, 'input'=>1, 'option'=>1, 'param'=>1); $c = array('caption'=>1, 'dd'=>1, 'dt'=>1, 'h1'=>1, 'h2'=>1, 'h3'=>1, 'h4'=>1, 'h5'=>1, 'h6'=>1, 'isindex'=>1, 'label'=>1, 'legend'=>1, 'li'=>1, 'object'=>1, 'p'=>1, 'pre'=>1, 'td'=>1, 'textarea'=>1, 'th'=>1); -$d = array('address'=>1, 'blockquote'=>1, 'center'=>1, 'colgroup'=>1, 'dir'=>1, 'div'=>1, 'dl'=>1, 'fieldset'=>1, 'form'=>1, 'hr'=>1, 'iframe'=>1, 'map'=>1, 'menu'=>1, 'noscript'=>1, 'ol'=>1, 'optgroup'=>1, 'rbc'=>1, 'rtc'=>1, 'ruby'=>1, 'script'=>1, 'select'=>1, 'table'=>1, 'tfoot'=>1, 'thead'=>1, 'tr'=>1, 'ul'=>1); -ob_start(); -if(isset($d[$p])){echo str_repeat($s, ++$n);} -$t = explode('<', $t); -echo ltrim(array_shift($t)); -for($i=-1, $j=count($t); ++$i<$j;){ - $r = ''; list($e, $r) = explode('>', $t[$i]); - $x = $e[0] == '/' ? 0 : (substr($e, -1) == '/' ? 1 : ($e[0] != '!' ? 2 : -1)); - $y = !$x ? ltrim($e, '/') : ($x > 0 ? substr($e, 0, strcspn($e, ' ')) : 0); - $e = "<$e>"; - if(isset($d[$y])){ - if(!$x){echo "\n", str_repeat($s, --$n), "$e\n", str_repeat($s, $n);} - else{echo "\n", str_repeat($s, $n), "$e\n", str_repeat($s, ($x != 1 ? ++$n : $n));} - echo ltrim($r); continue; +$d = array('address'=>1, 'blockquote'=>1, 'center'=>1, 'colgroup'=>1, 'dir'=>1, 'div'=>1, 'dl'=>1, 'fieldset'=>1, 'form'=>1, 'hr'=>1, 'iframe'=>1, 'map'=>1, 'menu'=>1, 'noscript'=>1, 'ol'=>1, 'optgroup'=>1, 'rbc'=>1, 'rtc'=>1, 'ruby'=>1, 'script'=>1, 'select'=>1, 'table'=>1, 'tbody'=>1, 'tfoot'=>1, 'thead'=>1, 'tr'=>1, 'ul'=>1); +$T = explode('<', $t); +$X = 1; +while($X){ + $n = $N; + $t = $T; + ob_start(); + if(isset($d[$p])){echo str_repeat($s, ++$n);} + echo ltrim(array_shift($t)); + for($i=-1, $j=count($t); ++$i<$j;){ + $r = ''; list($e, $r) = explode('>', $t[$i]); + $x = $e[0] == '/' ? 0 : (substr($e, -1) == '/' ? 1 : ($e[0] != '!' ? 2 : -1)); + $y = !$x ? ltrim($e, '/') : ($x > 0 ? substr($e, 0, strcspn($e, ' ')) : 0); + $e = "<$e>"; + if(isset($d[$y])){ + if(!$x){ + if($n){echo "\n", str_repeat($s, --$n), "$e\n", str_repeat($s, $n);} + else{++$N; ob_end_clean(); continue 2;} + } + else{echo "\n", str_repeat($s, $n), "$e\n", str_repeat($s, ($x != 1 ? ++$n : $n));} + echo $r; continue; + } + $f = "\n". str_repeat($s, $n); + if(isset($c[$y])){ + if(!$x){echo $e, $f, $r;} + else{echo $f, $e, $r;} + }elseif(isset($b[$y])){echo $f, $e, $r; + }elseif(isset($a[$y])){echo $e, $f, $r; + }elseif(!$y){echo $f, $e, $f, $r; + }else{echo $e, $r;} } - $f = "\n". str_repeat($s, $n); - if(isset($c[$y])){ - if(!$x){echo $e, $f, ltrim($r);} - else{echo $f, $e, $r;} - }elseif(isset($b[$y])){echo $f, $e, $r; - }elseif(isset($a[$y])){echo $e, $f, ltrim($r); - }elseif(!$y){echo $f, $e, $f, ltrim($r); - }else{echo $e, $r;} -} -$t = preg_replace('`[\n]\s*?[\n]+`', "\n", ob_get_contents()); + $X = 0; +} +$t = str_replace(array("\n ", " \n"), "\n", preg_replace('`[\n]\s*?[\n]+`', "\n", ob_get_contents())); ob_end_clean(); if(($l = strpos(" $w", 'r') ? (strpos(" $w", 'n') ? "\r\n" : "\r") : 0)){ $t = str_replace("\n", $l, $t); @@ -686,7 +698,7 @@ return str_replace(array("\x01", "\x02", "\x03", "\x04", "\x05", "\x07"), array( function hl_version(){ // rel -return '1.1.10'; +return '1.1.16'; // eof } @@ -708,4 +720,4 @@ function kses_hook($t, &$C, &$S){ // kses compat return $t; // eof -} \ No newline at end of file +} diff --git a/include/html2text.php b/include/html2text.php index a4997d1b2b978131b5ad2d155035abf981c40898..f0fdf4fefdda0a1abd87b47cc5fdecbe9d07ff8a 100644 --- a/include/html2text.php +++ b/include/html2text.php @@ -25,8 +25,8 @@ * @return the HTML converted, as best as possible, to text */ function convert_html_to_text($html, $width=74) { - $html = fix_newlines($html); + $html = fix_newlines($html); $doc = new DOMDocument('1.0', 'utf-8'); if (!@$doc->loadHTML($html)) return $html; @@ -151,6 +151,7 @@ class HtmlInlineElement { var $children = array(); var $style = false; var $stylesheets = array(); + var $footnotes = array(); var $ws = false; function __construct($node, $parent) { @@ -200,6 +201,11 @@ class HtmlInlineElement { elseif (is_string($more)) $output .= $more; } + if ($this->footnotes) { + $output .= "\n\n" . str_repeat('-', $width/2) . "\n"; + foreach ($this->footnotes as $name=>$content) + $output .= "[$name] ".$content."\n"; + } return $output; } @@ -253,6 +259,10 @@ class HtmlInlineElement { function addStylesheet(&$s) { $this->stylesheets[] = $s; } + + function addFootNote($name, $content) { + $this->footnotes[$name] = $content; + } } class HtmlBlockElement extends HtmlInlineElement { @@ -397,7 +407,9 @@ class HtmlImgElement extends HtmlInlineElement { $alt = $this->node->getAttribute("alt"); return "[image:$alt$title] "; } - function getWeight() { return parent::getWeight() + 4; } + function getWeight() { + return strlen($this->node->getAttribute("alt")) + 8; + } } class HtmlAElement extends HtmlInlineElement { @@ -410,6 +422,10 @@ class HtmlAElement extends HtmlInlineElement { if ($this->node->getAttribute("name") != null) { $output = "[$output]"; } + } elseif (strlen($href) > $width / 2) { + $output = "[$output][]"; + if ($href != $output) + $this->getRoot()->addFootnote($output, $href); } else { if ($href != $output) { $output = "[$output]($href)"; @@ -627,7 +643,7 @@ class HtmlTable extends HtmlBlockElement { # Stash the computed width so it doesn't need to be # recomputed again below $cell->width = $cwidth; - unset($data); # nolint + unset($data); $data = explode("\n", $cell->render($cwidth, $options)); $heights[$y] = max(count($data), $heights[$y]); $contents[$y][$i] = &$data; @@ -687,7 +703,7 @@ class HtmlTableCell extends HtmlBlockElement { } function getMinWidth() { - return parent::getMinWidth() / $this->cols; + return max(4, parent::getMinWidth() / $this->cols); } } diff --git a/include/mysql.php b/include/mysql.php index 4e3bd7eb8caf242c92b3846fb1b7d5c845f31a87..95be46f5a93b8ee239f13fa441bd94494dad35bc 100644 --- a/include/mysql.php +++ b/include/mysql.php @@ -51,10 +51,11 @@ function db_version() { $version=0; + $matches = array(); if(preg_match('/(\d{1,2}\.\d{1,2}\.\d{1,2})/', mysql_result(db_query('SELECT VERSION()'),0,0), - $matches)) # nolint - $version=$matches[1]; # nolint + $matches)) + $version=$matches[1]; return $version; } @@ -129,6 +130,7 @@ } function db_assoc_array($res, $mode=false) { + $result = array(); if($res && db_num_rows($res)) { while ($row=db_fetch_array($res, $mode)) $result[]=$row; diff --git a/include/mysqli.php b/include/mysqli.php index 06d4032fdc5419f3ffd24bd5a0fe6abe2ea46fb4..7d765b428ac35b350dc9c9f4dd58f3e6b22af5c7 100644 --- a/include/mysqli.php +++ b/include/mysqli.php @@ -30,7 +30,7 @@ function db_connect($host, $user, $passwd, $options = array()) { // Setup SSL if enabled if (isset($options['ssl'])) - $__db->ssl_set( # nolint + $__db->ssl_set( $options['ssl']['key'], $options['ssl']['cert'], $options['ssl']['ca'], @@ -50,16 +50,16 @@ function db_connect($host, $user, $passwd, $options = array()) { // Connect $start = microtime(true); - if (!@$__db->real_connect($host, $user, $passwd, null, $port)) # nolint + if (!@$__db->real_connect($host, $user, $passwd, null, $port)) return NULL; //Select the database, if any. - if(isset($options['db'])) $__db->select_db($options['db']); # nolint + if(isset($options['db'])) $__db->select_db($options['db']); //set desired encoding just in case mysql charset is not UTF-8 - Thanks to FreshMedia - @$__db->query('SET NAMES "utf8"'); # nolint - @$__db->query('SET CHARACTER SET "utf8"'); # nolint - @$__db->query('SET COLLATION_CONNECTION=utf8_general_ci'); # nolint + @$__db->query('SET NAMES "utf8"'); + @$__db->query('SET CHARACTER SET "utf8"'); + @$__db->query('SET COLLATION_CONNECTION=utf8_general_ci'); @db_set_variable('sql_mode', ''); @@ -77,10 +77,11 @@ function db_close() { function db_version() { $version=0; + $matches = array(); if(preg_match('/(\d{1,2}\.\d{1,2}\.\d{1,2})/', db_result(db_query('SELECT VERSION()')), - $matches)) # nolint - $version=$matches[1]; # nolint + $matches)) + $version=$matches[1]; return $version; } @@ -102,13 +103,13 @@ function db_set_variable($variable, $value, $type='session') { function db_select_database($database) { global $__db; - return ($database && @$__db->select_db($database)); # nolint + return ($database && @$__db->select_db($database)); } function db_create_database($database, $charset='utf8', $collate='utf8_general_ci') { global $__db; - return @$__db->query( # nolint + return @$__db->query( sprintf('CREATE DATABASE %s DEFAULT CHARACTER SET %s COLLATE %s', $database, $charset, $collate)); } @@ -147,24 +148,25 @@ function db_result($res, $row=0) { if (!$res) return NULL; - $res->data_seek($row); # nolint + $res->data_seek($row); list($value) = db_output($res->fetch_row()); return $value; } function db_fetch_array($res, $mode=MYSQL_ASSOC) { - return ($res) ? db_output($res->fetch_array($mode)) : NULL; # nolint + return ($res) ? db_output($res->fetch_array($mode)) : NULL; } function db_fetch_row($res) { - return ($res) ? db_output($res->fetch_row()) : NULL; # nolint + return ($res) ? db_output($res->fetch_row()) : NULL; } function db_fetch_field($res) { - return ($res) ? $res->fetch_field() : NULL; # nolint + return ($res) ? $res->fetch_field() : NULL; } function db_assoc_array($res, $mode=false) { + $result = array(); if($res && db_num_rows($res)) { while ($row=db_fetch_array($res, $mode)) $result[]=$row; @@ -173,7 +175,7 @@ function db_assoc_array($res, $mode=false) { } function db_num_rows($res) { - return ($res) ? $res->num_rows : 0; # nolint + return ($res) ? $res->num_rows : 0; } function db_affected_rows() { @@ -182,7 +184,7 @@ function db_affected_rows() { } function db_data_seek($res, $row_number) { - return ($res && $res->data_seek($row_number)); # nolint + return ($res && $res->data_seek($row_number)); } function db_data_reset($res) { @@ -195,7 +197,7 @@ function db_insert_id() { } function db_free_result($res) { - return ($res && $res->free()); # nolint + return ($res && $res->free()); } function db_output($var) { @@ -232,7 +234,7 @@ function db_input($var, $quote=true) { function db_field_type($res, $col=0) { global $__db; - return $res->fetch_field_direct($col); # nolint + return $res->fetch_field_direct($col); } function db_prepare($stmt) { diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php index a3ef79d5d4d29f0752ef720c41cd769ab318d6df..aecf2edd6f2cfc9fa422b1826eaaafeaace1b981 100644 --- a/include/staff/ticket-view.inc.php +++ b/include/staff/ticket-view.inc.php @@ -444,12 +444,12 @@ if(!$cfg->showNotesInline()) { ?> <input type="hidden" name="msgId" value="<?php echo $msgId; ?>"> <input type="hidden" name="a" value="reply"> <span class="error"></span> - <table border="0" cellspacing="0" cellpadding="3"> + <table style="width:100%" border="0" cellspacing="0" cellpadding="3"> <tr> - <td width="160"> + <td width="120"> <label><strong>TO:</strong></label> </td> - <td width="765"> + <td> <?php $to = $ticket->getReplyToEmail(); if(($name=$ticket->getName()) && !strpos($name,'@')) @@ -463,14 +463,14 @@ if(!$cfg->showNotesInline()) { ?> </tr> <?php if($errors['response']) {?> - <tr><td width="160"> </td><td class="error"><?php echo $errors['response']; ?> </td></tr> + <tr><td width="120"> </td><td class="error"><?php echo $errors['response']; ?> </td></tr> <?php }?> <tr> - <td width="160"> + <td width="120" style="vertical-align:top"> <label><strong>Response:</strong></label> </td> - <td width="765"> + <td> <?php if(($cannedResponses=Canned::responsesByDeptId($ticket->getDeptId()))) {?> <select id="cannedResp" name="cannedResp"> @@ -489,6 +489,7 @@ if(!$cfg->showNotesInline()) { ?> <input type="hidden" name="draft_id" value=""/> <textarea name="response" id="response" cols="50" data-draft-namespace="ticket.response" + placeholder="Start writing your response here. Use canned responses from the drop-down above" data-draft-object-id="<?php echo $ticket->getId(); ?>" rows="9" wrap="soft" class="richtext ifhtml draft"><?php @@ -498,10 +499,10 @@ if(!$cfg->showNotesInline()) { ?> <?php if($cfg->allowAttachments()) { ?> <tr> - <td width="160"> + <td width="120"> <label for="attachment">Attachments:</label> </td> - <td width="765" id="reply_form_attachments" class="attachments"> + <td id="reply_form_attachments" class="attachments"> <div class="canned_attachments"> </div> <div class="uploads"> @@ -514,10 +515,10 @@ if(!$cfg->showNotesInline()) { ?> <?php }?> <tr> - <td width="160"> + <td width="120"> <label for="signature" class="left">Signature:</label> </td> - <td width="765"> + <td> <?php $info['signature']=$info['signature']?$info['signature']:$thisstaff->getDefaultSignatureType(); ?> @@ -540,10 +541,10 @@ if(!$cfg->showNotesInline()) { ?> <?php if($ticket->isClosed() || $thisstaff->canCloseTickets()) { ?> <tr> - <td width="160"> + <td width="120"> <label><strong>Ticket Status:</strong></label> </td> - <td width="765"> + <td> <?php $statusChecked=isset($info['reply_ticket_status'])?'checked="checked"':''; if($ticket->isClosed()) { ?> @@ -573,42 +574,45 @@ if(!$cfg->showNotesInline()) { ?> <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"> + <table width="100%" border="0" cellspacing="0" cellpadding="3"> <?php if($errors['postnote']) {?> <tr> - <td width="160"> </td> + <td width="120"> </td> <td class="error"><?php echo $errors['postnote']; ?></td> </tr> <?php } ?> <tr> - <td width="160"> - <label><strong>Internal Note:</strong></label> + <td width="120" style="vertical-align:top"> + <label><strong>Internal Note:</strong><span class='error'> *</span></label> </td> - <td width="765"> - <div><span class="faded">Note details</span> - <span class="error">* <?php echo $errors['note']; ?></span> + <td> + <div> + <div class="faded" style="padding-left:0.15em"> + Note title - summary of the note (optional)</div> + <input type="text" name="title" id="title" size="60" value="<?php echo $info['title']; ?>" > + <br/> + <span class="error" <?php echo $errors['title']; ?></span> </div> + <br/> <textarea name="note" id="internal_note" cols="80" + placeholder="Note details" rows="9" wrap="soft" data-draft-namespace="ticket.note" data-draft-object-id="<?php echo $ticket->getId(); ?>" class="richtext ifhtml draft"><?php echo $info['note']; - ?></textarea><br> - <div> - <span class="faded">Note title - summary 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']; ?>" > + ?></textarea> + <span class="error"><?php echo $errors['note']; ?></span> + <br> </td> </tr> <?php if($cfg->allowAttachments()) { ?> <tr> - <td width="160"> + <td width="120"> <label for="attachment">Attachments:</label> </td> - <td width="765" class="attachments"> + <td class="attachments"> <div class="uploads"> </div> <div class="file_input"> @@ -621,10 +625,10 @@ if(!$cfg->showNotesInline()) { ?> ?> <tr><td colspan="2"> </td></tr> <tr> - <td width="160"> + <td width="120"> <label>Ticket Status:</label> </td> - <td width="765"> + <td> <div class="faded"></div> <select name="state"> <option value="" selected="selected">— unchanged —</option> @@ -683,21 +687,21 @@ if(!$cfg->showNotesInline()) { ?> <?php csrf_token(); ?> <input type="hidden" name="ticket_id" value="<?php echo $ticket->getId(); ?>"> <input type="hidden" name="a" value="transfer"> - <table border="0" cellspacing="0" cellpadding="3"> + <table width="100%" border="0" cellspacing="0" cellpadding="3"> <?php if($errors['transfer']) { ?> <tr> - <td width="160"> </td> + <td width="120"> </td> <td class="error"><?php echo $errors['transfer']; ?></td> </tr> <?php } ?> <tr> - <td width="160"> + <td width="120"> <label for="deptId"><strong>Department:</strong></label> </td> - <td width="765"> + <td> <?php echo sprintf('<span class="faded">Ticket is currently in <b>%s</b> department.</span>', $ticket->getDeptName()); ?> @@ -717,15 +721,15 @@ if(!$cfg->showNotesInline()) { ?> </td> </tr> <tr> - <td width="160"> - <label><strong>Comments:</strong></label> + <td width="120" style="vertical-align:top"> + <label><strong>Comments:</strong><span class='error'> *</span></label> </td> - <td width="765"> - <span class="faded">Enter reasons for the transfer.</span> - <span class="error">* <?php echo $errors['transfer_comments']; ?></span><br> + <td> <textarea name="transfer_comments" id="transfer_comments" + placeholder="Enter reasons for the transfer" class="richtext ifhtml no-bar" cols="80" rows="7" wrap="soft"><?php echo $info['transfer_comments']; ?></textarea> + <span class="error"><?php echo $errors['transfer_comments']; ?></span> </td> </tr> </table> @@ -742,31 +746,22 @@ if(!$cfg->showNotesInline()) { ?> <?php csrf_token(); ?> <input type="hidden" name="id" value="<?php echo $ticket->getId(); ?>"> <input type="hidden" name="a" value="assign"> - <table border="0" cellspacing="0" cellpadding="3"> + <table style="width:100%" border="0" cellspacing="0" cellpadding="3"> <?php if($errors['assign']) { ?> <tr> - <td width="160"> </td> + <td width="120"> </td> <td class="error"><?php echo $errors['assign']; ?></td> </tr> <?php } ?> <tr> - <td width="160"> + <td width="120" style="vertical-align:top"> <label for="assignId"><strong>Assignee:</strong></label> </td> - <td width="765"> - <?php - if($ticket->isAssigned() && $ticket->isOpen()) { - echo sprintf('<span class="faded">Ticket is currently assigned to <b>%s</b></span>', - $ticket->getAssignee()); - } else { - echo '<span class="faded">Assigning a closed ticket will <b>reopen</b> it!</span>'; - } - ?> - <br> + <td> <select id="assignId" name="assignId"> <option value="0" selected="selected">— Select Staff Member OR a Team —</option> <?php @@ -803,17 +798,25 @@ if(!$cfg->showNotesInline()) { ?> } ?> </select> <span class='error'>* <?php echo $errors['assignId']; ?></span> + <?php + if($ticket->isAssigned() && $ticket->isOpen()) { + echo sprintf('<div class="faded">Ticket is currently assigned to <b>%s</b></div>', + $ticket->getAssignee()); + } elseif ($ticket->isClosed()) { ?> + <div class="faded">Assigning a closed ticket will <b>reopen</b> it!</div> + <?php } ?> </td> </tr> <tr> - <td width="160"> - <label><strong>Comments:</strong><span class='error'> </span></label> + <td width="120" style="vertical-align:top"> + <label><strong>Comments:</strong><span class='error'> *</span></label> </td> - <td width="765"> - <span class="faded">Enter reasons for the assignment or instructions for assignee.</span> - <span class="error">* <?php echo $errors['assign_comments']; ?></span><br> - <textarea name="assign_comments" id="assign_comments" cols="80" rows="7" wrap="soft" + <td> + <textarea name="assign_comments" id="assign_comments" + cols="80" rows="7" wrap="soft" + placeholder="Enter reasons for the assignment or instructions for assignee" class="richtext ifhtml no-bar"><?php echo $info['assign_comments']; ?></textarea> + <span class="error"><?php echo $errors['assign_comments']; ?></span><br> </td> </tr> </table> diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php index 6c0eb1b26bceb46945710ad2768d7a9c69450959..0e0a7ec0df709188d85c743d9629f52f59a86eb7 100644 --- a/include/staff/tickets.inc.php +++ b/include/staff/tickets.inc.php @@ -76,6 +76,11 @@ if($status) { $qwhere.=' AND status='.db_input(strtolower($status)); } +if (isset($_REQUEST['ownerId'])) { + $qwhere .= ' AND ticket.user_id='.db_input($_REQUEST['ownerId']); + $qstr .= '&ownerId='.urlencode($_REQUEST['ownerId']); +} + //Queues: Overloaded sub-statuses - you've got to just have faith! if($staffId && ($staffId==$thisstaff->getId())) { //My tickets $results_type='Assigned Tickets'; diff --git a/include/upgrader/streams/core/d51f303a-dad45ca2.patch.sql b/include/upgrader/streams/core/d51f303a-dad45ca2.patch.sql index 94b8f8aa7fcd59f202bcc281324c58894f160a72..19ddac83bf11b1d262c167e6f73b190db63babb7 100644 --- a/include/upgrader/streams/core/d51f303a-dad45ca2.patch.sql +++ b/include/upgrader/streams/core/d51f303a-dad45ca2.patch.sql @@ -47,89 +47,92 @@ CREATE TABLE `%TABLE_PREFIX%draft` ( UPDATE `%TABLE_PREFIX%email_template` SET `body` = REPLACE( REPLACE( REPLACE( REPLACE( `body`, + '&', '&'), '<', '<'), '>', '>'), - '\n', '<br/>'), - '&', '&'); + '\n', '<br/>'); + +UPDATE `%TABLE_PREFIX%email_template` + SET `body` = CONCAT('<div>', `body`, '</div>'); -- Migrate notes to HTML UPDATE `%TABLE_PREFIX%api_key` SET `notes` = REPLACE( REPLACE( REPLACE( REPLACE( `notes`, + '&', '&'), '<', '<'), '>', '>'), - '\n', '<br/>'), - '&', '&'); + '\n', '<br/>'); UPDATE `%TABLE_PREFIX%email` SET `notes` = REPLACE( REPLACE( REPLACE( REPLACE( `notes`, + '&', '&'), '<', '<'), '>', '>'), - '\n', '<br/>'), - '&', '&'); + '\n', '<br/>'); UPDATE `%TABLE_PREFIX%email_template_group` SET `notes` = REPLACE( REPLACE( REPLACE( REPLACE( `notes`, + '&', '&'), '<', '<'), '>', '>'), - '\n', '<br/>'), - '&', '&'); + '\n', '<br/>'); UPDATE `%TABLE_PREFIX%faq` SET `notes` = REPLACE( REPLACE( REPLACE( REPLACE( `notes`, + '&', '&'), '<', '<'), '>', '>'), - '\n', '<br/>'), - '&', '&'); + '\n', '<br/>'); UPDATE `%TABLE_PREFIX%faq_category` SET `notes` = REPLACE( REPLACE( REPLACE( REPLACE( `notes`, + '&', '&'), '<', '<'), '>', '>'), - '\n', '<br/>'), - '&', '&'); + '\n', '<br/>'); UPDATE `%TABLE_PREFIX%filter` SET `notes` = REPLACE( REPLACE( REPLACE( REPLACE( `notes`, + '&', '&'), '<', '<'), '>', '>'), - '\n', '<br/>'), - '&', '&'); + '\n', '<br/>'); UPDATE `%TABLE_PREFIX%groups` SET `notes` = REPLACE( REPLACE( REPLACE( REPLACE( `notes`, + '&', '&'), '<', '<'), '>', '>'), - '\n', '<br/>'), - '&', '&'); + '\n', '<br/>'); UPDATE `%TABLE_PREFIX%help_topic` SET `notes` = REPLACE( REPLACE( REPLACE( REPLACE( `notes`, + '&', '&'), '<', '<'), '>', '>'), - '\n', '<br/>'), - '&', '&'); + '\n', '<br/>'); UPDATE `%TABLE_PREFIX%page` SET `notes` = REPLACE( REPLACE( REPLACE( REPLACE( `notes`, + '&', '&'), '<', '<'), '>', '>'), - '\n', '<br/>'), - '&', '&'); + '\n', '<br/>'); UPDATE `%TABLE_PREFIX%sla` SET `notes` = REPLACE( REPLACE( REPLACE( REPLACE( `notes`, + '&', '&'), '<', '<'), '>', '>'), - '\n', '<br/>'), - '&', '&'); + '\n', '<br/>'); UPDATE `%TABLE_PREFIX%staff` SET `notes` = REPLACE( REPLACE( REPLACE( REPLACE( `notes`, + '&', '&'), '<', '<'), '>', '>'), - '\n', '<br/>'), - '&', '&'); + '\n', '<br/>'); UPDATE `%TABLE_PREFIX%team` SET `notes` = REPLACE( REPLACE( REPLACE( REPLACE( `notes`, @@ -142,16 +145,16 @@ UPDATE `%TABLE_PREFIX%team` UPDATE `%TABLE_PREFIX%canned_response` SET `notes` = REPLACE( REPLACE( REPLACE( REPLACE( `notes`, + '&', '&'), '<', '<'), '>', '>'), '\n', '<br/>'), - '&', '&'), `response` = REPLACE( REPLACE( REPLACE( REPLACE( `response`, + '&', '&'), '<', '<'), '>', '>'), - '\n', '<br/>'), - '&', '&'); + '\n', '<br/>'); -- Migrate ticket-thread to HTML -- XXX: Migrate & -> & ? -- the problem is that there's a fix in 1.7.1 diff --git a/js/redactor-osticket.js b/js/redactor-osticket.js index 0fd53146f7e986b754f48e565476dffcf0dbbe18..486cadd7281d2467a437dbb3af9b6cde0ab3b1a1 100644 --- a/js/redactor-osticket.js +++ b/js/redactor-osticket.js @@ -134,7 +134,9 @@ $(function() { 'focus': false, 'plugins': ['fontcolor','fontfamily'], 'imageGetJson': 'ajax.php/draft/images/browse', - 'syncBeforeCallback': captureImageSizes + 'syncBeforeCallback': captureImageSizes, + 'linebreaks': true, + 'tabFocus': false }; if (el.data('redactor')) return; if (el.hasClass('draft')) { diff --git a/main.inc.php b/main.inc.php index 56da2cb710261bb7fa21c04c700aa9823981f669..e62d6e5e43bec6fed2bdee435005c9eae94c6d44 100644 --- a/main.inc.php +++ b/main.inc.php @@ -16,7 +16,9 @@ vim: expandtab sw=4 ts=4 sts=4: **********************************************************************/ #Disable direct access. -if(!strcasecmp(basename($_SERVER['SCRIPT_NAME']),basename(__FILE__))) die('kwaheri rafiki!'); +if(isset($_SERVER['SCRIPT_NAME']) + && !strcasecmp(basename($_SERVER['SCRIPT_NAME']),basename(__FILE__))) + die('kwaheri rafiki!'); require('bootstrap.php'); Bootstrap::loadConfig(); diff --git a/scp/js/ticket.js b/scp/js/ticket.js index cbe042f4435d8bb46af55f4506947a9d04fab0ef..fb9f7370ed84ce178bdc0f233ef2ab16c066d7a4 100644 --- a/scp/js/ticket.js +++ b/scp/js/ticket.js @@ -428,10 +428,18 @@ showImagesInline = function(urls, thread_id) { e = $(el); if (info) { // Add a hover effect with the filename - var caption = $('<div class="image-hover">') + var timeout, caption = $('<div class="image-hover">') .hover( - function() { $(this).find('.caption').slideDown(250); }, - function() { $(this).find('.caption').slideUp(250); } + function() { + var self = this; + timeout = setTimeout( + function() { $(self).find('.caption').slideDown(250); }, + 500); + }, + function() { + clearTimeout(timeout); + $(this).find('.caption').slideUp(250); + } ).append($('<div class="caption">') .append('<span class="filename">'+info.filename+'</span>') .append('<a href="'+info.download_url+'" class="action-button"><i class="icon-download-alt"></i> Download</a>') diff --git a/scp/tickets.php b/scp/tickets.php index 455cbb41ae71df41ce49a9aa1f61ea913915b5b9..1058b80d95f67fed03e39c191292f76e5c8b2774 100644 --- a/scp/tickets.php +++ b/scp/tickets.php @@ -167,23 +167,25 @@ if($_POST && !$errors): $errors['state'] = "You don't have permission to set the state"; } - $wasOpen = ($ticket->isOpen()); - $vars = $_POST; if($_FILES['attachments']) $vars['files'] = AttachmentFile::format($_FILES['attachments']); + $wasOpen = ($ticket->isOpen()); if(($note=$ticket->postNote($vars, $errors, $thisstaff))) { + // Cleanup drafts for the ticket. If not closed, only clean + // note drafts for this staff. Else clean all drafts for the ticket. + Draft::deleteForNamespace( + sprintf('ticket.%s.%d', + $ticket->isClosed() ? '%' : 'note', + $ticket->getId()), + $ticket->isOpen() ? $thisstaff->getId() : false); + $msg='Internal note posted successfully'; if($wasOpen && $ticket->isClosed()) $ticket = null; //Going back to main listing. - // Cleanup drafts for the ticket. If not closed, only clean - // for this staff. Else clean all drafts for the ticket. - Draft::deleteForNamespace('ticket.%.' . $ticket->getId(), - $ticket->isClosed() ? false : $thisstaff->getId()); - } else { if(!$errors['err']) diff --git a/setup/cli/modules/class.module.php b/setup/cli/modules/class.module.php index 788cb21fbde330821ab9feb9855475ab9b156a57..437f87c609eb6b63f6799978ce324f437a456ca5 100644 --- a/setup/cli/modules/class.module.php +++ b/setup/cli/modules/class.module.php @@ -72,10 +72,10 @@ class Option { $short = explode(':', $this->short); $long = explode(':', $this->long); if ($this->nargs === '?') - $switches = sprintf(' %s [%3$s], %s[=%3$s]', $short[0], # nolint + $switches = sprintf(' %s [%3$s], %s[=%3$s]', $short[0], $long[0], $this->metavar); elseif ($this->hasArg()) - $switches = sprintf(' %s %3$s, %s=%3$s', $short[0], $long[0], # nolint + $switches = sprintf(' %s %3$s, %s=%3$s', $short[0], $long[0], $this->metavar); else $switches = sprintf(" %s, %s", $short[0], $long[0]); @@ -143,7 +143,7 @@ class Module { echo "Usage:\n"; echo " " . str_replace( - array('$script', '$args'), # nolint + array('$script', '$args'), array($manager ." ". $this->module_name, implode(' ', array_keys($this->arguments))), $this->usage) . "\n"; diff --git a/setup/cli/modules/deploy.php b/setup/cli/modules/deploy.php index 77798797b9e5aebd2fd3810af570cf0263170df6..8ba0c0fafbf9740957072d12ea803efb9e2c6a16 100644 --- a/setup/cli/modules/deploy.php +++ b/setup/cli/modules/deploy.php @@ -50,8 +50,7 @@ class Deployment extends Unpacker { $include = ($upgrade) ? $this->get_include_dir() : ($options['include'] ? $options['include'] : "{$this->destination}/include"); - if (substr($include, -1) !== '/') - $include .= '/'; + $include = rtrim($include, '/').'/'; # Locate the upload folder $root = $this->find_root_folder(); @@ -67,8 +66,8 @@ class Deployment extends Unpacker { # Unpack the include folder $this->unpackage("$root/include/{,.}*", $include, -1, array("*/include/ost-config.php")); - if (!$options['dry-run'] && !$upgrade - && $include != "{$this->destination}/include") + if (!$options['dry-run'] + && $include != "{$this->destination}/include") $this->change_include_dir($include); } } diff --git a/setup/cli/modules/import.php b/setup/cli/modules/import.php index e0f23d57a1a11e8b8b8c24df592edc4b36cd992e..84c27396d4b790c4a5612c2146490e5da42d9090 100644 --- a/setup/cli/modules/import.php +++ b/setup/cli/modules/import.php @@ -113,6 +113,7 @@ class Importer extends Module { $sql = 'CREATE TABLE `'.TABLE_PREFIX.$info[1].'` ('; $pk = array(); $fields = array(); + $queries = array(); foreach ($info[2] as $col) { $field = "`{$col['Field']}` {$col['Type']}"; if ($col['Null'] == 'NO') diff --git a/setup/cli/modules/unpack.php b/setup/cli/modules/unpack.php index 50156ea7e77d9ecc1627de92887ba4f6f25e5c79..55fe6ab5a35299bd4e1c3017a40b7aab8342823f 100644 --- a/setup/cli/modules/unpack.php +++ b/setup/cli/modules/unpack.php @@ -46,8 +46,9 @@ class Unpacker extends Module { function change_include_dir($include_path) { # Read the main.inc.php script - $main_inc_php = $this->destination . '/main.inc.php'; - $lines = explode("\n", file_get_contents($main_inc_php)); + $bootstrap_php = $this->destination . '/bootstrap.php'; + $lines = explode("\n", file_get_contents($bootstrap_php)); + $include_path = preg_replace('://+:', '/', $include_path); # Try and use ROOT_DIR if (strpos($include_path, $this->destination) === 0) $include_path = "ROOT_DIR . '" . @@ -57,6 +58,7 @@ class Unpacker extends Module { # Find the line that defines INCLUDE_DIR $match = array(); foreach ($lines as &$line) { + // TODO: Change THIS_VERSION inline to be current `git describe` if (preg_match("/(\s*)define\s*\(\s*'INCLUDE_DIR'/", $line, $match)) { # Replace the definition with the new locatin $line = $match[1] . "define('INCLUDE_DIR', " @@ -65,7 +67,7 @@ class Unpacker extends Module { break; } } - if (!file_put_contents($main_inc_php, implode("\n", $lines))) + if (!file_put_contents($bootstrap_php, implode("\n", $lines))) die("Unable to configure location of INCLUDE_DIR in main.inc.php\n"); } @@ -134,17 +136,18 @@ class Unpacker extends Module { } function get_include_dir() { - $main_inc_php = $this->destination . '/main.inc.php'; + $bootstrap_php = $this->destination . '/bootstrap.php'; $lines = preg_grep("/define\s*\(\s*'INCLUDE_DIR'/", - explode("\n", file_get_contents($main_inc_php))); + explode("\n", file_get_contents($bootstrap_php))); // NOTE: that this won't work for crafty folks who have a define or some // variable in the value of their include path - if (!defined('ROOT_DIR')) define('ROOT_DIR', $this->destination . '/'); + if (!defined('ROOT_DIR')) + define('ROOT_DIR', rtrim($this->destination, '/').'/'); foreach ($lines as $line) eval($line); - return INCLUDE_DIR; + return rtrim(INCLUDE_DIR, '/').'/'; } function run($args, $options) { diff --git a/setup/test/tests/class.php_analyze.php b/setup/test/tests/class.php_analyze.php new file mode 100644 index 0000000000000000000000000000000000000000..9690c980afe51162da54164cc6c1d6b1b7fd08d2 --- /dev/null +++ b/setup/test/tests/class.php_analyze.php @@ -0,0 +1,300 @@ +<?php +require_once('class.test.php'); + +class SourceAnalyzer extends Test { + static $super_globals = array( + '$_SERVER'=>1, '$_FILES'=>1, '$_SESSION'=>1, '$_GET'=>1, + '$_POST'=>1, '$_REQUEST'=>1, '$_ENV'=>1, '$_COOKIE'=>1); + + var $bugs = array(); + var $globals = array(); + var $file = ''; + + function __construct($source) { + $this->tokens = token_get_all(file_get_contents($source)); + $this->file = $source; + } + + function parseFile() { + $this->checkVariableUsage( + array('line'=>array(0, $this->file), 'name'=>'(main)'), + array(), + 1); + } + + function traverseClass($line) { + $class = array('line'=>$line); + $token = false; + $blocks = 0; + while (list($i,$token) = each($this->tokens)) { + switch ($token[0]) { + case '{': + $blocks++; + break; + case '}': + if (--$blocks == 0) + return; + break; + case T_STRING: + if (!isset($class['name'])) + $class['name'] = $token[1]; + break; + case T_FUNCTION: + $this->traverseFunction( + array($token[2], $line[1]), + array('allow_this'=>true)); + break; + case T_VAR: + // var $variable + // used inside classes to define instance variables + while (list(,$token) = each($this->tokens)) { + if (is_array($token) && $token[0] == T_VARIABLE) + // TODO: Add this to some class context in the + // future to support indefined access to $this->blah + break; + } + break; + } + } + } + + function traverseFunction($line=0, $options=array()) { + // Scan for function name + $function = array('line'=>$line, 'name'=>'(inline)'); + $token = false; + $scope = array(); + while ($token != "{") { + list(,$token) = each($this->tokens); + if (!is_array($token) + || $token[0] == T_WHITESPACE) + continue; + if ($token[0] == T_STRING) + $function['name'] = $token[1]; + elseif ($token[0] == T_VARIABLE) + $scope[$token[1]] = 1; + } + // Start inside a block -- we've already consumed the { + $this->checkVariableUsage($function, $scope, 1, $options); + } + + function checkVariableUsage($function, $scope=array(), $blocks=0, + $options=array()) { + // Merge in defaults to the options array + $options = array_merge(array( + 'allow_this' => false, + ), $options); + // Unpack function[line][file] if set + if (is_array($function['line'])) + $function['file'] = $function['line'][1]; + while (list($i,$token) = each($this->tokens)) { + // Check variable usage and for nested blocks + switch ($token[0]) { + case '{': + $blocks++; + break; + case '}': + if (--$blocks == 0) + return; + break; + case T_VARIABLE: + // Look-ahead for assignment + $assignment = false; + while ($next = @$this->tokens[++$i]) + if (!is_array($next) || $next[0] != T_WHITESPACE) + break; + switch ($next[0]) { + case '=': + // For assignment, check if the variable is explictly + // assigned to NULL. If so, treat the assignment as an + // unset() + while ($next = @$this->tokens[++$i]) + if (!is_array($next) || $next[0] != T_WHITESPACE) + break; + if (is_array($next) && strcasecmp('NULL', $next[1]) === 0) { + $scope[$token[1]] = 'null'; + $assignment = true; + break; + } + case T_AND_EQUAL: + case T_CONCAT_EQUAL: + case T_DIV_EQUAL: + case T_MINUS_EQUAL: + case T_MOD_EQUAL: + case T_MUL_EQUAL: + case T_OR_EQUAL: + case T_PLUS_EQUAL: + case T_SL_EQUAL: + case T_SR_EQUAL: + case T_XOR_EQUAL: + $assignment = true; + $scope[$token[1]] = 1; + break; + } + if ($assignment) + break; + + if (!isset($scope[$token[1]])) { + if ($token[1] == '$this' && $options['allow_this']) { + // Always valid in a non-static class method + // TODO: Determine if this function is defined in a class + break; + } + elseif (isset(static::$super_globals[$token[1]])) + // Super globals are always in scope + break; + elseif (!isset($function['name']) || $function['name'] == '(main)') + // Not in a function. Cowardly continue. + // TODO: Recurse into require() statements to + // determine if global variables are actually + // defined somewhere in the code base + break; + $this->bugs[] = array( + 'type' => 'UNDEF_ACCESS', + 'func' => $function['name'], + 'line' => array($token[2], $function['file']), + 'name' => $token[1], + ); + } + elseif ($scope[$token[1]] == 'null') { + // See if the next token is accessing a property of the + // object + $c = current($this->tokens); + switch ($c[0]) { + case T_OBJECT_OPERATOR: + case T_PAAMAYIM_NEKUDOTAYIM: + case '[': + $this->bugs[] = array( + 'type' => 'MAYBE_UNDEF_ACCESS', + 'func' => $function['name'], + 'line' => array($token[2], $function['file']), + 'name' => $token[1], + ); + } + } + break; + case T_PAAMAYIM_NEKUDOTAYIM: + // Handle static variables $instance::$static + $current = current($this->tokens); + if ($current[0] == T_VARIABLE) + next($this->tokens); + break; + case T_CLASS: + // XXX: PHP does not allow nested classes + $this->traverseClass( + array($token[2], $function['file'])); + break; + case T_FUNCTION: + // PHP does not automatically nest scopes. Variables + // available inside the closure must be explictly defined. + // Therefore, there is no need to pass the current scope. + // However, $this is not allowed inside inline functions + // unless declared in the use () parameters. + $this->traverseFunction( + array($token[2], $function['file']), + array('allow_this'=>false)); + break; + case T_STATIC: + $c = current($this->tokens); + // (nolint) static::func() or static::$var + if ($c[0] == T_PAAMAYIM_NEKUDOTAYIM) + break; + case T_GLOBAL: + while (list(,$token) = each($this->tokens)) { + if ($token == ';') + break; + elseif (!is_array($token)) + continue; + elseif ($token[0] == T_VARIABLE) + $scope[$token[1]] = 1; + } + break; + case T_FOR: + // for ($i=0;...) + // Find first semi-colon, variables defined before it should + // be added to the current scope + while (list(,$token) = each($this->tokens)) { + if ($token == ';') + break; + elseif (!is_array($token)) + continue; + elseif ($token[0] == T_VARIABLE) + $scope[$token[1]] = 1; + } + break; + case T_FOREACH: + // foreach ($blah as $a=>$b) -- add $a, $b to the local + // scope + $after_as = false; + $parens = 0; + // Scan for the variables defined for the scope of the + // foreach block + while (list(,$token) = each($this->tokens)) { + if ($token == '(') + $parens++; + elseif ($token == ')' && --$parens == 0) + break; + elseif (!is_array($token)) + continue; + elseif ($token[0] == T_AS) + $after_as = true; + elseif ($after_as && $token[0] == T_VARIABLE) + // Technically, variables defined in a foreach() + // block are still accessible after the completion + // of the foreach block + $scope[$token[1]] = 1; + } + break; + case T_LIST: + // list($a, $b) = ... + // Find all variables defined up to the closing parenthesis + while (list(,$token) = each($this->tokens)) { + if ($token == ')') + break; + elseif (!is_array($token)) + continue; + elseif ($token[0] == T_VARIABLE) + $scope[$token[1]] = 1; + } + break; + case T_ISSET: + // isset($var) + // $var is allowed to be undefined and not be an error. + // Consume tokens until close parentheses + while (list(,$token) = each($this->tokens)) { + if ($token == ')') + break; + } + break; + case T_UNSET: + // unset($var) + // Var will no longer be in scope + while (list(,$token) = each($this->tokens)) { + if ($token == ')') + break; + elseif (is_array($token) && $token[0] == T_VARIABLE) { + // Check for unset($var[key]) -- don't unset anything + // Check for unset($this->blah) + $next = current($this->tokens); + switch ($next[0]) { + case '[': + case T_OBJECT_OPERATOR: + break; + default: + unset($scope[$token[1]]); + } + break; + } + } + break; + case T_DOLLAR_OPEN_CURLY_BRACES: + case T_CURLY_OPEN: + // "{$a .. }" + // This screws up block detection. We will see another close + // brace somewhere along the way + $blocks++; + break; + } + } + } +} +?> diff --git a/setup/test/tests/class.test.php b/setup/test/tests/class.test.php index 5ce1297619714533c19bdfd7f2c083b4b9d255b8..44842b3fef80d125623ae3ff4f6df8eeadcce9ec 100644 --- a/setup/test/tests/class.test.php +++ b/setup/test/tests/class.test.php @@ -2,6 +2,7 @@ class Test { var $fails = array(); + var $warnings = array(); var $name = ""; var $third_party_paths = array( @@ -11,6 +12,9 @@ class Test { '/include/pear/', '/include/Spyc.php', '/setup/cli/stage/', + '/include/plugins/', + '/include/h2o/', + '/include/fpdf/', ); function Test() { @@ -57,7 +61,7 @@ class Test { } function warn($message) { - $this->fails[] = array(get_class($this), '', '', 'WARNING: '.$message); + $this->warnings[] = array(get_class($this), '', '', 'WARNING: '.$message); fputs(STDOUT, 'w'); } diff --git a/setup/test/tests/lib/phplint.tcl b/setup/test/tests/lib/phplint.tcl deleted file mode 100755 index 30e10074baba6f31705c1c0b152995b33f564f66..0000000000000000000000000000000000000000 --- a/setup/test/tests/lib/phplint.tcl +++ /dev/null @@ -1,217 +0,0 @@ -#!/usr/bin/tclsh - -# Copyright (C) 2007 Salvatore Sanfilippo <antirez at gmail dot com> -# This software is released under the GPL license version 2 - -proc scan file { - set fd [open $file] - set infunc 0 - set linenr 0 - set fnre {(^\s*)((public|private|protected|static)\s*)*function(?=\s|\()\s*([^(]+)?\s*\(((\(\)|[^)])*)\)(\s*use\s*\((.*)\))?} - while {[gets $fd line] != -1} { - incr linenr - if {[regexp $fnre $line - ind - - - fa - - ua]} { - # If $infunc is true we miss the end of the last function - # so we analyze it now. - if {$infunc} { - analyze $file $arglist $body - } - set body {} - set arglist {} - foreach arg [split $fa ,] { - # remove default value - regsub {=.*} $arg {} arg - # remove optional type spec - regsub {^.*\s+} [string trim $arg] {} arg - set arg [string trim $arg " $&"] - lappend arglist $arg - } - # Support closure variables - foreach arg [split $ua ,] { - set arg [string trim $arg " $"] - lappend arglist $arg - } - set infunc 1 - } elseif {$infunc && [regexp "^$ind\}" $line]} { - set infunc 0 - analyze $file $arglist $body - } elseif {$infunc} { - lappend body $linenr [string trim $line] - } - } -} - -proc analyze {file arglist body} { - set initialized(this) 1 - set linton 1 - foreach arg $arglist { - set initialized($arg) 1 - } - # Superglobals - set superglobals { - "GLOBALS" - "_SESSION" - "_SESSION" - "_GET" - "_POST" - "_REQUEST" - "_ENV" - "_SERVER" - "_FILES" - "php_errormsg" - } - foreach sg $superglobals { - set initialized($sg) 1 - } - # analyze body - foreach {linenr line} $body { - # Handle annotations - if {[string first {nolint} [string tolower $line]] != -1} continue - if {[string first {linton} [string tolower $line]] != -1} { - if {$linton == 1} { - puts "! Warning 'linton' annotation with lint already ON" - continue - } - set linton 1 - puts ". $skipped lines skipped in $file from line $skipstart" - } - if {[string first {lintoff} [string tolower $line]] != -1} { - if {$linton == 0} { - puts "! Warning 'lintoff' annotation with lint already OFF" - continue - } - set linton 0 - set skipped 0 - set skipstart [expr {$linenr+1}] - continue - } - if {$linton == 0} { - incr skipped - continue - } - # Skip comments - if {[string index $line 0] eq {#}} continue - if {[string index $line 0] eq {/} && [string index $line 1] eq {/}} continue - # PHP variable regexp - set varre {\$[_A-Za-z]+[_A-Za-z0-9]*(\[[^\]]*\])*} - # Check for globals - set re {\s*(global|static)\s+((?:\$[^;,]+[ ,]*)+)(;|$)} - if {[regexp $re $line -> - g]} { - set g [split [string trim $g ";"] ,] - foreach v $g { - set v [string trim $v "$ "] - set initialized($v) 1 - } - } - # Check for assignment via foreach ... as &$varname - set re {} - append re {foreach\s*\(.*\s+as\s+&?(} $varre {)\s*\)} - set l [regexp -all -inline -nocase $re $line] - foreach {- a -} $l { - set initialized([string trim $a "$ "]) 1 - } - # Check for assignment via foreach ... as $key => &$val - set re {} - append re {foreach\s*\(.*\s+as\s+(} $varre {)\s*=>\s*&?(} $varre {)\s*\)} - set l [regexp -all -inline -nocase $re $line] - foreach {- a1 - a2 -} $l { - set initialized([string trim $a1 "$ "]) 1 - set initialized([string trim $a2 "$ "]) 1 - } - # Check for assigments in the form list($a,$b,$c) = ... - set re {list\s*\(([^=]*)\)\s*=} - set l [regexp -all -inline $re $line] - foreach {- vars} $l { - foreach v [split $vars ,] { - set v [string trim $v "$ "] - set initialized($v) 1 - } - } - # Check for assigments via = operator - set re $varre - append re {\s*=} - set l [regexp -all -inline $re $line] - foreach {a -} $l { - set a [string trim $a "=$ "] - regsub -all {\[.*\]} $a {[]} a - #puts "assigmnent of $a" - set initialized($a) 1 - regsub -all {\[\]} $a {} a - set initialized($a) 1 - } - # Check for assignments via catch(Exception $e) - set re {} - append re {catch\s*\(.*\s+(} $varre {)} - set l [regexp -all -inline -nocase $re $line] - foreach {- a} $l { - set initialized([string trim $a "$ "]) 1 - } - # Check for assignments by reference - # - # funclist format is {type funcname spos epos} where spos is the - # zero-based index of the first argument that can be considered - # an assignment, while epos is the last. - # - # name is the function name to match, and type is what - # to do with the args. "assignment" to consider them assigned - # or "ingore" to ingore them for the current line. - # - # The "ignore" is used for isset() and other functions that can - # deal with not initialized vars. - unset -nocomplain -- ignore - array set ignore {} - set funclist { - assignment scanf 2 100 - assignment preg_match 2 100 - assignment preg_match_all 2 100 - assignment ereg 2 100 - ignore isset 0 0 - } - set cline $line - regsub -all {'[^']+'} $cline {''} cline - foreach {type name spos epos} $funclist { - set re {} - append re $name {\s*\(([^()]*)\)} - foreach {- fargs} [regexp -all -inline $re $cline] { - set argidx 0 - foreach a [split $fargs ,] { - set a [string trim $a ", $"] - regsub -all {\[.*\]} $a {} a - if {$argidx >= $spos && $argidx <= $epos} { - if {$type eq {assignment}} { - set initialized($a) 1 - } elseif {$type eq {ignore}} { - set ignore($a) 1 - } - } - incr argidx - } - } - } - - # Check for var accesses - set varsimplere {(?:::)?\$[_A-Za-z]+[_A-Za-z0-9]*} - set l [regexp -all -inline $varsimplere $line] - foreach a $l { - set a [string trim $a "=$ "] - regsub -all {\[.*\]} $a {} a - # Skip access to class-static variables - if {[string first :: $a] == 0} { - continue - } - #puts "access of $a" - if {![info exists initialized($a)] && - ![info exists ignore($a)]} { - puts "* In $file line $linenr: access to uninitialized var '$a'" - } - } - } -} - -proc main argv { - foreach file $argv { - scan $file - } -} - -main $argv diff --git a/setup/test/tests/stubs.php b/setup/test/tests/stubs.php index fba61da31c613e7a3cae65f8ba37c8f08f599a27..73afec7cd326565fe56d30f0690281a6dd565b7f 100644 --- a/setup/test/tests/stubs.php +++ b/setup/test/tests/stubs.php @@ -6,14 +6,23 @@ class mysqli { function real_escape_string() {} function fetch_row() {} function prepare() {} + function ssl_set() {} + function real_connect() {} + function select_db() {} } class mysqli_stmt { + var $num_rows; + function store_result() {} function data_seek() {} function fetch() {} + function fetch_array() {} function fetch_field() {} + function fetch_field_direct() {} + function fetch_row() {} function result_metadata() {} + function free() {} } class ReflectionClass { diff --git a/setup/test/tests/test.undefinedmethods.php b/setup/test/tests/test.undefinedmethods.php index 4d22d1aafed62fcfe6d1b68dc985a03db6020343..2e06a452ba20d648f6b52c5f2c3cfd8fc3f0e818 100644 --- a/setup/test/tests/test.undefinedmethods.php +++ b/setup/test/tests/test.undefinedmethods.php @@ -32,7 +32,7 @@ function find_function_calls($scripts) { $lineno=0; foreach ($lines as $line) { $lineno++; $matches=array(); - preg_match_all('/(?:-[>]|::)([a-zA-Z0-9_]+)\(.*/', $line, $matches, + preg_match_all('/^.*\w+(?:-[>]|::)([a-zA-Z0-9_]+)\(.*/', $line, $matches, PREG_SET_ORDER); foreach ($matches as $m) if (strpos($m[0], 'nolint') === false) diff --git a/setup/test/tests/test.unitialized.php b/setup/test/tests/test.unitialized.php index 09bd0509e155072ccb6044429248965c2e318b36..47978fdb2fd9741bd2c4f554d527795a4927cbed 100644 --- a/setup/test/tests/test.unitialized.php +++ b/setup/test/tests/test.unitialized.php @@ -1,5 +1,6 @@ <?php require_once "class.test.php"; +require_once "class.php_analyze.php"; class UnitializedVars extends Test { var $name = "Access to unitialized variables"; @@ -7,16 +8,21 @@ class UnitializedVars extends Test { function testUnitializedUsage() { $scripts = $this->getAllScripts(); $matches = array(); - foreach (range(0, count($scripts), 40) as $start) { - $slice = array_slice($scripts, $start, 40); - ob_start(); - # XXX: This won't run well on Windoze - system(dirname(__file__)."/lib/phplint.tcl ".implode(" ", $slice)); - $lint_errors = ob_get_clean(); - preg_match_all("/\* In (.*) line (\d+): access to uninitialized var '([^']+)'/m", - $lint_errors, $matches, PREG_SET_ORDER); - foreach ($matches as $match) - $this->fail($match[1], $match[2], "'\${$match[3]}'"); + foreach ($scripts as $s) { + $a = new SourceAnalyzer($s); + $a->parseFile(); + foreach ($a->bugs as $bug) { + if ($bug['type'] == 'UNDEF_ACCESS') { + list($line, $file) = $bug['line']; + $this->fail($file, $line, "'{$bug['name']}'"); + } + elseif ($bug['type'] == 'MAYBE_UNDEF_ACCESS') { + list($line, $file) = $bug['line']; + $this->warn("Possible access to NULL object @ $file : $line"); + } + } + if (!$a->bugs) + $this->pass(); } } }