diff --git a/README.md b/README.md index 63eae4967a6558339879c92e98743dd950d7f56b..c6d45adbddc61bb032759f4662529faeb60452f9 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,11 @@ osTicket-1.7, visit the /scp page of you ticketing system. The upgrader will be presented and will walk you through the rest of the process. (The couple clicks needed to go through the process are pretty boring to describe). +**WARNING**: If you are upgrading from osTicket 1.6, please ensure that all + your files in your upload folder are both readable and writable to your + http server software. Unreadable files will not be migrated to the + database during the upgrade and will be effectively lost. + View the UPGRADING.txt file for other todo items to complete your upgrade. Help @@ -86,3 +91,5 @@ osTicket is supported by several magical open source projects including: * [PEAR/Net_Socket](http://pear.php.net/package/Net_Socket) * [PEAR/Serivces_JSON](http://pear.php.net/package/Services_JSON) * [phplint](http://antirez.com/page/phplint.html) + * [phpseclib](http://phpseclib.sourceforge.net/) + * [Spyc](http://github.com/mustangostang/spyc) diff --git a/WHATSNEW.md b/WHATSNEW.md index 752e61fc94d82555e76d0ad61a7d3ea927871bca..507b1c521cec807155a19370ab14b518eb902b9c 100644 --- a/WHATSNEW.md +++ b/WHATSNEW.md @@ -1,3 +1,72 @@ +osTicket v1.7.1 +=============== +### Bugfixes + * Properly reject attachments submitted via the API (#668) + * Correctly support the "Use Reply-To" in ticket filters (#669) + * Don't log users out after changing username or email address (#684) + * Don't leak private FAQ article titles (#683) + +osTicket v1.7.1-rc1 +=================== +### Enhancements + * Custom logos and site pages (#604, #632, #616) + * Password reset link (#638) + * Export and import feature. Useful for migrations and backups. (#626) + * Use your email address as your username for logins (#631) + * SLA's can be marked *transient*. Tickets with a transient SLA will + change to the SLA of the new department or help-topic when transferred + or edited. + * Support installation on MySQL and MariaDB clusters. Use default storage + engine and don't assume predictable auto-increment values (#568, #621) + +### Geeky Stuff + * mysqli support for PHP5+ + * SSL support for database connections + * Namespaced configuration. This greatly simplifies the process of adding + * new configurable item (#564) + * Add signals API. A simple event hooking mechanism to allow for + * extensibility (#577) + * Add deployment command-line script (#586) + * Allow XHTML editing in the nicEditor (#615) + * Allow parallel database migration streams (#563) -- paves the way for + *extensions* + * Use row-based email templates (#604) -- simplifies the process of adding + new email message templates (think *extensions*) + * Support fetching from email boxes with aliased email addresses (#663) + * Introduce new crypto library that provides failsafe encryption for email + passwords (#651) + +### Bugfixes + * Several typos in code and messages (#617, #618, #644, #660) + * Fix several upgrader bugs (#548, #619) + * Fix install fail on some Windows platforms (#570) + * Fix several issues in the command-line management (#580) + * Make room for command-line installation of osTicket (#581) + * *regression* Fix corrupted attachment downloads (#579, #583) + * Fix truncated attachment downloads when `zlib.output_compression` is + enabled (#596) + * Disable cron activities when upgrade is pending (#594) + * Provide failsafe encoding for improperly-formatted emails (#601) + * Fix corrupted email attachments processed via `pipe.php` (#607) + * Fix discarding of poorly encoded base64 emails (#624) + * Support MariaDB 10.0+ (#630) + * Properly trim ticket email and name fields (#600) + * Fix truncated text from text/plain emails and web interface posts (#652) + * Add **Assigned To** and other fields to ticket view export (#646) + * *regression* Fix attachment migration (#648) + * Display correct staff notes (#588) + * Display correct auto-response email for departments (#575) + * Fix login form ("Authentication Required") loop (#653) + * Ensure email message-id is fetched correctly (#664) + * Ensure X-Forwarded-For header does not have leading or trailing + whitespace (#665) + +### Performance + * Only fetch configuration for multifile upload if necessary (#637) + * Don't use sessions on the API (#623) + * *regression* Avoid an extra query per request to fetch schema signature + (#658) + New stuff in 1.7.0 ==================== * Bug fixes from rc6 diff --git a/api/api.inc.php b/api/api.inc.php index 351a21570f24b3e643b8ab8e90e562054c466bd0..ecde89f9b7d8c6c7aa6a6f1082c39fd5a9484c6d 100644 --- a/api/api.inc.php +++ b/api/api.inc.php @@ -14,6 +14,13 @@ vim: expandtab sw=4 ts=4 sts=4: **********************************************************************/ file_exists('../main.inc.php') or die('System Error'); + +// Disable sessions for the API. API should be considered stateless and +// shouldn't chew up database records to store sessions +if (!function_exists('noop')) { function noop() {} } +session_set_save_handler('noop','noop','noop','noop','noop','noop'); +define('DISABLE_SESSION', true); + require_once('../main.inc.php'); require_once(INCLUDE_DIR.'class.http.php'); require_once(INCLUDE_DIR.'class.api.php'); diff --git a/assets/default/images/logo.png b/assets/default/images/logo.png index 3b021740c7850b14d840fdf43bf790a06dc475fb..6ac1fa6151d16f264130cf79a8fc16875047cf46 100644 Binary files a/assets/default/images/logo.png and b/assets/default/images/logo.png differ diff --git a/client.inc.php b/client.inc.php index 6fe324bff52eea9229f7bd774800f94fe2149ff1..262b8680c791a3e96d2e4d8ff5fbe462cee29022 100644 --- a/client.inc.php +++ b/client.inc.php @@ -29,7 +29,8 @@ define('OSTCLIENTINC',TRUE); define('ASSETS_PATH',ROOT_PATH.'assets/default/'); //Check the status of the HelpDesk. -if(!is_object($ost) || !$ost->isSystemOnline()) { +if (!in_array(strtolower(basename($_SERVER['SCRIPT_NAME'])), array('logo.php',)) + && !(is_object($ost) && $ost->isSystemOnline())) { include('./offline.php'); exit; } diff --git a/include/Spyc.php b/include/Spyc.php new file mode 100644 index 0000000000000000000000000000000000000000..17d67db3ea32baa729606efc7e42b6468b0d8131 --- /dev/null +++ b/include/Spyc.php @@ -0,0 +1,1084 @@ +<?php +/** + * Spyc -- A Simple PHP YAML Class + * @version 0.5.1 + * @author Vlad Andersen <vlad.andersen@gmail.com> + * @author Chris Wanstrath <chris@ozmm.org> + * @link http://code.google.com/p/spyc/ + * @copyright Copyright 2005-2006 Chris Wanstrath, 2006-2011 Vlad Andersen + * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @package Spyc + */ + +if (!function_exists('spyc_load')) { + /** + * Parses YAML to array. + * @param string $string YAML string. + * @return array + */ + function spyc_load ($string) { + return Spyc::YAMLLoadString($string); + } +} + +if (!function_exists('spyc_load_file')) { + /** + * Parses YAML to array. + * @param string $file Path to YAML file. + * @return array + */ + function spyc_load_file ($file) { + return Spyc::YAMLLoad($file); + } +} + +if (!function_exists('spyc_dump')) { + /** + * Dumps array to YAML. + * @param array $data Array. + * @return string + */ + function spyc_dump ($data) { + return Spyc::YAMLDump($data, false, false, true); + } +} + +/** + * The Simple PHP YAML Class. + * + * This class can be used to read a YAML file and convert its contents + * into a PHP array. It currently supports a very limited subsection of + * the YAML spec. + * + * Usage: + * <code> + * $Spyc = new Spyc; + * $array = $Spyc->load($file); + * </code> + * or: + * <code> + * $array = Spyc::YAMLLoad($file); + * </code> + * or: + * <code> + * $array = spyc_load_file($file); + * </code> + * @package Spyc + */ +class Spyc { + + // SETTINGS + + const REMPTY = "\0\0\0\0\0"; + + /** + * Setting this to true will force YAMLDump to enclose any string value in + * quotes. False by default. + * + * @var bool + */ + var $setting_dump_force_quotes = false; + + /** + * Setting this to true will forse YAMLLoad to use syck_load function when + * possible. False by default. + * @var bool + */ + var $setting_use_syck_is_possible = false; + + + + /**#@+ + * @access private + * @var mixed + */ + var $_dumpIndent; + var $_dumpWordWrap; + var $_containsGroupAnchor = false; + var $_containsGroupAlias = false; + var $path; + var $result; + var $LiteralPlaceHolder = '___YAML_Literal_Block___'; + var $SavedGroups = array(); + var $indent; + /** + * Path modifier that should be applied after adding current element. + * @var array + */ + var $delayedPath = array(); + + /**#@+ + * @access public + * @var mixed + */ + var $_nodeId; + +/** + * Load a valid YAML string to Spyc. + * @param string $input + * @return array + */ + function load ($input) { + return $this->__loadString($input); + } + + /** + * Load a valid YAML file to Spyc. + * @param string $file + * @return array + */ + function loadFile ($file) { + return $this->__load($file); + } + + /** + * Load YAML into a PHP array statically + * + * The load method, when supplied with a YAML stream (string or file), + * will do its best to convert YAML in a file into a PHP array. Pretty + * simple. + * Usage: + * <code> + * $array = Spyc::YAMLLoad('lucky.yaml'); + * print_r($array); + * </code> + * @access public + * @return array + * @param string $input Path of YAML file or string containing YAML + */ + function YAMLLoad($input) { + $Spyc = new Spyc; + return $Spyc->__load($input); + } + + /** + * Load a string of YAML into a PHP array statically + * + * The load method, when supplied with a YAML string, will do its best + * to convert YAML in a string into a PHP array. Pretty simple. + * + * Note: use this function if you don't want files from the file system + * loaded and processed as YAML. This is of interest to people concerned + * about security whose input is from a string. + * + * Usage: + * <code> + * $array = Spyc::YAMLLoadString("---\n0: hello world\n"); + * print_r($array); + * </code> + * @access public + * @return array + * @param string $input String containing YAML + */ + function YAMLLoadString($input) { + $Spyc = new Spyc; + return $Spyc->__loadString($input); + } + + /** + * Dump YAML from PHP array statically + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. Pretty simple. Feel free to + * save the returned string as nothing.yaml and pass it around. + * + * Oh, and you can decide how big the indent is and what the wordwrap + * for folding is. Pretty cool -- just pass in 'false' for either if + * you want to use the default. + * + * Indent's default is 2 spaces, wordwrap's default is 40 characters. And + * you can turn off wordwrap by passing in 0. + * + * @access public + * @return string + * @param array $array PHP array + * @param int $indent Pass in false to use the default, which is 2 + * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40) + * @param int $no_opening_dashes Do not start YAML file with "---\n" + */ + function YAMLDump($array, $indent = false, $wordwrap = false, $no_opening_dashes = false) { + $spyc = new Spyc; + return $spyc->dump($array, $indent, $wordwrap, $no_opening_dashes); + } + + + /** + * Dump PHP array to YAML + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. Pretty simple. Feel free to + * save the returned string as tasteful.yaml and pass it around. + * + * Oh, and you can decide how big the indent is and what the wordwrap + * for folding is. Pretty cool -- just pass in 'false' for either if + * you want to use the default. + * + * Indent's default is 2 spaces, wordwrap's default is 40 characters. And + * you can turn off wordwrap by passing in 0. + * + * @access public + * @return string + * @param array $array PHP array + * @param int $indent Pass in false to use the default, which is 2 + * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40) + */ + function dump($array,$indent = false,$wordwrap = false, $no_opening_dashes = false) { + // Dumps to some very clean YAML. We'll have to add some more features + // and options soon. And better support for folding. + + // New features and options. + if ($indent === false or !is_numeric($indent)) { + $this->_dumpIndent = 2; + } else { + $this->_dumpIndent = $indent; + } + + if ($wordwrap === false or !is_numeric($wordwrap)) { + $this->_dumpWordWrap = 40; + } else { + $this->_dumpWordWrap = $wordwrap; + } + + // New YAML document + $string = ""; + if (!$no_opening_dashes) $string = "---\n"; + + // Start at the base of the array and move through it. + if ($array) { + $array = (array)$array; + $previous_key = -1; + foreach ($array as $key => $value) { + if (!isset($first_key)) $first_key = $key; + $string .= $this->_yamlize($key,$value,0,$previous_key, $first_key, $array); + $previous_key = $key; + } + } + return $string; + } + + /** + * Attempts to convert a key / value array item to YAML + * @access private + * @return string + * @param $key The name of the key + * @param $value The value of the item + * @param $indent The indent of the current node + */ + function _yamlize($key,$value,$indent, $previous_key = -1, $first_key = 0, $source_array = null) { + if (is_array($value)) { + if (empty ($value)) + return $this->_dumpNode($key, array(), $indent, $previous_key, $first_key, $source_array); + // It has children. What to do? + // Make it the right kind of item + $string = $this->_dumpNode($key, $this->REMPTY, $indent, $previous_key, $first_key, $source_array); + // Add the indent + $indent += $this->_dumpIndent; + // Yamlize the array + $string .= $this->_yamlizeArray($value,$indent); + } elseif (!is_array($value)) { + // It doesn't have children. Yip. + $string = $this->_dumpNode($key, $value, $indent, $previous_key, $first_key, $source_array); + } + return $string; + } + + /** + * Attempts to convert an array to YAML + * @access private + * @return string + * @param $array The array you want to convert + * @param $indent The indent of the current level + */ + function _yamlizeArray($array,$indent) { + if (is_array($array)) { + $string = ''; + $previous_key = -1; + foreach ($array as $key => $value) { + if (!isset($first_key)) $first_key = $key; + $string .= $this->_yamlize($key, $value, $indent, $previous_key, $first_key, $array); + $previous_key = $key; + } + return $string; + } else { + return false; + } + } + + /** + * Returns YAML from a key and a value + * @access private + * @return string + * @param $key The name of the key + * @param $value The value of the item + * @param $indent The indent of the current node + */ + function _dumpNode($key, $value, $indent, $previous_key = -1, $first_key = 0, $source_array = null) { + // do some folding here, for blocks + if (is_string ($value) && ((strpos($value,"\n") !== false || strpos($value,": ") !== false || strpos($value,"- ") !== false || + strpos($value,"*") !== false || strpos($value,"#") !== false || strpos($value,"<") !== false || strpos($value,">") !== false || strpos ($value, ' ') !== false || + strpos($value,"[") !== false || strpos($value,"]") !== false || strpos($value,"{") !== false || strpos($value,"}") !== false) || strpos($value,"&") !== false || strpos($value, "'") !== false || strpos($value, "!") === 0 || + substr ($value, -1, 1) == ':') + ) { + $value = $this->_doLiteralBlock($value,$indent); + } else { + $value = $this->_doFolding($value,$indent); + } + + if ($value === array()) $value = '[ ]'; + if ($value === "") $value = '""'; + if (in_array ($value, array ('true', 'TRUE', 'false', 'FALSE', 'y', 'Y', 'n', 'N', 'null', 'NULL'), true)) { + $value = $this->_doLiteralBlock($value,$indent); + } + if (trim ($value) != $value) + $value = $this->_doLiteralBlock($value,$indent); + + if (is_bool($value)) { + $value = ($value) ? "true" : "false"; + } + + if ($value === null) $value = 'null'; + if ($value === "'" . $this->REMPTY . "'") $value = null; + + $spaces = str_repeat(' ',$indent); + + //if (is_int($key) && $key - 1 == $previous_key && $first_key===0) { + if (is_array ($source_array) && array_keys($source_array) === range(0, count($source_array) - 1)) { + // It's a sequence + $string = $spaces.'- '.$value."\n"; + } else { + // if ($first_key===0) trigger_error('Keys are all screwy. The first one was zero, now it\'s "'. $key .'"',E_USER_ERROR); + // It's mapped + if (strpos($key, ":") !== false || strpos($key, "#") !== false) { $key = '"' . $key . '"'; } + $string = rtrim ($spaces.$key.': '.$value)."\n"; + } + return $string; + } + + /** + * Creates a literal block for dumping + * @access private + * @return string + * @param $value + * @param $indent int The value of the indent + */ + function _doLiteralBlock($value,$indent) { + if ($value === "\n") return '\n'; + if (strpos($value, "\n") === false && strpos($value, "'") === false) { + return sprintf ("'%s'", $value); + } + if (strpos($value, "\n") === false && strpos($value, '"') === false) { + return sprintf ('"%s"', $value); + } + $exploded = explode("\n",$value); + $newValue = '|'; + $indent += $this->_dumpIndent; + $spaces = str_repeat(' ',$indent); + foreach ($exploded as $line) { + $newValue .= "\n" . $spaces . ($line); + } + return $newValue; + } + + /** + * Folds a string of text, if necessary + * @access private + * @return string + * @param $value The string you wish to fold + */ + function _doFolding($value,$indent) { + // Don't do anything if wordwrap is set to 0 + + if ($this->_dumpWordWrap !== 0 && is_string ($value) && strlen($value) > $this->_dumpWordWrap) { + $indent += $this->_dumpIndent; + $indent = str_repeat(' ',$indent); + $wrapped = wordwrap($value,$this->_dumpWordWrap,"\n$indent"); + $value = ">\n".$indent.$wrapped; + } else { + if ($this->setting_dump_force_quotes && is_string ($value) && $value !== $this->REMPTY) + $value = '"' . $value . '"'; + if (is_numeric($value) && is_string($value)) + $value = '"' . $value . '"'; + } + + + return $value; + } + +// LOADING FUNCTIONS + + function __load($input) { + $Source = $this->loadFromSource($input); + return $this->loadWithSource($Source); + } + + function __loadString($input) { + $Source = $this->loadFromString($input); + return $this->loadWithSource($Source); + } + + function loadWithSource($Source) { + if (empty ($Source)) return array(); + if ($this->setting_use_syck_is_possible && function_exists ('syck_load')) { + $array = syck_load (implode ("\n", $Source)); + return is_array($array) ? $array : array(); + } + + $this->path = array(); + $this->result = array(); + + $cnt = count($Source); + for ($i = 0; $i < $cnt; $i++) { + $line = $Source[$i]; + + $this->indent = strlen($line) - strlen(ltrim($line)); + $tempPath = $this->getParentPathByIndent($this->indent); + $line = $this->stripIndent($line, $this->indent); + if ($this->isComment($line)) continue; + if ($this->isEmpty($line)) continue; + $this->path = $tempPath; + + $literalBlockStyle = $this->startsLiteralBlock($line); + if ($literalBlockStyle) { + $line = rtrim ($line, $literalBlockStyle . " \n"); + $literalBlock = ''; + $line .= $this->LiteralPlaceHolder; + $literal_block_indent = strlen($Source[$i+1]) - strlen(ltrim($Source[$i+1])); + while (++$i < $cnt && $this->literalBlockContinues($Source[$i], $this->indent)) { + $literalBlock = $this->addLiteralLine($literalBlock, $Source[$i], $literalBlockStyle, $literal_block_indent); + } + $i--; + } + + // Strip out comments + if (strpos ($line, '#')) { + $line = preg_replace('/\s*#([^"\']+)$/','',$line); + } + + while (++$i < $cnt && $this->greedilyNeedNextLine($line)) { + $line = rtrim ($line, " \n\t\r") . ' ' . ltrim ($Source[$i], " \t"); + } + $i--; + + $lineArray = $this->_parseLine($line); + + if ($literalBlockStyle) + $lineArray = $this->revertLiteralPlaceHolder ($lineArray, $literalBlock); + + $this->addArray($lineArray, $this->indent); + + foreach ($this->delayedPath as $indent => $delayedPath) + $this->path[$indent] = $delayedPath; + + $this->delayedPath = array(); + + } + return $this->result; + } + + function loadFromSource ($input) { + if (!empty($input) && strpos($input, "\n") === false && file_exists($input)) + $input = file_get_contents($input); + + return $this->loadFromString($input); + } + + function loadFromString ($input) { + $lines = explode("\n",$input); + foreach ($lines as $k => $_) { + $lines[$k] = rtrim ($_, "\r"); + } + return $lines; + } + + /** + * Parses YAML code and returns an array for a node + * @access private + * @return array + * @param string $line A line from the YAML file + */ + function _parseLine($line) { + if (!$line) return array(); + $line = trim($line); + if (!$line) return array(); + + $array = array(); + + $group = $this->nodeContainsGroup($line); + if ($group) { + $this->addGroup($line, $group); + $line = $this->stripGroup ($line, $group); + } + + if ($this->startsMappedSequence($line)) + return $this->returnMappedSequence($line); + + if ($this->startsMappedValue($line)) + return $this->returnMappedValue($line); + + if ($this->isArrayElement($line)) + return $this->returnArrayElement($line); + + if ($this->isPlainArray($line)) + return $this->returnPlainArray($line); + + + return $this->returnKeyValuePair($line); + + } + + /** + * Finds the type of the passed value, returns the value as the new type. + * @access private + * @param string $value + * @return mixed + */ + function _toType($value) { + if ($value === '') return ""; + $first_character = $value[0]; + $last_character = substr($value, -1, 1); + + $is_quoted = false; + do { + if (!$value) break; + if ($first_character != '"' && $first_character != "'") break; + if ($last_character != '"' && $last_character != "'") break; + $is_quoted = true; + } while (0); + + if ($is_quoted) + return strtr(substr ($value, 1, -1), array ('\\"' => '"', '\'\'' => '\'', '\\\'' => '\'')); + + if (strpos($value, ' #') !== false && !$is_quoted) + $value = preg_replace('/\s+#(.+)$/','',$value); + + if (!$is_quoted) $value = str_replace('\n', "\n", $value); + + if ($first_character == '[' && $last_character == ']') { + // Take out strings sequences and mappings + $innerValue = trim(substr ($value, 1, -1)); + if ($innerValue === '') return array(); + $explode = $this->_inlineEscape($innerValue); + // Propagate value array + $value = array(); + foreach ($explode as $v) { + $value[] = $this->_toType($v); + } + return $value; + } + + if (strpos($value,': ')!==false && $first_character != '{') { + $array = explode(': ',$value); + $key = trim($array[0]); + array_shift($array); + $value = trim(implode(': ',$array)); + $value = $this->_toType($value); + return array($key => $value); + } + + if ($first_character == '{' && $last_character == '}') { + $innerValue = trim(substr ($value, 1, -1)); + if ($innerValue === '') return array(); + // Inline Mapping + // Take out strings sequences and mappings + $explode = $this->_inlineEscape($innerValue); + // Propagate value array + $array = array(); + foreach ($explode as $v) { + $SubArr = $this->_toType($v); + if (empty($SubArr)) continue; + if (is_array ($SubArr)) { + $array[key($SubArr)] = $SubArr[key($SubArr)]; continue; + } + $array[] = $SubArr; + } + return $array; + } + + if ($value == 'null' || $value == 'NULL' || $value == 'Null' || $value == '' || $value == '~') { + return null; + } + + if ( is_numeric($value) && preg_match ('/^(-|)[1-9]+[0-9]*$/', $value) ){ + $intvalue = (int)$value; + if ($intvalue != PHP_INT_MAX) + $value = $intvalue; + return $value; + } + + if (in_array($value, + array('true', 'on', '+', 'yes', 'y', 'True', 'TRUE', 'On', 'ON', 'YES', 'Yes', 'Y'))) { + return true; + } + + if (in_array(strtolower($value), + array('false', 'off', '-', 'no', 'n'))) { + return false; + } + + if (is_numeric($value)) { + if ($value === '0') return 0; + if (rtrim ($value, 0) === $value) + $value = (float)$value; + return $value; + } + + return $value; + } + + /** + * Used in inlines to check for more inlines or quoted strings + * @access private + * @return array + */ + function _inlineEscape($inline) { + // There's gotta be a cleaner way to do this... + // While pure sequences seem to be nesting just fine, + // pure mappings and mappings with sequences inside can't go very + // deep. This needs to be fixed. + + $seqs = array(); + $maps = array(); + $saved_strings = array(); + $saved_empties = array(); + + // Check for empty strings + $regex = '/("")|(\'\')/'; + if (preg_match_all($regex,$inline,$strings)) { + $saved_empties = $strings[0]; + $inline = preg_replace($regex,'YAMLEmpty',$inline); + } + unset($regex); + + // Check for strings + $regex = '/(?:(")|(?:\'))((?(1)[^"]+|[^\']+))(?(1)"|\')/'; + if (preg_match_all($regex,$inline,$strings)) { + $saved_strings = $strings[0]; + $inline = preg_replace($regex,'YAMLString',$inline); + } + unset($regex); + + // echo $inline; + + $i = 0; + do { + + // Check for sequences + while (preg_match('/\[([^{}\[\]]+)\]/U',$inline,$matchseqs)) { + $seqs[] = $matchseqs[0]; + $inline = preg_replace('/\[([^{}\[\]]+)\]/U', ('YAMLSeq' . (count($seqs) - 1) . 's'), $inline, 1); + } + + // Check for mappings + while (preg_match('/{([^\[\]{}]+)}/U',$inline,$matchmaps)) { + $maps[] = $matchmaps[0]; + $inline = preg_replace('/{([^\[\]{}]+)}/U', ('YAMLMap' . (count($maps) - 1) . 's'), $inline, 1); + } + + if ($i++ >= 10) break; + + } while (strpos ($inline, '[') !== false || strpos ($inline, '{') !== false); + + $explode = explode(',',$inline); + $explode = array_map('trim', $explode); + $stringi = 0; $i = 0; + + while (1) { + + // Re-add the sequences + if (!empty($seqs)) { + foreach ($explode as $key => $value) { + if (strpos($value,'YAMLSeq') !== false) { + foreach ($seqs as $seqk => $seq) { + $explode[$key] = str_replace(('YAMLSeq'.$seqk.'s'),$seq,$value); + $value = $explode[$key]; + } + } + } + } + + // Re-add the mappings + if (!empty($maps)) { + foreach ($explode as $key => $value) { + if (strpos($value,'YAMLMap') !== false) { + foreach ($maps as $mapk => $map) { + $explode[$key] = str_replace(('YAMLMap'.$mapk.'s'), $map, $value); + $value = $explode[$key]; + } + } + } + } + + + // Re-add the strings + if (!empty($saved_strings)) { + foreach ($explode as $key => $value) { + while (strpos($value,'YAMLString') !== false) { + $explode[$key] = preg_replace('/YAMLString/',$saved_strings[$stringi],$value, 1); + unset($saved_strings[$stringi]); + ++$stringi; + $value = $explode[$key]; + } + } + } + + + // Re-add the empties + if (!empty($saved_empties)) { + foreach ($explode as $key => $value) { + while (strpos($value,'YAMLEmpty') !== false) { + $explode[$key] = preg_replace('/YAMLEmpty/', '', $value, 1); + $value = $explode[$key]; + } + } + } + + $finished = true; + foreach ($explode as $key => $value) { + if (strpos($value,'YAMLSeq') !== false) { + $finished = false; break; + } + if (strpos($value,'YAMLMap') !== false) { + $finished = false; break; + } + if (strpos($value,'YAMLString') !== false) { + $finished = false; break; + } + if (strpos($value,'YAMLEmpty') !== false) { + $finished = false; break; + } + } + if ($finished) break; + + $i++; + if ($i > 10) + break; // Prevent infinite loops. + } + + + return $explode; + } + + function literalBlockContinues ($line, $lineIndent) { + if (!trim($line)) return true; + if (strlen($line) - strlen(ltrim($line)) > $lineIndent) return true; + return false; + } + + function referenceContentsByAlias ($alias) { + do { + if (!isset($this->SavedGroups[$alias])) { echo "Bad group name: $alias."; break; } + $groupPath = $this->SavedGroups[$alias]; + $value = $this->result; + foreach ($groupPath as $k) { + $value = $value[$k]; + } + } while (false); + return $value; + } + + function addArrayInline ($array, $indent) { + $CommonGroupPath = $this->path; + if (empty ($array)) return false; + + foreach ($array as $k => $_) { + $this->addArray(array($k => $_), $indent); + $this->path = $CommonGroupPath; + } + return true; + } + + function addArray ($incoming_data, $incoming_indent) { + + // print_r ($incoming_data); + + if (count ($incoming_data) > 1) + return $this->addArrayInline ($incoming_data, $incoming_indent); + + $key = key ($incoming_data); + $value = isset($incoming_data[$key]) ? $incoming_data[$key] : null; + if ($key === '__!YAMLZero') $key = '0'; + + if ($incoming_indent == 0 && !$this->_containsGroupAlias && !$this->_containsGroupAnchor) { // Shortcut for root-level values. + if ($key || $key === '' || $key === '0') { + $this->result[$key] = $value; + } else { + $this->result[] = $value; end ($this->result); $key = key ($this->result); + } + $this->path[$incoming_indent] = $key; + return; + } + + + + $history = array(); + // Unfolding inner array tree. + $history[] = $_arr = $this->result; + foreach ($this->path as $k) { + $history[] = $_arr = $_arr[$k]; + } + + if ($this->_containsGroupAlias) { + $value = $this->referenceContentsByAlias($this->_containsGroupAlias); + $this->_containsGroupAlias = false; + } + + + // Adding string or numeric key to the innermost level or $this->arr. + if (is_string($key) && $key == '<<') { + if (!is_array ($_arr)) { $_arr = array (); } + + $_arr = array_merge ($_arr, $value); + } else if ($key || $key === '' || $key === '0') { + if (!is_array ($_arr)) + $_arr = array ($key=>$value); + else + $_arr[$key] = $value; + } else { + if (!is_array ($_arr)) { $_arr = array ($value); $key = 0; } + else { $_arr[] = $value; end ($_arr); $key = key ($_arr); } + } + + $reverse_path = array_reverse($this->path); + $reverse_history = array_reverse ($history); + $reverse_history[0] = $_arr; + $cnt = count($reverse_history) - 1; + for ($i = 0; $i < $cnt; $i++) { + $reverse_history[$i+1][$reverse_path[$i]] = $reverse_history[$i]; + } + $this->result = $reverse_history[$cnt]; + + $this->path[$incoming_indent] = $key; + + if ($this->_containsGroupAnchor) { + $this->SavedGroups[$this->_containsGroupAnchor] = $this->path; + if (is_array ($value)) { + $k = key ($value); + if (!is_int ($k)) { + $this->SavedGroups[$this->_containsGroupAnchor][$incoming_indent + 2] = $k; + } + } + $this->_containsGroupAnchor = false; + } + + } + + function startsLiteralBlock ($line) { + $lastChar = substr (trim($line), -1); + if ($lastChar != '>' && $lastChar != '|') return false; + if ($lastChar == '|') return $lastChar; + // HTML tags should not be counted as literal blocks. + if (preg_match ('#<.*?>$#', $line)) return false; + return $lastChar; + } + + function greedilyNeedNextLine($line) { + $line = trim ($line); + if (!strlen($line)) return false; + if (substr ($line, -1, 1) == ']') return false; + if ($line[0] == '[') return true; + if (preg_match ('#^[^:]+?:\s*\[#', $line)) return true; + return false; + } + + function addLiteralLine ($literalBlock, $line, $literalBlockStyle, $indent = -1) { + $line = $this->stripIndent($line, $indent); + if ($literalBlockStyle !== '|') { + $line = $this->stripIndent($line); + } + $line = rtrim ($line, "\r\n\t ") . "\n"; + if ($literalBlockStyle == '|') { + return $literalBlock . $line; + } + if (strlen($line) == 0) + return rtrim($literalBlock, ' ') . "\n"; + if ($line == "\n" && $literalBlockStyle == '>') { + return rtrim ($literalBlock, " \t") . "\n"; + } + if ($line != "\n") + $line = trim ($line, "\r\n ") . " "; + return $literalBlock . $line; + } + + function revertLiteralPlaceHolder ($lineArray, $literalBlock) { + foreach ($lineArray as $k => $_) { + if (is_array($_)) + $lineArray[$k] = $this->revertLiteralPlaceHolder ($_, $literalBlock); + else if (substr($_, -1 * strlen ($this->LiteralPlaceHolder)) == $this->LiteralPlaceHolder) + $lineArray[$k] = rtrim ($literalBlock, " \r\n"); + } + return $lineArray; + } + + function stripIndent ($line, $indent = -1) { + if ($indent == -1) $indent = strlen($line) - strlen(ltrim($line)); + return substr ($line, $indent); + } + + function getParentPathByIndent ($indent) { + if ($indent == 0) return array(); + $linePath = $this->path; + do { + end($linePath); $lastIndentInParentPath = key($linePath); + if ($indent <= $lastIndentInParentPath) array_pop ($linePath); + } while ($indent <= $lastIndentInParentPath); + return $linePath; + } + + + function clearBiggerPathValues ($indent) { + + + if ($indent == 0) $this->path = array(); + if (empty ($this->path)) return true; + + foreach ($this->path as $k => $_) { + if ($k > $indent) unset ($this->path[$k]); + } + + return true; + } + + + function isComment ($line) { + if (!$line) return false; + if ($line[0] == '#') return true; + if (trim($line, " \r\n\t") == '---') return true; + return false; + } + + function isEmpty ($line) { + return (trim ($line) === ''); + } + + + function isArrayElement ($line) { + if (!$line) return false; + if ($line[0] != '-') return false; + if (strlen ($line) > 3) + if (substr($line,0,3) == '---') return false; + + return true; + } + + function isHashElement ($line) { + return strpos($line, ':'); + } + + function isLiteral ($line) { + if ($this->isArrayElement($line)) return false; + if ($this->isHashElement($line)) return false; + return true; + } + + + function unquote ($value) { + if (!$value) return $value; + if (!is_string($value)) return $value; + if ($value[0] == '\'') return trim ($value, '\''); + if ($value[0] == '"') return trim ($value, '"'); + return $value; + } + + function startsMappedSequence ($line) { + return ($line[0] == '-' && substr ($line, -1, 1) == ':'); + } + + function returnMappedSequence ($line) { + $array = array(); + $key = $this->unquote(trim(substr($line,1,-1))); + $array[$key] = array(); + $this->delayedPath = array(strpos ($line, $key) + $this->indent => $key); + return array($array); + } + + function returnMappedValue ($line) { + $array = array(); + $key = $this->unquote (trim(substr($line,0,-1))); + $array[$key] = ''; + return $array; + } + + function startsMappedValue ($line) { + return (substr ($line, -1, 1) == ':'); + } + + function isPlainArray ($line) { + return ($line[0] == '[' && substr ($line, -1, 1) == ']'); + } + + function returnPlainArray ($line) { + return $this->_toType($line); + } + + function returnKeyValuePair ($line) { + $array = array(); + $key = ''; + if (strpos ($line, ':')) { + // It's a key/value pair most likely + // If the key is in double quotes pull it out + if (($line[0] == '"' || $line[0] == "'") && preg_match('/^(["\'](.*)["\'](\s)*:)/',$line,$matches)) { + $value = trim(str_replace($matches[1],'',$line)); + $key = $matches[2]; + } else { + // Do some guesswork as to the key and the value + $explode = explode(':',$line); + $key = trim($explode[0]); + array_shift($explode); + $value = trim(implode(':',$explode)); + } + // Set the type of the value. Int, string, etc + $value = $this->_toType($value); + if ($key === '0') $key = '__!YAMLZero'; + $array[$key] = $value; + } else { + $array = array ($line); + } + return $array; + + } + + + function returnArrayElement ($line) { + if (strlen($line) <= 1) return array(array()); // Weird %) + $array = array(); + $value = trim(substr($line,1)); + $value = $this->_toType($value); + $array[] = $value; + return $array; + } + + + function nodeContainsGroup ($line) { + $symbolsForReference = 'A-z0-9_\-'; + if (strpos($line, '&') === false && strpos($line, '*') === false) return false; // Please die fast ;-) + if ($line[0] == '&' && preg_match('/^(&['.$symbolsForReference.']+)/', $line, $matches)) return $matches[1]; + if ($line[0] == '*' && preg_match('/^(\*['.$symbolsForReference.']+)/', $line, $matches)) return $matches[1]; + if (preg_match('/(&['.$symbolsForReference.']+)$/', $line, $matches)) return $matches[1]; + if (preg_match('/(\*['.$symbolsForReference.']+$)/', $line, $matches)) return $matches[1]; + if (preg_match ('#^\s*<<\s*:\s*(\*[^\s]+).*$#', $line, $matches)) return $matches[1]; + return false; + + } + + function addGroup ($line, $group) { + if ($group[0] == '&') $this->_containsGroupAnchor = substr ($group, 1); + if ($group[0] == '*') $this->_containsGroupAlias = substr ($group, 1); + //print_r ($this->path); + } + + function stripGroup ($line, $group) { + $line = trim(str_replace($group, '', $line)); + return $line; + } +} + +// Enable use of Spyc from command line +// The syntax is the following: php Spyc.php spyc.yaml + +do { + if (PHP_SAPI != 'cli') break; + if (empty ($_SERVER['argc']) || $_SERVER['argc'] < 2) break; + if (empty ($_SERVER['PHP_SELF']) || FALSE === strpos ($_SERVER['PHP_SELF'], 'Spyc.php') ) break; + $file = $argv[1]; + echo json_encode (spyc_load_file ($file)); +} while (0); diff --git a/include/ajax.content.php b/include/ajax.content.php index c9c6b7d78cca30243a769b175af1270383c8e0ce..197d75ffe50c622296ca0bb22335874773fb499b 100644 --- a/include/ajax.content.php +++ b/include/ajax.content.php @@ -15,9 +15,9 @@ **********************************************************************/ if(!defined('INCLUDE_DIR')) die('!'); - + class ContentAjaxAPI extends AjaxController { - + function log($id) { if($id && ($log=Log::lookup($id))) { @@ -39,7 +39,7 @@ class ContentAjaxAPI extends AjaxController { $content=' <div style="width:680px;"> <h2>Ticket Variables</h2> - Please note that non-base variables depends on the context of use. Visit osTicket Wiki for up to date documentation. + Please note that non-base variables depend on the context of use. Visit osTicket Wiki for up-to-date documentation. <br/> <table width="100%" border="0" cellspacing=1 cellpadding=2> <tr><td width="55%" valign="top"><b>Base Variables</b></td><td><b>Other Variables</b></td></tr> @@ -77,6 +77,8 @@ class ContentAjaxAPI extends AjaxController { <tr><td>%{assignee}</td><td>Assigned staff/team</td></tr> <tr><td>%{assigner}</td><td>Staff assigning the ticket</td></tr> <tr><td>%{url}</td><td>osTicket\'s base url (FQDN)</td></tr> + <tr><td>%{reset_link}</td> + <td>Reset link used by the password reset feature</td></tr> </table> </td> </tr> diff --git a/include/ajax.kbase.php b/include/ajax.kbase.php index a89161a79ac305be96996608f29609e23109c677..e467f51300dee94c438e81762421f5202baf96e1 100644 --- a/include/ajax.kbase.php +++ b/include/ajax.kbase.php @@ -15,9 +15,9 @@ **********************************************************************/ if(!defined('INCLUDE_DIR')) die('!'); - + class KbaseAjaxAPI extends AjaxController { - + function cannedResp($id, $format='') { global $thisstaff, $_GET; @@ -52,20 +52,21 @@ class KbaseAjaxAPI extends AjaxController { } function faq($id, $format='html') { - global $thisstaff; //XXX: user ajax->getThisStaff() + //XXX: user ajax->getThisStaff() (nolint) + global $thisstaff; include_once(INCLUDE_DIR.'class.faq.php'); if(!($faq=FAQ::lookup($id))) return null; - //TODO: $fag->getJSON() for json format. + //TODO: $fag->getJSON() for json format. (nolint) $resp = sprintf( '<div style="width:650px;"> <strong>%s</strong><p>%s</p> <div class="faded">Last updated %s</div> <hr> <a href="faq.php?id=%d">View</a> | <a href="faq.php?id=%d">Attachments (%s)</a>', - $faq->getQuestion(), + $faq->getQuestion(), Format::safe_html($faq->getAnswer()), Format::db_daydatetime($faq->getUpdateDate()), $faq->getId(), @@ -77,7 +78,7 @@ class KbaseAjaxAPI extends AjaxController { } $resp.='</div>'; - return $resp; + return $resp; } } ?> diff --git a/include/ajax.reports.php b/include/ajax.reports.php index 2634bc8d58c7d574a94cd87cf75ef528d5ac22fa..603fb41685e0c752ca6091edb451dab256309ecd 100644 --- a/include/ajax.reports.php +++ b/include/ajax.reports.php @@ -22,7 +22,7 @@ include_once(INCLUDE_DIR.'class.ticket.php'); /** * Overview Report - * + * * The overview report allows for the display of basic ticket statistics in * both graphical and tabular formats. */ @@ -76,14 +76,14 @@ class OverviewReportAjaxAPI extends AjaxController { "headers" => array('Staff Member'), "filter" => ('T1.staff_id=S1.staff_id - AND + AND (T1.staff_id='.db_input($thisstaff->getId()) .(($depts=$thisstaff->getManagedDepartments())? (' OR T1.dept_id IN('.implode(',', db_input($depts)).')'):'') .(($thisstaff->canViewStaffStats())? (' OR T1.dept_id IN('.implode(',', db_input($thisstaff->getDepts())).')'):'') .')' - ) + ) ) ); $group = $this->get('group', 'dept'); @@ -98,10 +98,10 @@ class OverviewReportAjaxAPI extends AjaxController { COUNT(*)-COUNT(NULLIF(A1.state, "overdue")) AS Overdue, COUNT(*)-COUNT(NULLIF(A1.state, "closed")) AS Closed, COUNT(*)-COUNT(NULLIF(A1.state, "reopened")) AS Reopened - FROM '.$info['table'].' T1 - LEFT JOIN '.TICKET_EVENT_TABLE.' A1 + FROM '.$info['table'].' T1 + LEFT JOIN '.TICKET_EVENT_TABLE.' A1 ON (A1.'.$info['pk'].'=T1.'.$info['pk'].' - AND NOT annulled + AND NOT annulled AND (A1.timestamp BETWEEN '.$start.' AND '.$stop.')) LEFT JOIN '.STAFF_TABLE.' S1 ON (S1.staff_id=A1.staff_id) WHERE '.$info['filter'].' @@ -110,7 +110,7 @@ class OverviewReportAjaxAPI extends AjaxController { array(1, 'SELECT '.$info['fields'].', FORMAT(AVG(DATEDIFF(T2.closed, T2.created)),1) AS ServiceTime - FROM '.$info['table'].' T1 + FROM '.$info['table'].' T1 LEFT JOIN '.TICKET_TABLE.' T2 ON (T2.'.$info['pk'].'=T1.'.$info['pk'].') LEFT JOIN '.STAFF_TABLE.' S1 ON (S1.staff_id=T2.staff_id) WHERE '.$info['filter'].' AND T2.closed BETWEEN '.$start.' AND '.$stop.' @@ -119,7 +119,7 @@ class OverviewReportAjaxAPI extends AjaxController { array(1, 'SELECT '.$info['fields'].', FORMAT(AVG(DATEDIFF(B2.created, B1.created)),1) AS ResponseTime - FROM '.$info['table'].' T1 + FROM '.$info['table'].' T1 LEFT JOIN '.TICKET_TABLE.' T2 ON (T2.'.$info['pk'].'=T1.'.$info['pk'].') LEFT JOIN '.TICKET_THREAD_TABLE.' B2 ON (B2.ticket_id = T2.ticket_id AND B2.thread_type="R") @@ -174,7 +174,7 @@ class OverviewReportAjaxAPI extends AjaxController { function getPlotData() { - + if(($start = $this->get('start', 'last month'))) { $stop = $this->get('stop', 'now'); if (substr($stop, 0, 1) == '+') @@ -211,6 +211,7 @@ class OverviewReportAjaxAPI extends AjaxController { $time = null; $times = array(); # Iterate over result set, adding zeros for missing ticket events + $slots = array(); while ($row = db_fetch_row($res)) { $row_time = strtotime($row[1]); if ($time != $row_time) { diff --git a/include/ajax.upgrader.php b/include/ajax.upgrader.php index 8b263ba0ced03b51cacc0341b20f0fc7b8dc01b1..d021086ef1a55c84745b741dcebc70df160613d7 100644 --- a/include/ajax.upgrader.php +++ b/include/ajax.upgrader.php @@ -25,21 +25,14 @@ class UpgraderAjaxAPI extends AjaxController { if(!$thisstaff or !$thisstaff->isAdmin() or !$ost) Http::response(403, 'Access Denied'); - $upgrader = new Upgrader($ost->getDBSignature(), TABLE_PREFIX, SQL_DIR); - - //Just report the next action on the first call. - if(!$_SESSION['ost_upgrader'] || !$_SESSION['ost_upgrader'][$upgrader->getShash()]['progress']) { - $_SESSION['ost_upgrader'][$upgrader->getShash()]['progress'] = $upgrader->getNextAction(); - Http::response(200, $upgrader->getNextAction()); - exit; - } + $upgrader = new Upgrader(TABLE_PREFIX, UPGRADE_DIR.'streams/'); if($upgrader->isAborted()) { Http::response(416, "We have a problem ... wait a sec."); exit; } - if($upgrader->getNumPendingTasks() && $upgrader->doTasks()) { + if($upgrader->getTask() && $upgrader->doTask()) { //More pending tasks - doTasks returns the number of pending tasks Http::response(200, $upgrader->getNextAction()); exit; @@ -51,7 +44,7 @@ class UpgraderAjaxAPI extends AjaxController { Http::response(200, "Upgraded to $version ... post-upgrade checks!"); exit; } - } else { + } else { //Abort: Upgrade pending but NOT upgradable - invalid or wrong hash. $upgrader->abort(sprintf('Upgrade Failed: Invalid or wrong hash [%s]',$ost->getDBSignature())); } diff --git a/include/api.tickets.php b/include/api.tickets.php index 1cc93d995fc52a563b6c8ce826137b4f198522c5..d9118bc9a04042988116524ebc705a5f4d2eba7b 100644 --- a/include/api.tickets.php +++ b/include/api.tickets.php @@ -19,7 +19,8 @@ class TicketApiController extends ApiController { ); if(!strcasecmp($format, 'email')) - $supported = array_merge($supported, array('header', 'mid', 'emailId', 'ticketId')); + $supported = array_merge($supported, array('header', 'mid', + 'emailId', 'ticketId', 'reply-to', 'reply-to-name')); return $supported; } @@ -42,7 +43,7 @@ class TicketApiController extends ApiController { if($data['attachments'] && is_array($data['attachments'])) { foreach($data['attachments'] as &$attachment) { if(!$ost->isFileTypeAllowed($attachment)) - $data['error'] = 'Invalid file type (ext) for '.Format::htmlchars($attachment['name']); + $attachment['error'] = 'Invalid file type (ext) for '.Format::htmlchars($attachment['name']); elseif ($attachment['encoding'] && !strcasecmp($attachment['encoding'], 'base64')) { if(!($attachment['data'] = base64_decode($attachment['data'], true))) $attachment['error'] = sprintf('%s: Poorly encoded base64 data', Format::htmlchars($attachment['name'])); @@ -159,7 +160,7 @@ class PipeApiController extends TicketApiController { if(($ticket=$pipe->processEmail())) return $pipe->response(201, $ticket->getNumber()); - return $pipe->exerr(416, 'Request failed -retry again!'); + return $pipe->exerr(416, 'Request failed - retry again!'); } } diff --git a/include/class.api.php b/include/class.api.php index cbaf2e8116ade833a15084bb0639eedd3d577525..b254d6ce610d72fcce57ad26851f47be216aa881 100644 --- a/include/class.api.php +++ b/include/class.api.php @@ -143,7 +143,7 @@ class API { $sql='INSERT INTO '.API_KEY_TABLE.' SET '.$sql .',created=NOW() ' .',ipaddr='.db_input($vars['ipaddr']) - .',apikey='.db_input(strtoupper(md5(time().$vars['ipaddr'].md5(Misc::randcode(16))))); + .',apikey='.db_input(strtoupper(md5(time().$vars['ipaddr'].md5(Misc::randCode(16))))); if(db_query($sql) && ($id=db_insert_id())) return $id; @@ -415,7 +415,7 @@ class ApiEmailDataParser extends EmailDataParser { $data['source'] = 'Email'; if(!$data['message']) - $data['message'] = $data['subject']?$data['subject']:'(EMPTY)'; + $data['message'] = $data['subject']?$data['subject']:'-'; if(!$data['subject']) $data['subject'] = '[No Subject]'; diff --git a/include/class.config.php b/include/class.config.php index e6bed4657f69b4c432e617a750042ad3271b3611..4114cfcafe264e5e48a384b85527e776d202b5c2 100644 --- a/include/class.config.php +++ b/include/class.config.php @@ -17,50 +17,154 @@ require_once(INCLUDE_DIR.'class.email.php'); class Config { - - var $id = 0; var $config = array(); - var $defaultDept; //Default Department - var $defaultSLA; //Default SLA - var $defaultEmail; //Default Email - var $alertEmail; //Alert Email - var $defaultSMTPEmail; //Default SMTP Email + var $section = null; # Default namespace ('core') + var $table = CONFIG_TABLE; # Table name (with prefix) + var $section_column = 'namespace'; # namespace column name - function Config($id) { - $this->load($id); - } + var $session = null; # Session-backed configuration + + # Defaults for this configuration. If settings don't exist in the + # database yet, the ->getInfo() method will not include the (default) + # values in the returned array. $defaults allows developers to define + # new settings and the corresponding default values. + var $defaults = array(); # List of default values - function load($id=0) { + function Config($section=null) { + if ($section) + $this->section = $section; - if(!$id && !($id=$this->getId())) + if ($this->section === null) return false; - $sql='SELECT *, (TIME_TO_SEC(TIMEDIFF(NOW(), UTC_TIMESTAMP()))/3600) as db_tz_offset ' - .' FROM '.CONFIG_TABLE - .' WHERE id='.db_input($id); + if (!isset($_SESSION['cfg:'.$this->section])) + $_SESSION['cfg:'.$this->section] = array(); + $this->session = &$_SESSION['cfg:'.$this->section]; - if(!($res=db_query($sql)) || !db_num_rows($res)) - return false; + $sql='SELECT id, `key`, value, `updated` FROM '.$this->table + .' WHERE `'.$this->section_column.'` = '.db_input($this->section); + if(($res=db_query($sql)) && db_num_rows($res)) + while ($row = db_fetch_array($res)) + $this->config[$row['key']] = $row; + } - $this->config = db_fetch_array($res); - $this->id = $this->config['id']; + function getNamespace() { + return $this->section; + } - //Get the default time zone - // We can't JOIN timezone table above due to upgrade support. - if($this->config['default_timezone_id']) - $this->config['tz_offset'] = Timezone::getOffsetById($this->config['default_timezone_id']); + function getInfo() { + $info = $this->defaults; + foreach ($this->config as $key=>$setting) + $info[$key] = $setting['value']; + return $info; + } + + function get($key, $default=null) { + if (isset($this->session[$key])) + return $this->session[$key]; + elseif (isset($this->config[$key])) + return $this->config[$key]['value']; + elseif (isset($this->defaults[$key])) + return $this->defaults[$key]; + + return $default; + } + + function exists($key) { + return $this->get($key, null) ? true : false; + } + + function set($key, $value) { + return ($this->update($key, $value)) ? $value : null; + } + + function persist($key, $value) { + $this->session[$key] = $value; + return true; + } + + function lastModified($key) { + if (isset($this->config[$key])) + return $this->config[$key]['updated']; else - $this->config['tz_offset'] = 0; + return false; + } + function create($key, $value) { + $sql = 'INSERT INTO '.$this->table + .' SET `'.$this->section_column.'`='.db_input($this->section) + .', `key`='.db_input($key) + .', value='.db_input($value); + if (!db_query($sql) || !($id=db_insert_id())) + return false; + + $this->config[$key] = array('key'=>$key, 'value'=>$value, 'id'=>$id); return true; } - function reload() { - if(!$this->load($this->getId())) + function update($key, $value) { + if (!isset($this->config[$key])) + return $this->create($key, $value); + + $setting = &$this->config[$key]; + if ($setting['value'] == $value) + return true; + + if (!db_query('UPDATE '.$this->table.' SET updated=NOW(), value=' + .db_input($value).' WHERE id='.db_input($setting['id']))) return false; + $setting['value'] = $value; + return true; + } + + function updateAll($updates) { + foreach ($updates as $key=>$value) + if (!$this->update($key, $value)) + return false; + return true; + } +} + +class OsticketConfig extends Config { + var $table = CONFIG_TABLE; + var $section = 'core'; + + var $defaultDept; //Default Department + var $defaultSLA; //Default SLA + var $defaultEmail; //Default Email + var $alertEmail; //Alert Email + var $defaultSMTPEmail; //Default SMTP Email + + var $defaults = array( + 'allow_pw_reset' => true, + 'pw_reset_window' => 30, + ); + + function OsticketConfig($section=null) { + parent::Config($section); + + if (count($this->config) == 0) { + // Fallback for osticket < 1.7@852ca89e + $sql='SELECT * FROM '.$this->table.' WHERE id = 1'; + if (($res=db_query($sql)) && db_num_rows($res)) + foreach (db_fetch_array($res) as $key=>$value) + $this->config[$key] = array('value'=>$value); + } + + //Get the default time zone + // We can't JOIN timezone table above due to upgrade support. + if ($this->get('default_timezone_id')) { + if (!$this->exists('tz_offset')) + $this->persist('tz_offset', + Timezone::getOffsetById($this->get('default_timezone_id'))); + } else + // Previous osTicket versions saved the offset value instead of + // a timezone instance. This is compatibility for the upgrader + $this->persist('tz_offset', 0); + return true; } @@ -73,75 +177,80 @@ class Config { } function isOnline() { - return ($this->config['isonline']); + return ($this->get('isonline')); } function isKnowledgebaseEnabled() { require_once(INCLUDE_DIR.'class.faq.php'); - return ($this->config['enable_kb'] && FAQ::countPublishedFAQs()); + return ($this->get('enable_kb') && FAQ::countPublishedFAQs()); } function getVersion() { return THIS_VERSION; } - //Used to detect version prior to 1.7 (useful during upgrade) - function getDBVersion() { - return $this->config['ostversion']; - } + function getSchemaSignature($section=null) { - function getSchemaSignature() { + if ((!$section || $section == $this->section) + && ($v=$this->get('schema_signature'))) + return $v; - if($this->config['schema_signature']) - return $this->config['schema_signature']; + // 1.7 after namespaced configuration, other namespace + if ($section) { + $sql='SELECT value FROM '.$this->table + .' WHERE `key` = "schema_signature" and namespace='.db_input($section); + if (($res=db_query($sql, false)) && db_num_rows($res)) + return db_result($res); + } - if($this->config['ostversion']) //old version 1.6 RC[1-5]-ST - return md5(strtoupper(trim($this->config['ostversion']))); + // 1.7 before namespaced configuration + $sql='SELECT `schema_signature` FROM '.$this->table + .' WHERE id=1'; + if (($res=db_query($sql, false)) && db_num_rows($res)) + return db_result($res); - return null; + // old version 1.6 + return md5(self::getDBVersion()); } function getDBTZoffset() { - return $this->config['db_tz_offset']; + if (!$this->exists('db_tz_offset')) { + $sql='SELECT (TIME_TO_SEC(TIMEDIFF(NOW(), UTC_TIMESTAMP()))/3600) as db_tz_offset'; + if(($res=db_query($sql)) && db_num_rows($res)) + $this->persist('db_tz_offset', db_result($res)); + } + return $this->get('db_tz_offset'); } /* Date & Time Formats */ function observeDaylightSaving() { - return ($this->config['enable_daylight_saving']); + return ($this->get('enable_daylight_saving')); } function getTimeFormat() { - return $this->config['time_format']; + return $this->get('time_format'); } function getDateFormat() { - return $this->config['date_format']; + return $this->get('date_format'); } function getDateTimeFormat() { - return $this->config['datetime_format']; + return $this->get('datetime_format'); } function getDayDateTimeFormat() { - return $this->config['daydatetime_format']; - } - - function getId() { - return $this->id; - } - - function getConfigId() { - return $this->getId(); + return $this->get('daydatetime_format'); } function getConfigInfo() { - return $this->config; + return $this->getInfo(); } function getTitle() { - return $this->config['helpdesk_title']; + return $this->get('helpdesk_title'); } function getUrl() { - return $this->config['helpdesk_url']; + return $this->get('helpdesk_url'); } function getBaseUrl() { //Same as above with no trailing slash. @@ -149,27 +258,27 @@ class Config { } function getTZOffset() { - return $this->config['tz_offset']; + return $this->get('tz_offset'); } function getPageSize() { - return $this->config['max_page_size']; + return $this->get('max_page_size'); } function getGracePeriod() { - return $this->config['overdue_grace_period']; + return $this->get('overdue_grace_period'); } function getPasswdResetPeriod() { - return $this->config['passwd_reset_period']; + return $this->get('passwd_reset_period'); } function showRelatedTickets() { - return $this->config['show_related_tickets']; + return $this->get('show_related_tickets'); } function showNotesInline(){ - return $this->config['show_notes_inline']; + return $this->get('show_notes_inline'); } function getClientTimeout() { @@ -177,15 +286,15 @@ class Config { } function getClientSessionTimeout() { - return $this->config['client_session_timeout']*60; + return $this->get('client_session_timeout')*60; } function getClientLoginTimeout() { - return $this->config['client_login_timeout']*60; + return $this->get('client_login_timeout')*60; } function getClientMaxLogins() { - return $this->config['client_max_logins']; + return $this->get('client_max_logins'); } function getStaffTimeout() { @@ -193,23 +302,23 @@ class Config { } function getStaffSessionTimeout() { - return $this->config['staff_session_timeout']*60; + return $this->get('staff_session_timeout')*60; } function getStaffLoginTimeout() { - return $this->config['staff_login_timeout']*60; + return $this->get('staff_login_timeout')*60; } function getStaffMaxLogins() { - return $this->config['staff_max_logins']; + return $this->get('staff_max_logins'); } function getLockTime() { - return $this->config['autolock_minutes']; + return $this->get('autolock_minutes'); } function getDefaultDeptId() { - return $this->config['default_dept_id']; + return $this->get('default_dept_id'); } function getDefaultDept() { @@ -221,7 +330,7 @@ class Config { } function getDefaultEmailId() { - return $this->config['default_email_id']; + return $this->get('default_email_id'); } function getDefaultEmail() { @@ -238,7 +347,7 @@ class Config { } function getDefaultSLAId() { - return $this->config['default_sla_id']; + return $this->get('default_sla_id'); } function getDefaultSLA() { @@ -250,111 +359,176 @@ class Config { } function getAlertEmailId() { - return $this->config['alert_email_id']; + return $this->get('alert_email_id'); } function getAlertEmail() { - if(!$this->alertEmail && $this->config['alert_email_id']) - $this->alertEmail= new Email($this->config['alert_email_id']); + if(!$this->alertEmail && $this->get('alert_email_id')) + $this->alertEmail= new Email($this->get('alert_email_id')); return $this->alertEmail; } function getDefaultSMTPEmail() { - if(!$this->defaultSMTPEmail && $this->config['default_smtp_id']) - $this->defaultSMTPEmail= new Email($this->config['default_smtp_id']); + if(!$this->defaultSMTPEmail && $this->get('default_smtp_id')) + $this->defaultSMTPEmail= new Email($this->get('default_smtp_id')); return $this->defaultSMTPEmail; } - function allowSMTPSpoofing() { - return $this->config['spoof_default_smtp']; - } - function getDefaultPriorityId() { - return $this->config['default_priority_id']; + return $this->get('default_priority_id'); } function getDefaultTemplateId() { - return $this->config['default_template_id']; + return $this->get('default_template_id'); } function getDefaultTemplate() { if(!$this->defaultTemplate && $this->getDefaultTemplateId()) - $this->defaultTemplate = Template::lookup($this->getDefaultTemplateId()); + $this->defaultTemplate = EmailTemplateGroup::lookup($this->getDefaultTemplateId()); return $this->defaultTemplate; } + function getLandingPageId() { + return $this->get('landing_page_id'); + } + + function getLandingPage() { + + if(!$this->landing_page && $this->getLandingPageId()) + $this->landing_page = Page::lookup($this->getLandingPageId()); + + return $this->landing_page; + } + + function getOfflinePageId() { + return $this->get('offline_page_id'); + } + + function getOfflinePage() { + + if(!$this->offline_page && $this->getOfflinePageId()) + $this->offline_page = Page::lookup($this->getOfflinePageId()); + + return $this->offline_page; + } + + function getThankYouPageId() { + return $this->get('thank-you_page_id'); + } + + function getThankYouPage() { + + if(!$this->thankyou_page && $this->getThankYouPageId()) + $this->thankyou_page = Page::lookup($this->getThankYouPageId()); + + return $this->thankyou_page; + } + + function getDefaultPages() { + /* Array of ids...as opposed to objects */ + return array( + $this->getLandingPageId(), + $this->getOfflinePageId(), + $this->getThankYouPageId(), + ); + } + function getMaxOpenTickets() { - return $this->config['max_open_tickets']; + return $this->get('max_open_tickets'); } function getMaxFileSize() { - return $this->config['max_file_size']; + return $this->get('max_file_size'); } function getStaffMaxFileUploads() { - return $this->config['max_staff_file_uploads']; + return $this->get('max_staff_file_uploads'); } function getClientMaxFileUploads() { //TODO: change max_user_file_uploads to max_client_file_uploads - return $this->config['max_user_file_uploads']; + return $this->get('max_user_file_uploads'); } function getLogLevel() { - return $this->config['log_level']; + return $this->get('log_level'); } function getLogGracePeriod() { - return $this->config['log_graceperiod']; + return $this->get('log_graceperiod'); } function logTicketActivity() { - return $this->config['log_ticket_activity']; + return $this->get('log_ticket_activity'); } function clickableURLS() { - return ($this->config['clickable_urls']); + return ($this->get('clickable_urls')); } function enableStaffIPBinding() { - return ($this->config['staff_ip_binding']); + return ($this->get('staff_ip_binding')); + } + + /** + * Configuration: allow_pw_reset + * + * TRUE if the <a>Forgot my password</a> link and system should be + * enabled, and FALSE otherwise. + */ + function allowPasswordReset() { + return $this->get('allow_pw_reset'); + } + + /** + * Configuration: pw_reset_window + * + * Number of minutes for which the password reset token is valid. + * + * Returns: Number of seconds the password reset token is valid. The + * number of minutes from the database is automatically converted + * to seconds here. + */ + function getPwResetWindow() { + // pw_reset_window is stored in minutes. Return value in seconds + return $this->get('pw_reset_window') * 60; } function isCaptchaEnabled() { - return (extension_loaded('gd') && function_exists('gd_info') && $this->config['enable_captcha']); + return (extension_loaded('gd') && function_exists('gd_info') && $this->get('enable_captcha')); } function isAutoCronEnabled() { - return ($this->config['enable_auto_cron']); + return ($this->get('enable_auto_cron')); } function isEmailPollingEnabled() { - return ($this->config['enable_mail_polling']); + return ($this->get('enable_mail_polling')); } function allowPriorityChange() { - return ($this->config['allow_priority_change']); + return ($this->get('allow_priority_change')); } function useEmailPriority() { - return ($this->config['use_email_priority']); + return ($this->get('use_email_priority')); } function getAdminEmail() { - return $this->config['admin_email']; + return $this->get('admin_email'); } function getReplySeparator() { - return $this->config['reply_separator']; + return $this->get('reply_separator'); } function stripQuotedReply() { - return ($this->config['strip_quoted_reply']); + return ($this->get('strip_quoted_reply')); } function saveEmailHeaders() { @@ -362,176 +536,176 @@ class Config { } function useRandomIds() { - return ($this->config['random_ticket_ids']); + return ($this->get('random_ticket_ids')); } /* autoresponders & Alerts */ function autoRespONNewTicket() { - return ($this->config['ticket_autoresponder']); + return ($this->get('ticket_autoresponder')); } function autoRespONNewMessage() { - return ($this->config['message_autoresponder']); + return ($this->get('message_autoresponder')); } function notifyONNewStaffTicket() { - return ($this->config['ticket_notice_active']); + return ($this->get('ticket_notice_active')); } function alertONNewMessage() { - return ($this->config['message_alert_active']); + return ($this->get('message_alert_active')); } function alertLastRespondentONNewMessage() { - return ($this->config['message_alert_laststaff']); + return ($this->get('message_alert_laststaff')); } function alertAssignedONNewMessage() { - return ($this->config['message_alert_assigned']); + return ($this->get('message_alert_assigned')); } function alertDeptManagerONNewMessage() { - return ($this->config['message_alert_dept_manager']); + return ($this->get('message_alert_dept_manager')); } function alertONNewNote() { - return ($this->config['note_alert_active']); + return ($this->get('note_alert_active')); } function alertLastRespondentONNewNote() { - return ($this->config['note_alert_laststaff']); + return ($this->get('note_alert_laststaff')); } function alertAssignedONNewNote() { - return ($this->config['note_alert_assigned']); + return ($this->get('note_alert_assigned')); } function alertDeptManagerONNewNote() { - return ($this->config['note_alert_dept_manager']); + return ($this->get('note_alert_dept_manager')); } function alertONNewTicket() { - return ($this->config['ticket_alert_active']); + return ($this->get('ticket_alert_active')); } function alertAdminONNewTicket() { - return ($this->config['ticket_alert_admin']); + return ($this->get('ticket_alert_admin')); } function alertDeptManagerONNewTicket() { - return ($this->config['ticket_alert_dept_manager']); + return ($this->get('ticket_alert_dept_manager')); } function alertDeptMembersONNewTicket() { - return ($this->config['ticket_alert_dept_members']); + return ($this->get('ticket_alert_dept_members')); } function alertONTransfer() { - return ($this->config['transfer_alert_active']); + return ($this->get('transfer_alert_active')); } function alertAssignedONTransfer() { - return ($this->config['transfer_alert_assigned']); + return ($this->get('transfer_alert_assigned')); } function alertDeptManagerONTransfer() { - return ($this->config['transfer_alert_dept_manager']); + return ($this->get('transfer_alert_dept_manager')); } function alertDeptMembersONTransfer() { - return ($this->config['transfer_alert_dept_members']); + return ($this->get('transfer_alert_dept_members')); } function alertONAssignment() { - return ($this->config['assigned_alert_active']); + return ($this->get('assigned_alert_active')); } function alertStaffONAssignment() { - return ($this->config['assigned_alert_staff']); + return ($this->get('assigned_alert_staff')); } function alertTeamLeadONAssignment() { - return ($this->config['assigned_alert_team_lead']); + return ($this->get('assigned_alert_team_lead')); } function alertTeamMembersONAssignment() { - return ($this->config['assigned_alert_team_members']); + return ($this->get('assigned_alert_team_members')); } function alertONOverdueTicket() { - return ($this->config['overdue_alert_active']); + return ($this->get('overdue_alert_active')); } function alertAssignedONOverdueTicket() { - return ($this->config['overdue_alert_assigned']); + return ($this->get('overdue_alert_assigned')); } function alertDeptManagerONOverdueTicket() { - return ($this->config['overdue_alert_dept_manager']); + return ($this->get('overdue_alert_dept_manager')); } function alertDeptMembersONOverdueTicket() { - return ($this->config['overdue_alert_dept_members']); + return ($this->get('overdue_alert_dept_members')); } function autoAssignReopenedTickets() { - return ($this->config['auto_assign_reopened_tickets']); + return ($this->get('auto_assign_reopened_tickets')); } function showAssignedTickets() { - return ($this->config['show_assigned_tickets']); + return ($this->get('show_assigned_tickets')); } function showAnsweredTickets() { - return ($this->config['show_answered_tickets']); + return ($this->get('show_answered_tickets')); } function hideStaffName() { - return ($this->config['hide_staff_name']); + return ($this->get('hide_staff_name')); } function sendOverLimitNotice() { - return ($this->config['overlimit_notice_active']); + return ($this->get('overlimit_notice_active')); } /* Error alerts sent to admin email when enabled */ function alertONSQLError() { - return ($this->config['send_sql_errors']); + return ($this->get('send_sql_errors')); } function alertONLoginError() { - return ($this->config['send_login_errors']); + return ($this->get('send_login_errors')); } function alertONMailParseError() { - return ($this->config['send_mailparse_errors']); + return ($this->get('send_mailparse_errors')); } /* Attachments */ function getAllowedFileTypes() { - return trim($this->config['allowed_filetypes']); + return trim($this->get('allowed_filetypes')); } function emailAttachments() { - return ($this->config['email_attachments']); + return ($this->get('email_attachments')); } function allowAttachments() { - return ($this->config['allow_attachments']); + return ($this->get('allow_attachments')); } function allowOnlineAttachments() { - return ($this->allowAttachments() && $this->config['allow_online_attachments']); + return ($this->allowAttachments() && $this->get('allow_online_attachments')); } function allowAttachmentsOnlogin() { - return ($this->allowOnlineAttachments() && $this->config['allow_online_attachments_onlogin']); + return ($this->allowOnlineAttachments() && $this->get('allow_online_attachments_onlogin')); } function allowEmailAttachments() { - return ($this->allowAttachments() && $this->config['allow_email_attachments']); + return ($this->allowAttachments() && $this->get('allow_email_attachments')); } //TODO: change db field to allow_api_attachments - which will include email/json/xml attachments @@ -542,7 +716,7 @@ class Config { /* Needed by upgrader on 1.6 and older releases upgrade - not not remove */ function getUploadDir() { - return $this->config['upload_dir']; + return $this->get('upload_dir'); } function updateSettings($vars, &$errors) { @@ -560,8 +734,8 @@ class Config { case 'emails': return $this->updateEmailsSettings($vars, $errors); break; - case 'attachments': - return $this->updateAttachmentsSetting($vars,$errors); + case 'pages': + return $this->updatePagesSettings($vars, $errors); break; case 'autoresp': return $this->updateAutoresponderSettings($vars, $errors); @@ -594,37 +768,39 @@ class Config { $f['datetime_format']=array('type'=>'string', 'required'=>1, 'error'=>'Datetime format required'); $f['daydatetime_format']=array('type'=>'string', 'required'=>1, 'error'=>'Day, Datetime format required'); $f['default_timezone_id']=array('type'=>'int', 'required'=>1, 'error'=>'Default Timezone required'); + $f['pw_reset_window']=array('type'=>'int', 'required'=>1, 'min'=>1, + 'error'=>'Valid password reset window required'); if(!Validator::process($f, $vars, $errors) || $errors) return false; - $sql='UPDATE '.CONFIG_TABLE.' SET updated=NOW() ' - .',isonline='.db_input($vars['isonline']) - .',helpdesk_title='.db_input($vars['helpdesk_title']) - .',helpdesk_url='.db_input($vars['helpdesk_url']) - .',default_dept_id='.db_input($vars['default_dept_id']) - .',default_template_id='.db_input($vars['default_template_id']) - .',max_page_size='.db_input($vars['max_page_size']) - .',log_level='.db_input($vars['log_level']) - .',log_graceperiod='.db_input($vars['log_graceperiod']) - .',passwd_reset_period='.db_input($vars['passwd_reset_period']) - .',staff_max_logins='.db_input($vars['staff_max_logins']) - .',staff_login_timeout='.db_input($vars['staff_login_timeout']) - .',staff_session_timeout='.db_input($vars['staff_session_timeout']) - .',staff_ip_binding='.db_input(isset($vars['staff_ip_binding'])?1:0) - .',client_max_logins='.db_input($vars['client_max_logins']) - .',client_login_timeout='.db_input($vars['client_login_timeout']) - .',client_session_timeout='.db_input($vars['client_session_timeout']) - .',time_format='.db_input($vars['time_format']) - .',date_format='.db_input($vars['date_format']) - .',datetime_format='.db_input($vars['datetime_format']) - .',daydatetime_format='.db_input($vars['daydatetime_format']) - .',default_timezone_id='.db_input($vars['default_timezone_id']) - .',enable_daylight_saving='.db_input(isset($vars['enable_daylight_saving'])?1:0) - .' WHERE id='.db_input($this->getId()); - - return (db_query($sql)); + return $this->updateAll(array( + 'isonline'=>$vars['isonline'], + 'helpdesk_title'=>$vars['helpdesk_title'], + 'helpdesk_url'=>$vars['helpdesk_url'], + 'default_dept_id'=>$vars['default_dept_id'], + 'default_template_id'=>$vars['default_template_id'], + 'max_page_size'=>$vars['max_page_size'], + 'log_level'=>$vars['log_level'], + 'log_graceperiod'=>$vars['log_graceperiod'], + 'passwd_reset_period'=>$vars['passwd_reset_period'], + 'staff_max_logins'=>$vars['staff_max_logins'], + 'staff_login_timeout'=>$vars['staff_login_timeout'], + 'staff_session_timeout'=>$vars['staff_session_timeout'], + 'staff_ip_binding'=>isset($vars['staff_ip_binding'])?1:0, + 'client_max_logins'=>$vars['client_max_logins'], + 'client_login_timeout'=>$vars['client_login_timeout'], + 'client_session_timeout'=>$vars['client_session_timeout'], + 'allow_pw_reset'=>isset($vars['allow_pw_reset'])?1:0, + 'pw_reset_window'=>$vars['pw_reset_window'], + 'time_format'=>$vars['time_format'], + 'date_format'=>$vars['date_format'], + 'datetime_format'=>$vars['datetime_format'], + 'daydatetime_format'=>$vars['daydatetime_format'], + 'default_timezone_id'=>$vars['default_timezone_id'], + 'enable_daylight_saving'=>isset($vars['enable_daylight_saving'])?1:0, + )); } function updateTicketsSettings($vars, &$errors) { @@ -670,35 +846,33 @@ class Config { if(!Validator::process($f, $vars, $errors) || $errors) return false; - $sql='UPDATE '.CONFIG_TABLE.' SET updated=NOW() ' - .',random_ticket_ids='.db_input($vars['random_ticket_ids']) - .',default_priority_id='.db_input($vars['default_priority_id']) - .',default_sla_id='.db_input($vars['default_sla_id']) - .',max_open_tickets='.db_input($vars['max_open_tickets']) - .',autolock_minutes='.db_input($vars['autolock_minutes']) - .',allow_priority_change='.db_input(isset($vars['allow_priority_change'])?1:0) - .',use_email_priority='.db_input(isset($vars['use_email_priority'])?1:0) - .',enable_captcha='.db_input(isset($vars['enable_captcha'])?1:0) - .',log_ticket_activity='.db_input(isset($vars['log_ticket_activity'])?1:0) - .',auto_assign_reopened_tickets='.db_input(isset($vars['auto_assign_reopened_tickets'])?1:0) - .',show_assigned_tickets='.db_input(isset($vars['show_assigned_tickets'])?1:0) - .',show_answered_tickets='.db_input(isset($vars['show_answered_tickets'])?1:0) - .',show_related_tickets='.db_input(isset($vars['show_related_tickets'])?1:0) - .',show_notes_inline='.db_input(isset($vars['show_notes_inline'])?1:0) - .',clickable_urls='.db_input(isset($vars['clickable_urls'])?1:0) - .',hide_staff_name='.db_input(isset($vars['hide_staff_name'])?1:0) - .',allow_attachments='.db_input(isset($vars['allow_attachments'])?1:0) - .',allowed_filetypes='.db_input(strtolower(preg_replace("/\n\r|\r\n|\n|\r/", '',trim($vars['allowed_filetypes'])))) - .',max_file_size='.db_input($vars['max_file_size']) - .',max_user_file_uploads='.db_input($vars['max_user_file_uploads']) - .',max_staff_file_uploads='.db_input($vars['max_staff_file_uploads']) - .',email_attachments='.db_input(isset($vars['email_attachments'])?1:0) - .',allow_email_attachments='.db_input(isset($vars['allow_email_attachments'])?1:0) - .',allow_online_attachments='.db_input(isset($vars['allow_online_attachments'])?1:0) - .',allow_online_attachments_onlogin='.db_input(isset($vars['allow_online_attachments_onlogin'])?1:0) - .' WHERE id='.db_input($this->getId()); - - return (db_query($sql)); + return $this->updateAll(array( + 'random_ticket_ids'=>$vars['random_ticket_ids'], + 'default_priority_id'=>$vars['default_priority_id'], + 'default_sla_id'=>$vars['default_sla_id'], + 'max_open_tickets'=>$vars['max_open_tickets'], + 'autolock_minutes'=>$vars['autolock_minutes'], + 'allow_priority_change'=>isset($vars['allow_priority_change'])?1:0, + 'use_email_priority'=>isset($vars['use_email_priority'])?1:0, + 'enable_captcha'=>isset($vars['enable_captcha'])?1:0, + 'log_ticket_activity'=>isset($vars['log_ticket_activity'])?1:0, + 'auto_assign_reopened_tickets'=>isset($vars['auto_assign_reopened_tickets'])?1:0, + 'show_assigned_tickets'=>isset($vars['show_assigned_tickets'])?1:0, + 'show_answered_tickets'=>isset($vars['show_answered_tickets'])?1:0, + 'show_related_tickets'=>isset($vars['show_related_tickets'])?1:0, + 'show_notes_inline'=>isset($vars['show_notes_inline'])?1:0, + 'clickable_urls'=>isset($vars['clickable_urls'])?1:0, + 'hide_staff_name'=>isset($vars['hide_staff_name'])?1:0, + 'allow_attachments'=>isset($vars['allow_attachments'])?1:0, + 'allowed_filetypes'=>strtolower(preg_replace("/\n\r|\r\n|\n|\r/", '',trim($vars['allowed_filetypes']))), + 'max_file_size'=>$vars['max_file_size'], + 'max_user_file_uploads'=>$vars['max_user_file_uploads'], + 'max_staff_file_uploads'=>$vars['max_staff_file_uploads'], + 'email_attachments'=>isset($vars['email_attachments'])?1:0, + 'allow_email_attachments'=>isset($vars['allow_email_attachments'])?1:0, + 'allow_online_attachments'=>isset($vars['allow_online_attachments'])?1:0, + 'allow_online_attachments_onlogin'=>isset($vars['allow_online_attachments_onlogin'])?1:0, + )); } @@ -718,75 +892,79 @@ class Config { if(!Validator::process($f,$vars,$errors) || $errors) return false; - $sql='UPDATE '.CONFIG_TABLE.' SET updated=NOW() ' - .',default_email_id='.db_input($vars['default_email_id']) - .',alert_email_id='.db_input($vars['alert_email_id']) - .',default_smtp_id='.db_input($vars['default_smtp_id']) - .',admin_email='.db_input($vars['admin_email']) - .',enable_auto_cron='.db_input(isset($vars['enable_auto_cron'])?1:0) - .',enable_mail_polling='.db_input(isset($vars['enable_mail_polling'])?1:0) - .',strip_quoted_reply='.db_input(isset($vars['strip_quoted_reply'])?1:0) - .',reply_separator='.db_input($vars['reply_separator']) - .' WHERE id='.db_input($this->getId()); - - - - return (db_query($sql)); + return $this->updateAll(array( + 'default_email_id'=>$vars['default_email_id'], + 'alert_email_id'=>$vars['alert_email_id'], + 'default_smtp_id'=>$vars['default_smtp_id'], + 'admin_email'=>$vars['admin_email'], + 'enable_auto_cron'=>isset($vars['enable_auto_cron'])?1:0, + 'enable_mail_polling'=>isset($vars['enable_mail_polling'])?1:0, + 'strip_quoted_reply'=>isset($vars['strip_quoted_reply'])?1:0, + 'reply_separator'=>$vars['reply_separator'], + )); } - function updateAttachmentsSetting($vars,&$errors) { - - - if($vars['allow_attachments']) { - - if(!ini_get('file_uploads')) - $errors['err']='The \'file_uploads\' directive is disabled in php.ini'; - - if(!is_numeric($vars['max_file_size'])) - $errors['max_file_size']='Maximum file size required'; - - if(!$vars['allowed_filetypes']) - $errors['allowed_filetypes']='Allowed file extentions required'; - - if(!($maxfileuploads=ini_get('max_file_uploads'))) - $maxfileuploads=DEFAULT_MAX_FILE_UPLOADS; + function getLogo($site) { + $id = $this->get("{$site}_logo_id", false); + return ($id) ? AttachmentFile::lookup($id) : null; + } + function getClientLogo() { + return $this->getLogo('client'); + } + function getLogoId($site) { + return $this->get("{$site}_logo_id", false); + } + function getClientLogoId() { + return $this->getLogoId('client'); + } - if(!$vars['max_user_file_uploads'] || $vars['max_user_file_uploads']>$maxfileuploads) - $errors['max_user_file_uploads']='Invalid selection. Must be less than '.$maxfileuploads; + function updatePagesSettings($vars, &$errors) { - if(!$vars['max_staff_file_uploads'] || $vars['max_staff_file_uploads']>$maxfileuploads) - $errors['max_staff_file_uploads']='Invalid selection. Must be less than '.$maxfileuploads; + $f=array(); + $f['landing_page_id'] = array('type'=>'int', 'required'=>1, 'error'=>'required'); + $f['offline_page_id'] = array('type'=>'int', 'required'=>1, 'error'=>'required'); + $f['thank-you_page_id'] = array('type'=>'int', 'required'=>1, 'error'=>'required'); + + if ($_FILES['logo']) { + $error = false; + list($logo) = AttachmentFile::format($_FILES['logo']); + if (!$logo) + ; // Pass + elseif ($logo['error']) + $errors['logo'] = $logo['error']; + elseif (!($id = AttachmentFile::uploadLogo($logo, $error))) + $errors['logo'] = 'Unable to upload logo image. '.$error; } - if($errors) return false; + if(!Validator::process($f, $vars, $errors) || $errors) + return false; - $sql= 'UPDATE '.CONFIG_TABLE.' SET updated=NOW() ' - .',allow_attachments='.db_input(isset($vars['allow_attachments'])?1:0) - .',allowed_filetypes='.db_input(strtolower(preg_replace("/\n\r|\r\n|\n|\r/", '',trim($vars['allowed_filetypes'])))) - .',max_file_size='.db_input($vars['max_file_size']) - .',max_user_file_uploads='.db_input($vars['max_user_file_uploads']) - .',max_staff_file_uploads='.db_input($vars['max_staff_file_uploads']) - .',email_attachments='.db_input(isset($vars['email_attachments'])?1:0) - .',allow_email_attachments='.db_input(isset($vars['allow_email_attachments'])?1:0) - .',allow_online_attachments='.db_input(isset($vars['allow_online_attachments'])?1:0) - .',allow_online_attachments_onlogin='.db_input(isset($vars['allow_online_attachments_onlogin'])?1:0) - .' WHERE id='.db_input($this->getId()); + if (isset($vars['delete-logo'])) + foreach ($vars['delete-logo'] as $id) + if (($vars['selected-logo'] != $id) + && ($f = AttachmentFile::lookup($id))) + $f->delete(); - return (db_query($sql)); + return $this->updateAll(array( + 'landing_page_id' => $vars['landing_page_id'], + 'offline_page_id' => $vars['offline_page_id'], + 'thank-you_page_id' => $vars['thank-you_page_id'], + 'client_logo_id' => ( + (is_numeric($vars['selected-logo']) && $vars['selected-logo']) + ? $vars['selected-logo'] : false), + )); } function updateAutoresponderSettings($vars, &$errors) { if($errors) return false; - $sql ='UPDATE '.CONFIG_TABLE.' SET updated=NOW() ' - .',ticket_autoresponder='.db_input($vars['ticket_autoresponder']) - .',message_autoresponder='.db_input($vars['message_autoresponder']) - .',ticket_notice_active='.db_input($vars['ticket_notice_active']) - .',overlimit_notice_active='.db_input($vars['overlimit_notice_active']) - .' WHERE id='.db_input($this->getId()); - - return (db_query($sql)); + return $this->updateAll(array( + 'ticket_autoresponder'=>$vars['ticket_autoresponder'], + 'message_autoresponder'=>$vars['message_autoresponder'], + 'ticket_notice_active'=>$vars['ticket_notice_active'], + 'overlimit_notice_active'=>$vars['overlimit_notice_active'], + )); } @@ -794,12 +972,10 @@ class Config { if($errors) return false; - $sql = 'UPDATE '.CONFIG_TABLE.' SET updated=NOW() ' - .',enable_kb='.db_input(isset($vars['enable_kb'])?1:0) - .',enable_premade='.db_input(isset($vars['enable_premade'])?1:0) - .' WHERE id='.db_input($this->getId()); - - return (db_query($sql)); + return $this->updateAll(array( + 'enable_kb'=>isset($vars['enable_kb'])?1:0, + 'enable_premade'=>isset($vars['enable_premade'])?1:0, + )); } @@ -849,43 +1025,42 @@ class Config { if($errors) return false; - $sql= 'UPDATE '.CONFIG_TABLE.' SET updated=NOW() ' - .',ticket_alert_active='.db_input($vars['ticket_alert_active']) - .',ticket_alert_admin='.db_input(isset($vars['ticket_alert_admin'])?1:0) - .',ticket_alert_dept_manager='.db_input(isset($vars['ticket_alert_dept_manager'])?1:0) - .',ticket_alert_dept_members='.db_input(isset($vars['ticket_alert_dept_members'])?1:0) - .',message_alert_active='.db_input($vars['message_alert_active']) - .',message_alert_laststaff='.db_input(isset($vars['message_alert_laststaff'])?1:0) - .',message_alert_assigned='.db_input(isset($vars['message_alert_assigned'])?1:0) - .',message_alert_dept_manager='.db_input(isset($vars['message_alert_dept_manager'])?1:0) - .',note_alert_active='.db_input($vars['note_alert_active']) - .',note_alert_laststaff='.db_input(isset($vars['note_alert_laststaff'])?1:0) - .',note_alert_assigned='.db_input(isset($vars['note_alert_assigned'])?1:0) - .',note_alert_dept_manager='.db_input(isset($vars['note_alert_dept_manager'])?1:0) - .',assigned_alert_active='.db_input($vars['assigned_alert_active']) - .',assigned_alert_staff='.db_input(isset($vars['assigned_alert_staff'])?1:0) - .',assigned_alert_team_lead='.db_input(isset($vars['assigned_alert_team_lead'])?1:0) - .',assigned_alert_team_members='.db_input(isset($vars['assigned_alert_team_members'])?1:0) - .',transfer_alert_active='.db_input($vars['transfer_alert_active']) - .',transfer_alert_assigned='.db_input(isset($vars['transfer_alert_assigned'])?1:0) - .',transfer_alert_dept_manager='.db_input(isset($vars['transfer_alert_dept_manager'])?1:0) - .',transfer_alert_dept_members='.db_input(isset($vars['transfer_alert_dept_members'])?1:0) - .',overdue_alert_active='.db_input($vars['overdue_alert_active']) - .',overdue_alert_assigned='.db_input(isset($vars['overdue_alert_assigned'])?1:0) - .',overdue_alert_dept_manager='.db_input(isset($vars['overdue_alert_dept_manager'])?1:0) - .',overdue_alert_dept_members='.db_input(isset($vars['overdue_alert_dept_members'])?1:0) - .',send_sys_errors='.db_input(isset($vars['send_sys_errors'])?1:0) - .',send_sql_errors='.db_input(isset($vars['send_sql_errors'])?1:0) - .',send_login_errors='.db_input(isset($vars['send_login_errors'])?1:0) - .' WHERE id='.db_input($this->getId()); - - return (db_query($sql)); - - } - - /** static **/ - function lookup($id) { - return ($id && ($cfg = new Config($id)) && $cfg->getId()==$id)?$cfg:null; + return $this->updateAll(array( + 'ticket_alert_active'=>$vars['ticket_alert_active'], + 'ticket_alert_admin'=>isset($vars['ticket_alert_admin'])?1:0, + 'ticket_alert_dept_manager'=>isset($vars['ticket_alert_dept_manager'])?1:0, + 'ticket_alert_dept_members'=>isset($vars['ticket_alert_dept_members'])?1:0, + 'message_alert_active'=>$vars['message_alert_active'], + 'message_alert_laststaff'=>isset($vars['message_alert_laststaff'])?1:0, + 'message_alert_assigned'=>isset($vars['message_alert_assigned'])?1:0, + 'message_alert_dept_manager'=>isset($vars['message_alert_dept_manager'])?1:0, + 'note_alert_active'=>$vars['note_alert_active'], + 'note_alert_laststaff'=>isset($vars['note_alert_laststaff'])?1:0, + 'note_alert_assigned'=>isset($vars['note_alert_assigned'])?1:0, + 'note_alert_dept_manager'=>isset($vars['note_alert_dept_manager'])?1:0, + 'assigned_alert_active'=>$vars['assigned_alert_active'], + 'assigned_alert_staff'=>isset($vars['assigned_alert_staff'])?1:0, + 'assigned_alert_team_lead'=>isset($vars['assigned_alert_team_lead'])?1:0, + 'assigned_alert_team_members'=>isset($vars['assigned_alert_team_members'])?1:0, + 'transfer_alert_active'=>$vars['transfer_alert_active'], + 'transfer_alert_assigned'=>isset($vars['transfer_alert_assigned'])?1:0, + 'transfer_alert_dept_manager'=>isset($vars['transfer_alert_dept_manager'])?1:0, + 'transfer_alert_dept_members'=>isset($vars['transfer_alert_dept_members'])?1:0, + 'overdue_alert_active'=>$vars['overdue_alert_active'], + 'overdue_alert_assigned'=>isset($vars['overdue_alert_assigned'])?1:0, + 'overdue_alert_dept_manager'=>isset($vars['overdue_alert_dept_manager'])?1:0, + 'overdue_alert_dept_members'=>isset($vars['overdue_alert_dept_members'])?1:0, + 'send_sys_errors'=>isset($vars['send_sys_errors'])?1:0, + 'send_sql_errors'=>isset($vars['send_sql_errors'])?1:0, + 'send_login_errors'=>isset($vars['send_login_errors'])?1:0, + )); + } + + //Used to detect version prior to 1.7 (useful during upgrade) + /* static */ function getDBVersion() { + $sql='SELECT `ostversion` FROM '.TABLE_PREFIX.'config ' + .'WHERE id=1'; + return db_result(db_query($sql)); } } ?> diff --git a/include/class.cron.php b/include/class.cron.php index 29265f8056a88d5b62c134e7cf96246fd5ebda0c..257926e253fe1662b0630548b62e483bffe20e48 100644 --- a/include/class.cron.php +++ b/include/class.cron.php @@ -41,6 +41,10 @@ class Cron { } function run(){ //called by outside cron NOT autocron + global $ost; + if (!$ost || $ost->isUpgradePending()) + return; + self::MailFetcher(); self::TicketMonitor(); self::PurgeLogs(); diff --git a/include/class.crypto.php b/include/class.crypto.php new file mode 100644 index 0000000000000000000000000000000000000000..389b38228e119fc1fa8f24ad93593886600be5f1 --- /dev/null +++ b/include/class.crypto.php @@ -0,0 +1,594 @@ +<?php +/********************************************************************* + class.crypto.php + + Crypto wrapper - provides encryption/decryption utility + + Peter Rotich <peter@osticket.com> + Jared Hancock <jared@osticket.com> + Copyright (c) 2006-2013 osTicket + http://www.osticket.com + + Credit: + *https://defuse.ca/secure-php-encryption.htm + *Interwebz + + Released under the GNU General Public License WITHOUT ANY WARRANTY. + See LICENSE.TXT for details. + + vim: expandtab sw=4 ts=4 sts=4: +**********************************************************************/ + +//Top level encryption options. +define('CRYPT_MCRYPT', 1); +define('CRYPT_OPENSSL', 2); +define('CRYPT_PHPSECLIB', 3); + +require_once PEAR_DIR.'Crypt/Hash.php'; +require_once PEAR_DIR.'Crypt/Random.php'; + +/** + * Class: Crypto + * + * Pluggable encryption/decryption utility which allows for automatic + * algorithm detection of encrypted data as well as automatic upgrading (on + * encrypt()) of existing data. + * + * The utility makes use of a subkey tecnhique where the master key used to + * encrypt and decrypt data is not used by itself the encrypt the data. + * Another key, called the subkey, is mixed with the master key to generate + * the key used to perform the encryption. This means that the same key is + * not used to encrypt all data stored in the database. + * + * The encryption process will select a library to perform the low-level + * encryption automatically based on the PHP extensions currently available. + * Therefore, the best encryption library, algorithm, and configuration will + * be used to perform the encryption. + */ +class Crypto { + + /** + * Encrypt data using two keys. The first key is considered a 'Master + * Key' which might be used to encrypt different kinds of data in your + * system. Using a subkey allows the master key to be reused in a way + * that two encrypted messages can be encrypted with the same master key + * but have different namespaces as it were. It allows various parts of + * the application to encrypt things using a common master key that are + * not recoverable by other parts of the application. + * + * Parameters: + * input - (string) text subject that is to be encrypted + * key - (string) master key used for the encryption + * skey - (string:optional) sub-key or namespace of the encryption + * context + * crypt - (int:optional) Cryto tag id used for the encryption. This + * is only really useful for testing. The crypto library will be + * automatically selected based on the available PHP extensions. + */ + function encrypt($input, $key, $skey='encryption', $crypt=null) { + + //Gets preffered crypto. + if(!($crypto = Crypto::get($crypt))) + return false; + + //Set master and subkeys + $crypto->setKeys($key, $skey); + + if(!($ciphertext=$crypto->encrypt($input))) + return false; + + return sprintf('$%d$%s', + $crypto->getTagNumber(), + base64_encode($ciphertext)); + } + + /** + * Decrypt data originally returned from ::encrypt() using the two keys + * that were originially passed into the ::encrypt() method. + * + * Parameters: + * ciphertext - (string<binary>) Unencoded data returned from the + * ::encrypt() method + * key - (string) master key used for the encryption originally + * skey - (string_ sub key or namespace used originally for the + * encryption + */ + function decrypt($ciphertext, $key, $skey='encryption') { + + if(!$key || !$ciphertext || $ciphertext[0] != '$') + return false; + + list(, $crypt, $ciphertext) = explode('$', $ciphertext, 3); + //Get crypto.... based on crypt tag. + if(!$crypt || !($crypto=Crypto::get($crypt)) || !$crypto->exists()) + return false; + + $crypto->setKeys($key, $skey); + + return $crypto->decrypt(base64_decode($ciphertext)); + } + + function get($crypt) { + + $cryptos = self::cryptos(); + if(!$cryptos || ($crypt && !isset($cryptos[$crypt]))) + return null; + + //Requested crypto available?? + if($crypt) return $cryptos[$crypt]; + + //cycle thru' available cryptos + foreach($cryptos as $crypto) + if(call_user_func(array($crypto, 'exists'))) + return $crypto; + + return null; + } + + /* + * Returns list of supported cryptos + * + */ + function cryptos() { + + static $cryptos = false; + + if ($cryptos === false) { + $cryptos = array(); + + if(defined('CRYPT_OPENSSL') && class_exists('CryptoOpenSSL')) + $cryptos[CRYPT_OPENSSL] = new CryptoOpenSSL(CRYPT_OPENSSL); + + if(defined('CRYPT_MCRYPT') && class_exists('CryptoMcrypt')) + $cryptos[CRYPT_MCRYPT] = new CryptoMcrypt(CRYPT_MCRYPT); + + if(defined('CRYPT_PHPSECLIB') && class_exists('CryptoPHPSecLib')) + $cryptos[CRYPT_PHPSECLIB] = new CryptoPHPSecLib(CRYPT_PHPSECLIB); + } + + return $cryptos; + } + + function hash($string, $key) { + $hash = new Crypt_Hash('sha512'); + $hash->setKey($key); + return $hash->hash($string); + } + + /* Generates random string of @len length */ + function randcode($len) { + return crypt_random_string($len); + } +} + +/** + * Class: CryptoAlgo + * + * Abstract cryptographic library implementation. This class is intended to + * be extended and implemented for a particular PHP extension, such as + * mcrypt, openssl, etc. + * + * The class implies but does not define abstract methods for encrypt() and + * decrypt() which will perform the respective operations on the text + * subjects using a specific library. + */ +/* abstract */ +class CryptoAlgo { + + var $master_key; + var $sub_key; + + var $tag_number; + + var $ciphers = null; + + function CryptoAlgo($tag) { + $this->tag_number = $tag; + } + + function getTagNumber() { + return $this->tag_number; + } + + function getCipher($cid, $callback=null) { + + if(!$this->ciphers) + return null; + + $cipher = null; + if($cid) + $cipher = isset($this->ciphers[$cid]) ? $this->ciphers[$cid] : null; + elseif($this->ciphers) { // search best available. + foreach($this->ciphers as $k => $c) { + if(!$callback + || (is_callable($callback) + && call_user_func($callback, $c))) { + $cid = $k; + $cipher = $c; + break; + } + } + } + + return $cipher ? + array_merge($cipher, array('cid' => $cid)) : null; + } + + function getMasterKey() { + return $this->master_key; + } + + function getSubKey() { + return $this->sub_key; + } + + function setKeys($master, $sub) { + $this->master_key = $master; + $this->sub_key = $sub; + } + + /** + * Function: getKeyHash + * + * Utility function to retrieve the encryption key used by the + * encryption algorithm based on the master key, the sub-key, and some + * known, random seed. The hash returned should be used as the binary key + * for the encryption algorithm. + * + * Parameters: + * seed - (string) third-level seed for the encryption key. This will + * likely an IV or salt value + * len - (int) length of the desired hash + */ + function getKeyHash($seed, $len=32) { + + $hash = Crypto::hash($this->getMasterKey().md5($this->getSubKey()), $seed); + return substr($hash, 0, $len); + } + + /** + * Determines if the library used to implement encryption is currently + * available and supported. This method is abstract and should be + * defined in extension classes. + */ + /* abstract */ + function exists() { return false; } + + +} + + +/** + * Class: CryptoMcrypt + * + * Mcrypt library encryption implementation. This allows for encrypting and + * decrypting text using the php_mcrypt extension. + * + * NOTE: Don't instanciate this class directly. Use Crypt::encrypt() and + * Crypt::decrypt() to encrypt data. + */ + +define('CRYPTO_CIPHER_MCRYPT_RIJNDAEL_128', 1); +if(!defined('MCRYPT_RIJNDAEL_128')): +define('MCRYPT_RIJNDAEL_128', ''); +endif; + +Class CryptoMcrypt extends CryptoAlgo { + + # WARNING: Change and you will lose your passwords ... + var $ciphers = array( + CRYPTO_CIPHER_MCRYPT_RIJNDAEL_128 => array( + 'name' => MCRYPT_RIJNDAEL_128, + 'mode' => 'cbc', + ), + ); + + function getCipher($cid=null) { + return parent::getCipher($cid, array($this, '_checkCipher')); + } + + function _checkCipher($c) { + + return ($c + && $c['name'] + && $c['mode'] + && $this->exists() + && mcrypt_module_open($c['name'], '', $c['mode'], '') + ); + } + + /** + * Encrypt clear-text data using the mycrpt library. Optionally, a + * configuration tag-id can be passed as the second parameter to specify + * the actual encryption algorithm to be used. + * + * Parameters: + * text - (string) clear text subject to be encrypted + * cid - (int) encryption configuration to be used. @see $this->ciphers + */ + function encrypt($text, $cid=0) { + + if(!$this->exists() + || !$text + || !($cipher=$this->getCipher($cid)) + || !$cipher['cid']) + return false; + + if(!($td = mcrypt_module_open($cipher['name'], '', $cipher['mode'], ''))) + return false; + + $keysize = mcrypt_enc_get_key_size($td); + $ivsize = mcrypt_enc_get_iv_size($td); + $iv = Crypto::randcode($ivsize); + + //Add padding + $blocksize = mcrypt_enc_get_block_size($td); + $pad = $blocksize - (strlen($text) % $blocksize); + $text .= str_repeat(chr($pad), $pad); + + // Do the encryption. + mcrypt_generic_init($td, $this->getKeyHash($iv, $ivsize), $iv); + $ciphertext = $iv . mcrypt_generic($td, $text); + mcrypt_generic_deinit($td); + mcrypt_module_close($td); + + return sprintf('$%s$%s', $cipher['cid'], $ciphertext); + } + + /** + * Recover text that was originally encrypted using this library with + * the ::encrypt() method. + * + * Parameters: + * text - (string<binary>) Unencoded, binary string which is the result + * of the ::encrypt() method. + */ + function decrypt($ciphertext) { + + if(!$this->exists() + || !$ciphertext + || $ciphertext[0] != '$' + ) + return false; + + list(, $cid, $ciphertext) = explode('$', $ciphertext, 3); + + if(!$cid + || !$ciphertext + || !($cipher=$this->getCipher($cid)) + || $cipher['cid']!=$cid) + return false; + + if(!($td = mcrypt_module_open($cipher['name'], '', $cipher['mode'], ''))) + return false; + + $keysize = mcrypt_enc_get_key_size($td); + $ivsize = mcrypt_enc_get_iv_size($td); + + $iv = substr($ciphertext, 0, $ivsize); + if (!($ciphertext = substr($ciphertext, $ivsize))) + return false; + + // Do the decryption. + mcrypt_generic_init($td, $this->getKeyHash($iv, $ivsize), $iv); + $plaintext = mdecrypt_generic($td, $ciphertext); + mcrypt_generic_deinit($td); + mcrypt_module_close($td); + + // Remove the padding. + $pad = ord($plaintext[strlen($plaintext) -1]); + $plaintext = substr($plaintext, 0, strlen($plaintext) - $pad); + + return $plaintext; + } + + function exists() { + return (extension_loaded('mcrypt') + && function_exists('mcrypt_module_open')); + } +} + + +/** + * Class: CryptoOpenSSL + * + * OpenSSL library encryption implementation. This allows for encrypting and + * decrypting text using the php_openssl extension. + * + * NOTE: Don't instanciate this class directly. Use Crypt::encrypt() and + * Crypt::decrypt() to encrypt data. + */ + +define('CRYPTO_CIPHER_OPENSSL_AES_128_CBC', 1); + +class CryptoOpenSSL extends CryptoAlgo { + + # WARNING: Change and you will lose your passwords ... + var $ciphers = array( + CRYPTO_CIPHER_OPENSSL_AES_128_CBC => array( + 'method' => 'aes-128-cbc', + ), + ); + + function getMethod($cid) { + + return (($cipher=$this->getCipher($cid))) + ? $cipher['method']: ''; + } + + function getCipher($cid) { + return parent::getCipher($cid, array($this, '_checkCipher')); + } + + function _checkCipher($c) { + + return ($c + && $c['method'] + && $this->exists() + && openssl_cipher_iv_length($c['method']) + ); + } + + /** + * Encrypt clear-text data using the openssl library. Optionally, a + * configuration tag-id can be passed as the second parameter to specify + * the actual encryption algorithm to be used. + * + * Parameters: + * text - (string) clear text subject to be encrypted + * cid - (int) encryption configuration to be used. @see $this->ciphers + */ + function encrypt($text, $cid=0) { + + if(!$this->exists() + || !$text + || !($cipher=$this->getCipher($cid))) + return false; + + $ivlen = openssl_cipher_iv_length($cipher['method']); + $iv = openssl_random_pseudo_bytes($ivlen); + $key = $this->getKeyHash($iv, $ivlen); + + $options = (defined('OPENSSL_RAW_DATA')) ? OPENSSL_RAW_DATA : true; + if(!($ciphertext = openssl_encrypt($text, $cipher['method'], $key, + $options, $iv))) + return false; + + return sprintf('$%s$%s%s', $cipher['cid'], $iv, $ciphertext); + } + + /** + * Decrypt and recover text originally encrypted using the ::encrypt() + * method of this class. + * + * Parameters: + * text - (string<binary>) Unencoded, binary string which is the result + * of the ::encrypt() method. + */ + function decrypt($ciphertext) { + + + if(!$this->exists() || !$ciphertext || $ciphertext[0] != '$') + return false; + + list(, $cid, $ciphertext) = explode('$', $ciphertext, 3); + + if(!$cid + || !$ciphertext + || !($cipher=$this->getCipher($cid))) + return false; + + $ivlen = openssl_cipher_iv_length($cipher['method']); + $iv = substr($ciphertext, 0, $ivlen); + $ciphertext = substr($ciphertext, $ivlen); + $key = $this->getKeyHash($iv, $ivlen); + + $options = (defined('OPENSSL_RAW_DATA')) ? OPENSSL_RAW_DATA : true; + $plaintext = openssl_decrypt($ciphertext, $cipher['method'], $key, + $options, $iv); + + return $plaintext; + } + + function exists() { + return (extension_loaded('openssl') && function_exists('openssl_cipher_iv_length')); + } +} + + +/** + * Class: CryptoPHPSecLib + * + * Pure PHP source library encryption implementation using phpseclib. This + * allows for encrypting and decrypting text when no compiled library is + * available for use. + * + * NOTE: Don't instanciate this class directly. Use Crypt::encrypt() and + * Crypt::decrypt() to encrypt data. + */ + +require_once PEAR_DIR.'Crypt/AES.php'; + +define('CRYPTO_CIPHER_PHPSECLIB_AES_CBC', 1); + +class CryptoPHPSecLib extends CryptoAlgo { + + var $ciphers = array( + CRYPTO_CIPHER_PHPSECLIB_AES_CBC => array( + 'mode' => CRYPT_AES_MODE_CBC, + 'ivlen' => 16, #WARNING: DO NOT CHANGE! + 'class' => 'Crypt_AES', + ), + ); + + + function getCrypto($cid) { + if(!$cid + || !($c=$this->getCipher($cid)) + || !$this->_checkCipher($c)) + return null; + + $class = $c['class']; + + return new $class($c['mode']); + } + + function getCipher($cid) { + return parent::getCipher($cid, array($this, '_checkCipher')); + } + + function _checkCipher($c) { + + return ($c + && $c['mode'] + && $c['ivlen'] + && $c['class'] + && class_exists($c['class'])); + } + + function encrypt($text, $cid=0) { + + if(!$this->exists() + || !$text + || !($cipher=$this->getCipher($cid)) + || !($crypto=$this->getCrypto($cipher['cid'])) + ) + return false; + + $ivlen = $cipher['ivlen']; + $iv = Crypto::randcode($ivlen); + $crypto->setKey($this->getKeyHash($iv, $ivlen)); + $crypto->setIV($iv); + + return sprintf('$%s$%s%s', $cipher['cid'], $iv, $crypto->encrypt($text)); + } + + function decrypt($ciphertext) { + + if(!$this->exists() || !$ciphertext || $ciphertext[0] != '$') + return false; + + list(, $cid, $ciphertext) = explode('$', $ciphertext, 3); + if(!$cid + || !$ciphertext + || !($cipher=$this->getCipher($cid)) + || !($crypto=$this->getCrypto($cipher['cid'])) + ) + return false; + + $ivlen = $cipher['ivlen']; + $iv = substr($ciphertext, 0, $ivlen); + if (!($ciphertext = substr($ciphertext, $ivlen))) + return false; + + $crypto->setKey($this->getKeyHash($iv, $ivlen)); + $crypto->setIV($iv); + + return $crypto->decrypt($ciphertext); + } + + function exists() { + return class_exists('Crypt_AES'); + } +} +?> diff --git a/include/class.csrf.php b/include/class.csrf.php index 806217284b4ca7f381d37496fb763462fedbe0d1..cdd04a29258c9c7daa4056b6a886e32a52ce096e 100644 --- a/include/class.csrf.php +++ b/include/class.csrf.php @@ -53,16 +53,11 @@ Class CSRF { return $this->name; } - function getToken($len=32) { + function getToken() { if(!$this->csrf['token'] || $this->isExpired()) { - $len = $len>8?$len:32; - $r = ''; - for ($i = 0; $i <= $len; $i++) - $r .= chr(mt_rand(0, 255)); - - $this->csrf['token'] = base64_encode(sha1(session_id().$r.SECRET_SALT)); + $this->csrf['token'] = sha1(session_id().Crypto::randcode(16).SECRET_SALT); $this->csrf['time'] = time(); } else { //Reset the timer diff --git a/include/class.dept.php b/include/class.dept.php index cd89c2cbb86770c2c0aff2c41c42fda8828c560a..8e34e2ba4860025082872b55cc5cc7108d546d2c 100644 --- a/include/class.dept.php +++ b/include/class.dept.php @@ -1,7 +1,7 @@ <?php /********************************************************************* class.dept.php - + Department class Peter Rotich <peter@osticket.com> @@ -18,23 +18,23 @@ class Dept { var $email; var $sla; - var $manager; + var $manager; var $members; var $groups; var $ht; - + function Dept($id) { $this->id=0; $this->load($id); } - + function load($id=0) { global $cfg; if(!$id && !($id=$this->getId())) return false; - + $sql='SELECT dept.*,dept.dept_id as id,dept.dept_name as name, dept.dept_signature as signature, count(staff.staff_id) as users ' .' FROM '.DEPT_TABLE.' dept ' .' LEFT JOIN '.STAFF_TABLE.' staff ON (dept.dept_id=staff.dept_id) ' @@ -66,21 +66,21 @@ class Dept { function getId() { return $this->id; } - + function getName() { return $this->ht['name']; } - + function getEmailId() { return $this->ht['email_id']; } function getEmail() { - + if(!$this->email && $this->getEmailId()) $this->email=Email::lookup($this->getEmailId()); - + return $this->email; } @@ -88,7 +88,7 @@ class Dept { return $this->ht['users']; } - + function getNumUsers() { return $this->getNumStaff(); } @@ -103,14 +103,14 @@ class Dept { $this->members = array(); $sql='SELECT DISTINCT s.staff_id FROM '.STAFF_TABLE.' s ' .' LEFT JOIN '.GROUP_DEPT_TABLE.' g ON(s.group_id=g.group_id) ' - .' INNER JOIN '.DEPT_TABLE.' d - ON(d.dept_id=s.dept_id - OR d.manager_id=s.staff_id + .' INNER JOIN '.DEPT_TABLE.' d + ON(d.dept_id=s.dept_id + OR d.manager_id=s.staff_id OR (d.dept_id=g.dept_id AND d.group_membership=1) ) ' .' WHERE d.dept_id='.db_input($this->getId()) .' ORDER BY s.lastname, s.firstname'; - + if(($res=db_query($sql)) && db_num_rows($res)) { while(list($id)=db_fetch_row($res)) $this->members[] = Staff::lookup($id); @@ -140,11 +140,11 @@ class Dept { function getTemplate() { if(!$this->template && $this->getTemplateId()) - $this->template = Template::lookup($this->getTemplateId()); + $this->template = EmailTemplateGroup::lookup($this->getTemplateId()); return $this->template; } - + function getAutoRespEmail() { if(!$this->autorespEmail && $this->ht['autoresp_email_id'] && ($email=Email::lookup($this->ht['autoresp_email_id']))) @@ -154,12 +154,12 @@ class Dept { return $this->autorespEmail; } - + function getEmailAddress() { if(($email=$this->getEmail())) return $email->getAddress(); } - + function getSignature() { return $this->ht['signature']; } @@ -167,13 +167,13 @@ class Dept { function canAppendSignature() { return ($this->getSignature() && $this->isPublic()); } - + function getManagerId() { return $this->ht['manager_id']; } function getManager() { - + if(!$this->manager && $this->getManagerId()) $this->manager=Staff::lookup($this->getManagerId()); @@ -191,11 +191,11 @@ class Dept { function isPublic() { return ($this->ht['ispublic']); } - + function autoRespONNewTicket() { return ($this->ht['ticket_auto_response']); } - + function autoRespONNewMessage() { return ($this->ht['message_auto_response']); } @@ -208,7 +208,7 @@ class Dept { function isGroupMembershipEnabled() { return ($this->ht['group_membership']); } - + function getHashtable() { return $this->ht; } @@ -218,7 +218,7 @@ class Dept { } - + function getAllowedGroups() { if($this->groups) return $this->groups; @@ -244,9 +244,9 @@ class Dept { } } - + $sql='DELETE FROM '.GROUP_DEPT_TABLE.' WHERE dept_id='.db_input($this->getId()); - if($groups && is_array($groups)) + if($groups && is_array($groups)) $sql.=' AND group_id NOT IN('.implode(',', db_input($groups)).')'; db_query($sql); @@ -262,13 +262,13 @@ class Dept { $this->updateAllowedGroups($vars['groups']); $this->reload(); - + return true; } function delete() { global $cfg; - + if(!$cfg || $this->getId()==$cfg->getDefaultDeptId() || $this->getNumUsers()) return 0; @@ -317,7 +317,7 @@ class Dept { } function getDepartments( $criteria=null) { - + $depts=array(); $sql='SELECT dept_id, dept_name FROM '.DEPT_TABLE.' WHERE 1'; if($criteria['publiconly']) @@ -347,13 +347,13 @@ class Dept { function save($id, $vars, &$errors) { global $cfg; - + if($id && $id!=$vars['id']) $errors['err']='Missing or invalid Dept ID (internal error).'; - + if(!$vars['email_id'] || !is_numeric($vars['email_id'])) $errors['email_id']='Email selection required'; - + if(!is_numeric($vars['tpl_id'])) $errors['tpl_id']='Template selection required'; @@ -362,15 +362,15 @@ class Dept { } elseif(strlen($vars['name'])<4) { $errors['name']='Name is too short.'; } elseif(($did=Dept::getIdByName($vars['name'])) && $did!=$id) { - $errors['name']='Department already exist'; + $errors['name']='Department already exists'; } - + if(!$vars['ispublic'] && ($vars['id']==$cfg->getDefaultDeptId())) - $errors['ispublic']='System default department can not be private'; + $errors['ispublic']='System default department cannot be private'; if($errors) return false; - + $sql='SET updated=NOW() ' .' ,ispublic='.db_input($vars['ispublic']) .' ,email_id='.db_input($vars['email_id']) @@ -384,25 +384,25 @@ class Dept { .' ,ticket_auto_response='.db_input(isset($vars['ticket_auto_response'])?$vars['ticket_auto_response']:1) .' ,message_auto_response='.db_input(isset($vars['message_auto_response'])?$vars['message_auto_response']:1); - + if($id) { $sql='UPDATE '.DEPT_TABLE.' '.$sql.' WHERE dept_id='.db_input($id); if(db_query($sql) && db_affected_rows()) return true; - + $errors['err']='Unable to update '.Format::htmlchars($vars['name']).' Dept. Error occurred'; - + } else { $sql='INSERT INTO '.DEPT_TABLE.' '.$sql.',created=NOW()'; if(db_query($sql) && ($id=db_insert_id())) return $id; - + $errors['err']='Unable to create department. Internal error'; - + } - + return false; } diff --git a/include/class.email.php b/include/class.email.php index d40099185b2fae5ce8dd461c70b680c1137aacd0..9f7ea95b0c76ceac926d5bdedea534afc1830ea4 100644 --- a/include/class.email.php +++ b/include/class.email.php @@ -21,12 +21,12 @@ class Email { var $dept; var $ht; - + function Email($id) { $this->id=0; $this->load($id); } - + function load($id=0) { if(!$id && !($id=$this->getId())) @@ -36,20 +36,20 @@ class Email { if(!($res=db_query($sql)) || !db_num_rows($res)) return false; - + $this->ht=db_fetch_array($res); $this->id=$this->ht['email_id']; $this->address=$this->ht['name']?($this->ht['name'].'<'.$this->ht['email'].'>'):$this->ht['email']; $this->dept = null; - + return true; } - + function reload() { return $this->load(); } - + function getId() { return $this->id; } @@ -57,11 +57,11 @@ class Email { function getEmail() { return $this->ht['email']; } - + function getAddress() { return $this->address; } - + function getName() { return $this->ht['name']; } @@ -78,7 +78,7 @@ class Email { if(!$this->dept && $this->getDeptId()) $this->dept=Dept::lookup($this->getDeptId()); - + return $this->dept; } @@ -87,13 +87,13 @@ class Email { } function getPasswd() { - return $this->ht['userpass']?Mcrypt::decrypt($this->ht['userpass'],SECRET_SALT):''; + return $this->ht['userpass']?Crypto::decrypt($this->ht['userpass'], SECRET_SALT, $this->ht['userid']):''; } function getHashtable() { return $this->ht; } - + function getInfo() { return $this->getHashtable(); } @@ -108,8 +108,8 @@ class Email { 'protocol' => $this->ht['mail_protocol'], 'encryption' => $this->ht['mail_encryption'], 'username' => $this->ht['userid'], - 'password' => Mcrypt::decrypt($this->ht['userpass'], SECRET_SALT), - //osTicket specific + 'password' => Crypto::decrypt($this->ht['userpass'], SECRET_SALT, $this->ht['userid']), + //osTicket specific 'email_id' => $this->getId(), //Required for email routing to work. 'max_fetch' => $this->ht['mail_fetchmax'], 'delete_mail' => $this->ht['mail_delete'], @@ -120,7 +120,12 @@ class Email { } function isSMTPEnabled() { - return $this->ht['smtp_active']; + + return ( + $this->ht['smtp_active'] + && ($info=$this->getSMTPInfo()) + && (!$info['auth'] || $info['password']) + ); } function allowSpoofing() { @@ -128,13 +133,13 @@ class Email { } function getSMTPInfo() { - + $info = array ( 'host' => $this->ht['smtp_host'], 'port' => $this->ht['smtp_port'], 'auth' => (bool) $this->ht['smtp_auth'], 'username' => $this->ht['userid'], - 'password' => Mcrypt::decrypt($this->ht['userpass'], SECRET_SALT) + 'password' => Crypto::decrypt($this->ht['userpass'], SECRET_SALT, $this->ht['userid']) ); return $info; @@ -167,7 +172,7 @@ class Email { return false; $this->reload(); - + return true; } @@ -191,13 +196,13 @@ class Email { /******* Static functions ************/ - + function getIdByEmail($email) { - + $sql='SELECT email_id FROM '.EMAIL_TABLE.' WHERE email='.db_input($email); - if(($res=db_query($sql)) && db_num_rows($res)) + if(($res=db_query($sql)) && db_num_rows($res)) list($id)=db_fetch_row($res); - + return $id; } @@ -224,11 +229,11 @@ class Email { if(!$vars['email'] || !Validator::is_email($vars['email'])) { $errors['email']='Valid email required'; }elseif(($eid=Email::getIdByEmail($vars['email'])) && $eid!=$id) { - $errors['email']='Email already exits'; + $errors['email']='Email already exists'; }elseif($cfg && !strcasecmp($cfg->getAdminEmail(), $vars['email'])) { $errors['email']='Email already used as admin email!'; - }elseif(Staff::getIdByEmail($vars['email'])) { //make sure the email doesn't belong to any of the staff - $errors['email']='Email in-use by a staff member'; + }elseif(Staff::getIdByEmail($vars['email'])) { //make sure the email doesn't belong to any of the staff + $errors['email']='Email in use by a staff member'; } if(!$vars['name']) @@ -237,11 +242,16 @@ class Email { if($vars['mail_active'] || ($vars['smtp_active'] && $vars['smtp_auth'])) { if(!$vars['userid']) $errors['userid']='Username missing'; - + if(!$id && !$vars['passwd']) $errors['passwd']='Password required'; + elseif($vars['passwd'] + && $vars['userid'] + && !Crypto::encrypt($vars['passwd'], SECRET_SALT, $vars['userid']) + ) + $errors['passwd'] = 'Unable to encrypt password - get technical support'; } - + if($vars['mail_active']) { //Check pop/imapinfo only when enabled. if(!function_exists('imap_open')) @@ -266,7 +276,7 @@ class Email { elseif(!strcasecmp($vars['postfetch'],'archive') && !$vars['mail_archivefolder'] ) $errors['postfetch']='Valid folder required'; } - + if($vars['smtp_active']) { if(!$vars['smtp_host']) $errors['smtp_host']='Host name required'; @@ -276,17 +286,17 @@ class Email { //abort on errors if($errors) return false; - + if(!$errors && ($vars['mail_host'] && $vars['userid'])) { $sql='SELECT email_id FROM '.EMAIL_TABLE .' WHERE mail_host='.db_input($vars['mail_host']).' AND userid='.db_input($vars['userid']); if($id) $sql.=' AND email_id!='.db_input($id); - + if(db_num_rows(db_query($sql))) - $errors['userid']=$errors['host']='Host/userid combination already in-use.'; + $errors['userid']=$errors['host']='Host/userid combination already in use.'; } - + $passwd=$vars['passwd']?$vars['passwd']:$vars['cpasswd']; if(!$errors && $vars['mail_active']) { //note: password is unencrypted at this point...MailFetcher expect plain text. @@ -308,7 +318,7 @@ class Email { $errors['mail']='Invalid or unknown archive folder!'; } } - + if(!$errors && $vars['smtp_active']) { //Check SMTP login only. require_once 'Mail.php'; // PEAR Mail package $smtp = mail::factory('smtp', @@ -322,13 +332,13 @@ class Email { )); $mail = $smtp->connect(); if(PEAR::isError($mail)) { - $errors['err']='Unable to login. Check SMTP settings.'; + $errors['err']='Unable to log in. Check SMTP settings.'; $errors['smtp']='<br>'.$mail->getMessage(); }else{ $smtp->disconnect(); //Thank you, sir! } } - + if($errors) return false; //Default to default priority and dept.. @@ -336,7 +346,7 @@ class Email { $vars['priority_id']=$cfg->getDefaultPriorityId(); if(!$vars['dept_id'] && $cfg) $vars['dept_id']=$cfg->getDefaultDeptId(); - + $sql='updated=NOW(),mail_errors=0, mail_lastfetch=NULL'. ',email='.db_input($vars['email']). ',name='.db_input(Format::striptags($vars['name'])). @@ -365,15 +375,15 @@ class Email { $sql.=',mail_delete=0,mail_archivefolder='.db_input($vars['mail_archivefolder']); else $sql.=',mail_delete=0,mail_archivefolder=NULL'; - + if($vars['passwd']) //New password - encrypt. - $sql.=',userpass='.db_input(Mcrypt::encrypt($vars['passwd'],SECRET_SALT)); - + $sql.=',userpass='.db_input(Crypto::encrypt($vars['passwd'],SECRET_SALT, $vars['userid'])); + if($id) { //update $sql='UPDATE '.EMAIL_TABLE.' SET '.$sql.' WHERE email_id='.db_input($id); if(db_query($sql) && db_affected_rows()) return true; - + $errors['err']='Unable to update email. Internal error occurred'; }else { $sql='INSERT INTO '.EMAIL_TABLE.' SET '.$sql.',created=NOW()'; @@ -382,7 +392,7 @@ class Email { $errors['err']='Unable to add email. Internal error'; } - + return false; } } diff --git a/include/class.error.php b/include/class.error.php new file mode 100644 index 0000000000000000000000000000000000000000..ed5bf7e4c3d8d0384c63f0d8a4dbaa7af8b543a3 --- /dev/null +++ b/include/class.error.php @@ -0,0 +1,60 @@ +<?php +/********************************************************************* + class.error.php + + Error handling for PHP < 5.0. Allows for returning a formal error from a + function since throwing it isn't available. Also allows for consistent + logging and debugging of errors in the osTicket system log. + + Peter Rotich <peter@osticket.com> + Jared Hancock <jared@osticket.com> + Copyright (c) 2006-2013 osTicket + http://www.osticket.com + + Released under the GNU General Public License WITHOUT ANY WARRANTY. + See LICENSE.TXT for details. + + vim: expandtab sw=4 ts=4 sts=4: +**********************************************************************/ + +class Error /* extends Exception */ { + var $title = ''; + + function Error($message) { + call_user_func_array(array($this,'__construct'), func_get_args()); + } + function __construct($message) { + global $ost; + + $message = str_replace(ROOT_DIR, '(root)/', $message); + + if ($ost->getConfig()->getLogLevel() == 3) + $message .= "\n\n" . $this->formatBacktrace(debug_backtrace()); + + $ost->logError($this->getTitle(), $message); + } + + function getTitle() { + return get_class($this) . ": {$this->title}"; + } + + function formatBacktrace($bt) { + $buffer = array(); + foreach ($bt as $i=>$frame) + $buffer[] = sprintf("#%d %s%s%s at [%s:%d]", $i, + $frame['class'], $frame['type'], $frame['function'], + str_replace(ROOT_DIR, '', $frame['file']), $frame['line']); + return implode("\n", $buffer); + } +} + +class InitialDataError extends Error { + var $title = 'Problem with install initial data'; +} + +function raise_error($message, $class=false) { + if (!$class) $class = 'Error'; + new $class($message); +} + +?> diff --git a/include/class.export.php b/include/class.export.php index 372cf62e2d6c55f09e25c59be29ba9691f075df5..67da6d5e463d968072e577c71fd3cd7e02ba6df6 100644 --- a/include/class.export.php +++ b/include/class.export.php @@ -1,7 +1,7 @@ <?php /************************************************************************* class.export.php - + Exports stuff (details to follow) Jared Hancock <jared@osticket.com> @@ -15,7 +15,7 @@ **********************************************************************/ class Export { - + /* static */ function dumpQuery($sql, $headers, $how='csv', $filter=false) { $exporters = array( 'csv' => CsvResultsExporter, @@ -43,7 +43,16 @@ class Export { 'dept_name' => 'Department', 'helptopic' => 'Help Topic', 'source' => 'Source', - 'status' => 'Current Status' + 'status' => 'Current Status', + 'effective_date' => 'Last Updated', + 'duedate' => 'Due Date', + 'isoverdue' => 'Overdue', + 'isanswered' => 'Answered', + 'assigned' => 'Assigned To', + 'staff' => 'Staff Assigned', + 'team' => 'Team Assigned', + 'thread_count' => 'Thread Count', + 'attachments' => 'Attachment Count', ), $how); } @@ -73,7 +82,7 @@ class ResultSetExporter { $this->keys = array(); $this->lookups = array(); foreach ($headers as $field=>$name) { - if (isset($row[$field])) { + if (array_key_exists($field, $row)) { $this->headers[] = $name; $this->keys[] = $field; # Remember the location of this header in the query results @@ -137,3 +146,80 @@ class JsonResultsExporter extends ResultSetExporter { echo $exp->encode($rows); } } + +require_once INCLUDE_DIR . 'class.json.php'; +require_once INCLUDE_DIR . 'class.migrater.php'; +require_once INCLUDE_DIR . 'class.signal.php'; + +class DatabaseExporter { + + var $stream; + var $tables = array(CONFIG_TABLE, SYSLOG_TABLE, FILE_TABLE, + FILE_CHUNK_TABLE, STAFF_TABLE, DEPT_TABLE, TOPIC_TABLE, GROUP_TABLE, + GROUP_DEPT_TABLE, TEAM_TABLE, TEAM_MEMBER_TABLE, FAQ_TABLE, + FAQ_ATTACHMENT_TABLE, FAQ_TOPIC_TABLE, FAQ_CATEGORY_TABLE, + CANNED_TABLE, CANNED_ATTACHMENT_TABLE, TICKET_TABLE, + TICKET_THREAD_TABLE, TICKET_ATTACHMENT_TABLE, TICKET_PRIORITY_TABLE, + TICKET_LOCK_TABLE, TICKET_EVENT_TABLE, TICKET_EMAIL_INFO_TABLE, + EMAIL_TABLE, EMAIL_TEMPLATE_TABLE, EMAIL_TEMPLATE_GRP_TABLE, + FILTER_TABLE, FILTER_RULE_TABLE, SLA_TABLE, API_KEY_TABLE, + TIMEZONE_TABLE, SESSION_TABLE, PAGE_TABLE); + + function DatabaseExporter($stream) { + $this->stream = $stream; + } + + function write_block($what) { + fwrite($this->stream, JsonDataEncoder::encode($what)); + fwrite($this->stream, "\x1e"); + } + + function dump($error_stream) { + // Allow plugins to change the tables exported + Signal::send('export.tables', $this, $this->tables); + + $header = array( + array(OSTICKET_BACKUP_SIGNATURE, OSTICKET_BACKUP_VERSION), + array( + 'version'=>THIS_VERSION, + 'table_prefix'=>TABLE_PREFIX, + 'salt'=>SECRET_SALT, + 'dbtype'=>DBTYPE, + 'streams'=>DatabaseMigrater::getUpgradeStreams( + UPGRADE_DIR . 'streams/'), + ), + ); + $this->write_block($header); + + foreach ($this->tables as $t) { + if ($error_stream) $error_stream->write("$t\n"); + // Inspect schema + $table = $indexes = array(); + $res = db_query("show columns from $t"); + while ($field = db_fetch_array($res)) + $table[] = $field; + + $res = db_query("show indexes from $t"); + while ($col = db_fetch_array($res)) + $indexes[] = $col; + + $res = db_query("select * from $t"); + $types = array(); + + if (!$table) { + if ($error_stream) $error_stream->write( + $t.': Cannot export table with no fields'."\n"); + die(); + } + $this->write_block( + array('table', substr($t, strlen(TABLE_PREFIX)), $table, + $indexes)); + + // Dump row data + while ($row = db_fetch_row($res)) + $this->write_block($row); + + $this->write_block(array('end-table')); + } + } +} diff --git a/include/class.faq.php b/include/class.faq.php index 447719fbda62e1eac0fd3fe68cc66cbefc069352..fb07e5effbdad6ab1f9e52ed12f9292c33af85c6 100644 --- a/include/class.faq.php +++ b/include/class.faq.php @@ -38,7 +38,7 @@ class FAQ { .' WHERE faq.faq_id='.db_input($id) .' GROUP BY faq.faq_id'; - if (!($res=db_query($sql)) || !db_num_rows($res)) + if (!($res=db_query($sql)) || !db_num_rows($res)) return false; $this->ht = db_fetch_array($res); @@ -66,9 +66,9 @@ class FAQ { function getCreateDate() { return $this->ht['created']; } function getUpdateDate() { return $this->ht['updated']; } - + function getCategoryId() { return $this->ht['category_id']; } - function getCategory() { + function getCategory() { if(!$this->category && $this->getCategoryId()) $this->category = Category::lookup($this->getCategoryId()); @@ -86,7 +86,7 @@ class FAQ { function getHelpTopics() { //XXX: change it to obj (when needed)! - + if (!isset($this->topics)) { $this->topics = array(); $sql='SELECT t.topic_id, CONCAT_WS(" / ", pt.topic, t.topic) as name FROM '.TOPIC_TABLE.' t ' @@ -159,7 +159,7 @@ class FAQ { return false; $this->updateTopics($vars['topics']); - + //Delete removed attachments. $keepers = $vars['files']?$vars['files']:array(); if(($attachments = $this->getAttachments())) { @@ -211,12 +211,12 @@ class FAQ { $str.=sprintf('<a class="Icon file" href="file.php?h=%s" target="%s">%s</a>%s %s', $hash, $target, Format::htmlchars($attachment['name']), $size, $separator); - + } } return $str; } - + function uploadAttachments($files) { $i=0; @@ -258,40 +258,40 @@ class FAQ { function delete() { - + $sql='DELETE FROM '.FAQ_TABLE .' WHERE faq_id='.db_input($this->getId()) .' LIMIT 1'; if(!db_query($sql) || !db_affected_rows()) return false; - + //Cleanup help topics. db_query('DELETE FROM '.FAQ_TOPIC_TABLE.' WHERE faq_id='.db_input($this->id)); //Cleanup attachments. $this->deleteAttachments(); - + return true; } /* ------------------> Static methods <--------------------- */ - + function add($vars, &$errors) { if(!($id=self::create($vars, $errors))) return false; if(($faq=self::lookup($id))) { $faq->updateTopics($vars['topics']); - + if($_FILES['attachments'] && ($files=AttachmentFile::format($_FILES['attachments']))) $faq->uploadAttachments($files); $faq->reload(); } - + return $faq; } - function create($vars, &$errors) { + function create($vars, &$errors) { return self::save(0, $vars, $errors); } @@ -319,12 +319,12 @@ class FAQ { function findByQuestion($question) { - if(($id=self::getIdByQuestion($question))) + if(($id=self::findIdByQuestion($question))) return self::lookup($id); return false; } - + function save($id, $vars, &$errors, $validation=false) { //Cleanup. @@ -359,7 +359,7 @@ class FAQ { $sql='UPDATE '.FAQ_TABLE.' SET '.$sql.' WHERE faq_id='.db_input($id); if(db_query($sql)) return true; - + $errors['err']='Unable to update FAQ.'; } else { diff --git a/include/class.file.php b/include/class.file.php index 6908dd8eec6efb4e2a24a0618fd1945471859878..9e85630c7bd92f42cce0225e4d2c9c13127073c4 100644 --- a/include/class.file.php +++ b/include/class.file.php @@ -91,11 +91,24 @@ class AttachmentFile { return $this->ht['hash']; } + function lastModified() { + return $this->ht['created']; + } + + /** + * Retrieve a hash that can be sent to scp/file.php?h= in order to + * download this file + */ + function getDownloadHash() { + return strtolower($this->getHash() . md5($this->getId().session_id().$this->getHash())); + } + function open() { return new AttachmentChunkedData($this->id); } function sendData() { + @ini_set('zlib.output_compression', 'Off'); $file = $this->open(); while ($chunk = $file->read()) echo $chunk; @@ -125,7 +138,19 @@ class AttachmentFile { function display() { - + + // Thanks, http://stackoverflow.com/a/1583753/1025836 + $last_modified = strtotime($this->lastModified()); + header("Last-Modified: ".gmdate(DATE_RFC822, $last_modified)." GMT", false); + header('ETag: "'.$this->getHash().'"'); + header('Cache-Control: private, max-age=3600'); + header('Expires: ' . date(DATE_RFC822, time() + 3600) . ' GMT'); + header('Pragma: private'); + if (@strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $last_modified || + @trim($_SERVER['HTTP_IF_NONE_MATCH']) == $this->getHash()) { + header("HTTP/1.1 304 Not Modified"); + exit(); + } header('Content-Type: '.($this->getType()?$this->getType():'application/octet-stream')); header('Content-Length: '.$this->getSize()); @@ -140,7 +165,7 @@ class AttachmentFile { header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); header('Cache-Control: public'); header('Content-Type: '.($this->getType()?$this->getType():'application/octet-stream')); - + $filename=basename($this->getName()); $user_agent = strtolower ($_SERVER['HTTP_USER_AGENT']); if ((is_integer(strpos($user_agent,'msie'))) && (is_integer(strpos($user_agent,'win')))) { @@ -148,7 +173,7 @@ class AttachmentFile { }else{ header('Content-Disposition: attachment; filename='.$filename.';' ); } - + header('Content-Transfer-Encoding: binary'); header('Content-Length: '.$this->getSize()); $this->sendData(); @@ -156,12 +181,13 @@ class AttachmentFile { } /* Function assumes the files types have been validated */ - function upload($file) { - + function upload($file, $ft='T') { + if(!$file['name'] || $file['error'] || !is_uploaded_file($file['tmp_name'])) return false; $info=array('type'=>$file['type'], + 'filetype'=>$ft, 'size'=>$file['size'], 'name'=>$file['name'], 'hash'=>MD5(MD5_FILE($file['tmp_name']).time()), @@ -171,19 +197,55 @@ class AttachmentFile { return AttachmentFile::save($info); } + function uploadLogo($file, &$error, $aspect_ratio=3) { + /* Borrowed in part from + * http://salman-w.blogspot.com/2009/04/crop-to-fit-image-using-aspphp.html + */ + if (!extension_loaded('gd')) + return self::upload($file, 'L'); + + $source_path = $file['tmp_name']; + + list($source_width, $source_height, $source_type) = getimagesize($source_path); + + switch ($source_type) { + case IMAGETYPE_GIF: + case IMAGETYPE_JPEG: + case IMAGETYPE_PNG: + break; + default: + // TODO: Return an error + $error = 'Invalid image file type'; + return false; + } + + $source_aspect_ratio = $source_width / $source_height; + + if ($source_aspect_ratio >= $aspect_ratio) + return self::upload($file, 'L'); + + $error = 'Image is too square. Upload a wider image'; + return false; + } + function save($file) { if(!$file['hash']) $file['hash']=MD5(MD5($file['data']).time()); if(!$file['size']) $file['size']=strlen($file['data']); - + $sql='INSERT INTO '.FILE_TABLE.' SET created=NOW() ' .',type='.db_input($file['type']) .',size='.db_input($file['size']) .',name='.db_input(Format::file_name($file['name'])) .',hash='.db_input($file['hash']); + # XXX: ft does not exists during the upgrade when attachments are + # migrated! + if(isset($file['filetype'])) + $sql.=',ft='.db_input($file['filetype']); + if (!(db_query($sql) && ($id=db_insert_id()))) return false; @@ -207,11 +269,11 @@ class AttachmentFile { function lookup($id) { $id = is_numeric($id)?$id:AttachmentFile::getIdByHash($id); - + return ($id && ($file = new AttachmentFile($id)) && $file->getId()==$id)?$file:null; } - /* + /* Method formats http based $_FILE uploads - plus basic validation. @restrict - make sure file type & size are allowed. */ @@ -233,7 +295,7 @@ class AttachmentFile { foreach($attachments as $i => &$file) { //skip no file upload "error" - why PHP calls it an error is beyond me. if($file['error'] && $file['error']==UPLOAD_ERR_NO_FILE) { - unset($attachments[$i]); + unset($attachments[$i]); continue; } @@ -262,7 +324,7 @@ class AttachmentFile { * canned-response, or faq point to any more. */ /* static */ function deleteOrphans() { - + $sql = 'DELETE FROM '.FILE_TABLE.' WHERE id NOT IN (' # DISTINCT implies sort and may not be necessary .'SELECT DISTINCT(file_id) FROM (' @@ -272,16 +334,27 @@ class AttachmentFile { .' UNION ALL ' .'SELECT file_id FROM '.FAQ_ATTACHMENT_TABLE .') still_loved' - .')'; + .') AND `ft` = "T"'; db_query($sql); - + //Delete orphaned chuncked data! AttachmentChunkedData::deleteOrphans(); - + return true; } + + /* static */ + function allLogos() { + $sql = 'SELECT id FROM '.FILE_TABLE.' WHERE ft="L" + ORDER BY created'; + $logos = array(); + $res = db_query($sql); + while (list($id) = db_fetch_row($res)) + $logos[] = AttachmentFile::lookup($id); + return $logos; + } } /** @@ -327,11 +400,11 @@ class AttachmentChunkedData { } function deleteOrphans() { - + $sql = 'DELETE c.* FROM '.FILE_CHUNK_TABLE.' c ' . ' LEFT JOIN '.FILE_TABLE.' f ON(f.id=c.file_id) ' . ' WHERE f.id IS NULL'; - + return db_query($sql)?db_affected_rows():0; } } diff --git a/include/class.filter.php b/include/class.filter.php index 02a5e04239e56a6013241d020d7874d0fea59ff7..df351c8a7b75a9231b384d4584ca6032e4a78d7b 100644 --- a/include/class.filter.php +++ b/include/class.filter.php @@ -36,10 +36,10 @@ class Filter { if(!($res=db_query($sql)) || !db_num_rows($res)) return false; - + $this->ht=db_fetch_array($res); $this->id=$this->ht['id']; - + return true; } @@ -130,7 +130,7 @@ class Filter { function disableAlerts() { return ($this->ht['disable_autoresponder']); } - + function sendAlerts() { return (!$this->disableAlerts()); } @@ -149,7 +149,7 @@ class Filter { return $this->ht['rules']; } - function getFlatRules() { //Format used on html... I'm ashamed + function getFlatRules() { //Format used on html... I'm ashamed $info=array(); if(($rules=$this->getRules())) { @@ -181,7 +181,7 @@ class Filter { return (db_query($sql) && db_affected_rows()); } - + function getRule($id) { return $this->getRuleById($id); } @@ -222,7 +222,7 @@ class Filter { * reply-to - reply-to email address * reply-to-name - name of sender to reply-to * headers - array of email headers - * emailId - osTicket system email id + * emailId - osTicket system email id */ function matches($what) { @@ -240,7 +240,7 @@ class Filter { $match = false; # Respect configured filter email-id - if ($this->getEmailId() + if ($this->getEmailId() && !strcasecmp($this->getTarget(), 'Email') && $this->getEmailId() != $what['emailId']) return false; @@ -259,7 +259,7 @@ class Filter { if (!$this->matchAllRules()) break; } else { # No match. Continue? - if ($this->matchAllRules()) { + if ($this->matchAllRules()) { $match = false; break; } @@ -268,7 +268,7 @@ class Filter { return $match; } - /** + /** * If the matches() method returns TRUE, send the initial ticket to this * method to apply the filter actions defined */ @@ -290,7 +290,7 @@ class Filter { # match if ($this->useReplyToEmail() && $info['reply-to']) { $ticket['email'] = $info['reply-to']; - if ($info['reply-to-name']) + if ($info['reply-to-name']) $ticket['name'] = $info['reply-to-name']; } @@ -323,12 +323,12 @@ class Filter { return false; $this->reload(); - + return true; } function delete() { - + $id=$this->getId(); $sql='DELETE FROM '.FILTER_TABLE.' WHERE id='.db_input($id).' LIMIT 1'; if(db_query($sql) && ($num=db_affected_rows())) { @@ -401,7 +401,7 @@ class Filter { if(!$id) return true; //When ID is 0 then assume it was just validation... - //Clear existing rules...we're doing mass replace on each save!! + //Clear existing rules...we're doing mass replace on each save!! db_query('DELETE FROM '.FILTER_RULE_TABLE.' WHERE filter_id='.db_input($id)); $num=0; foreach($rules as $rule) { @@ -410,7 +410,7 @@ class Filter { $num++; } - return $num; + return $num; } function save($id,$vars,&$errors) { @@ -420,11 +420,11 @@ class Filter { $errors['execorder'] = 'Order required'; elseif(!is_numeric($vars['execorder'])) $errors['execorder'] = 'Must be numeric value'; - + if(!$vars['name']) $errors['name'] = 'Name required'; elseif(($sid=self::getIdByName($vars['name'])) && $sid!=$id) - $errors['name'] = 'Name already in-use'; + $errors['name'] = 'Name already in use'; if(!$errors && !self::validate_rules($vars,$errors) && !$errors['rules']) $errors['rules'] = 'Unable to validate rules as entered'; @@ -459,7 +459,7 @@ class Filter { .',disable_autoresponder='.db_input(isset($vars['disable_autoresponder'])?1:0) .',canned_response_id='.db_input($vars['canned_response_id']) .',notes='.db_input($vars['notes']); - + //Auto assign ID is overloaded... if($vars['assign'] && $vars['assign'][0]=='s') @@ -484,7 +484,7 @@ 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 - + return true; } } @@ -507,15 +507,15 @@ class FilterRule { .' WHERE rule.id='.db_input($id); if($filterId) $sql.=' AND rule.filter_id='.db_input($filterId); - + if(!($res=db_query($sql)) || !db_num_rows($res)) return false; - + $this->ht=db_fetch_array($res); $this->id=$this->ht['id']; - + $this->filter=null; return true; @@ -546,7 +546,7 @@ class FilterRule { } function getFilter() { - + if(!$this->filter && $this->getFilterId()) $this->filter = Filter::lookup($this->getFilterId()); @@ -562,7 +562,7 @@ class FilterRule { } function delete() { - + $sql='DELETE FROM '.FILTER_RULE_TABLE.' WHERE id='.db_input($this->getId()).' AND filter_id='.db_input($this->getFilterId()); return (db_query($sql) && db_affected_rows()); @@ -579,14 +579,14 @@ class FilterRule { if($errors) return false; - + $sql=' updated=NOW() '. ',what='.db_input($vars['w']). ',how='.db_input($vars['h']). ',val='.db_input($vars['v']). ',isactive='.db_input(isset($vars['isactive'])?$vars['isactive']:1); - + if(isset($vars['notes'])) $sql.=',notes='.db_input($vars['notes']); @@ -640,26 +640,28 @@ class TicketFilter { * @see ::quickList() for more information. */ function TicketFilter($origin, $vars=null) { - + //Normalize the target based on ticket's origin. $this->target = self::origin2target($origin); - + //Extract the vars we care about (fields we filter by!). - $this->vars = array_filter(array_map('trim', + $this->vars = array_filter(array_map('trim', array( 'email' => $vars['email'], 'subject' => $vars['subject'], 'name' => $vars['name'], 'body' => $vars['message'], - 'emailId' => $vars['emailId']) - )); - + 'emailId' => $vars['emailId'], + 'reply-to' => @$vars['reply-to'], + 'reply-to-name' => @$vars['reply-to-name'], + ))); + //Init filters. $this->build(); } function build() { - + //Clear any memoized filters $this->filters = array(); $this->short_list = null; @@ -722,13 +724,13 @@ class TicketFilter { if ($filter->stopOnMatch()) break; } } - + /* static */ function getAllActive() { $sql='SELECT id FROM '.FILTER_TABLE .' WHERE isactive=1 ' .' AND target IN ("Any", '.db_input($this->getTarget()).') '; - + #Take into account email ID. if($this->vars['emailId']) $sql.=' AND (email_id=0 OR email_id='.db_input($this->vars['emailId']).')'; @@ -746,7 +748,7 @@ class TicketFilter { * arguments. This method will request the database to make a first pass * and eliminate the filters from being considered that would never * match the received email. - * + * * Returns an array<Filter::Id> which will need to have their respective * matches() method queried to determine if the Filter actually matches * the email. @@ -775,13 +777,13 @@ class TicketFilter { # Filter by system's email-id if specified if($this->vars['emailId']) $sql.=' AND (filter.email_id=0 OR filter.email_id='.db_input($this->vars['emailId']).')'; - + # Include rules for sender-email, sender-name and subject as # requested $sql.=" AND ((what='email' AND LOCATE(val, ".db_input($this->vars['email']).'))'; - if($this->vars['name']) + if($this->vars['name']) $sql.=" OR (what='name' AND LOCATE(val, ".db_input($this->vars['name']).'))'; - if($this->vars['subject']) + if($this->vars['subject']) $sql.=" OR (what='subject' AND LOCATE(val, ".db_input($this->vars['subject']).'))'; @@ -799,7 +801,7 @@ class TicketFilter { if (!$this->vars['name']) $sql.=" AND COUNT(*)-COUNT(NULLIF(what,'name'))=0"; if (!$this->vars['subject']) $sql.=" AND COUNT(*)-COUNT(NULLIF(what,'subject'))=0"; # Also include filters that do not have match_all_rules set to and - # have at least one rule 'what' type that wasn't considered e.g body + # have at least one rule 'what' type that wasn't considered e.g body $sql.=") OR filter.id IN (" ." SELECT filter_id" ." FROM ".FILTER_RULE_TABLE." rule" @@ -828,7 +830,8 @@ class TicketFilter { * Filter::matches() method. * Peter - Let's keep it as a quick scan for obviously banned emails. */ - /* static */ function isBanned($addr) { + /* static */ + function isBanned($addr) { $sql='SELECT filter.id, what, how, UPPER(val) ' .' FROM '.FILTER_TABLE.' filter' @@ -849,7 +852,7 @@ class TicketFilter { $addr = strtoupper($addr); $how=array('equal' => array('strcmp', 0), 'contains' => array('strpos', null, false)); - + while ($row=db_fetch_array($res)) { list($func, $pos, $neg) = $how[$row['how']]; if (!$func) continue; @@ -869,7 +872,8 @@ class TicketFilter { * X-Auto-Response-Supress is outlined here, * http://msdn.microsoft.com/en-us/library/ee219609(v=exchg.80).aspx */ - /* static */ function isAutoResponse($headers) { + /* static */ + function isAutoResponse($headers) { if($headers && !is_array($headers)) $headers = Mail_Parse::splitHeaders($headers); @@ -936,13 +940,13 @@ class TicketFilter { return false; } - /** - * Normalize ticket source to supported filter target + /** + * Normalize ticket source to supported filter target * */ function origin2target($origin) { $sources=array('web' => 'Web', 'email' => 'Email', 'phone' => 'Web', 'staff' => 'Web', 'api' => 'API'); - + return $sources[strtolower($origin)]; } } diff --git a/include/class.format.php b/include/class.format.php index 7a339e429212a1d1b7919dc14b7bd6bb0bd0e24c..892cc42f17bd56fe2e934d6c1ffb0c2a6f305fa5 100644 --- a/include/class.format.php +++ b/include/class.format.php @@ -41,10 +41,15 @@ class Format { if(!$charset && function_exists('mb_detect_encoding')) $charset = mb_detect_encoding($text); - //Cleanup - junk - if($charset && in_array(trim($charset), array('default','x-user-defined'))) + // Cleanup - incorrect, bogus, or ambiguous charsets + if($charset && in_array(strtolower(trim($charset)), + array('default','x-user-defined','iso'))) $charset = 'ISO-8859-1'; + if (strcasecmp($charset, $encoding) === 0) + return $text; + + $original = $text; if(function_exists('iconv') && $charset) $text = iconv($charset, $encoding.'//IGNORE', $text); elseif(function_exists('mb_convert_encoding') && $charset && $encoding) @@ -52,7 +57,10 @@ class Format { elseif(!strcasecmp($encoding, 'utf-8')) //forced blind utf8 encoding. $text = function_exists('imap_utf8')?imap_utf8($text):utf8_encode($text); - return $text; + // If $text is false, then we have a (likely) invalid charset, use + // the original text and assume 8-bit (latin-1 / iso-8859-1) + // encoding + return (!$text && $original) ? $original : $text; } //Wrapper for utf-8 encoding. @@ -194,14 +202,14 @@ class Format { $token = $ost->getLinkToken(); //Not perfect but it works - please help improve it. $text=preg_replace_callback('/(((f|ht){1}tp(s?):\/\/)[-a-zA-Z0-9@:%_\+.~#?&;\/\/=]+)/', - create_function('$matches', - sprintf('return "<a href=\"l.php?url=".urlencode($matches[1])."&auth=%s\" target=\"_blank\">".$matches[1]."</a>";', + create_function('$matches', # nolint + sprintf('return "<a href=\"l.php?url=".urlencode($matches[1])."&auth=%s\" target=\"_blank\">".$matches[1]."</a>";', # nolint $token)), $text); $text=preg_replace_callback("/(^|[ \\n\\r\\t])(www\.([a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+)(\/[^\/ \\n\\r]*)*)/", - create_function('$matches', - sprintf('return "<a href=\"l.php?url=".urlencode("http://".$matches[2])."&auth=%s\" target=\"_blank\">".$matches[2]."</a>";', + create_function('$matches', # nolint + sprintf('return "<a href=\"l.php?url=".urlencode("http://".$matches[2])."&auth=%s\" target=\"_blank\">".$matches[2]."</a>";', # nolint $token)), $text); @@ -211,7 +219,7 @@ class Format { return $text; } - function stripEmptyLines ($string) { + function stripEmptyLines($string) { //return preg_replace("/(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+/", "\n", $string); //return preg_replace('/\s\s+/',"\n",$string); //Too strict?? return preg_replace("/\n{3,}/", "\n\n", $string); @@ -293,5 +301,20 @@ class Format { return date($format, ($gmtimestamp+ ($offset*3600))); } + // Thanks, http://stackoverflow.com/a/2955878/1025836 + /* static */ + function slugify($text) { + // replace non letter or digits by - + $text = preg_replace('~[^\p{L}\p{N}]+~u', '-', $text); + + // trim + $text = trim($text, '-'); + + // lowercase + $text = strtolower($text); + + return (empty($text)) ? 'n-a' : $text; + } + } ?> diff --git a/include/class.json.php b/include/class.json.php index f983c73e42fe0394e5a133baebab5e6b26fa639f..b5a589cfac122e008acc207f3bd2942931f31fe7 100644 --- a/include/class.json.php +++ b/include/class.json.php @@ -26,6 +26,10 @@ class JsonDataParser { while (!feof($stream)) { $contents .= fread($stream, 8192); } + return self::decode($contents); + } + + function decode($contents) { if (function_exists("json_decode")) { return json_decode($contents, true); } else { @@ -56,7 +60,11 @@ class JsonDataParser { class JsonDataEncoder { function encode($var) { - $decoder = new Services_JSON(); - return $decoder->encode($var); + if (function_exists('json_encode')) + return json_encode($var); + else { + $decoder = new Services_JSON(); + return $decoder->encode($var); + } } } diff --git a/include/class.log.php b/include/class.log.php index 0edea8991dd90b08c2139d30feddd8ce2f8e0d91..4fea7e4c1ffbf5662bbca89e9216f2ce7650470a 100644 --- a/include/class.log.php +++ b/include/class.log.php @@ -1,8 +1,8 @@ <?php /********************************************************************* - class.group.php + class.log.php - User Group - Everything about a group! + Log Peter Rotich <peter@osticket.com> Copyright (c) 2006-2013 osTicket diff --git a/include/class.mailer.php b/include/class.mailer.php index b4ec97c243718f1b86b3a669c8bc901a6c947096..94c92626f3efd4f5d8536f67b10c8bd181532c72 100644 --- a/include/class.mailer.php +++ b/include/class.mailer.php @@ -2,7 +2,7 @@ /********************************************************************* class.mailer.php - osTicket mailer + osTicket mailer It's mainly PEAR MAIL wrapper for now (more improvements planned). @@ -27,10 +27,10 @@ class Mailer { var $smtp = array(); var $eol="\n"; - + function Mailer($email=null, $options=null) { global $cfg; - + if(is_object($email) && $email->isSMTPEnabled() && ($info=$email->getSMTPInfo())) { //is SMTP enabled for the current email? $this->smtp = $info; } elseif($cfg && ($e=$cfg->getDefaultSMTPEmail()) && $e->isSMTPEnabled()) { //What about global SMTP setting? @@ -54,7 +54,7 @@ class Mailer { function getEmail() { return $this->email; } - + function getSMTPInfo() { return $this->smtp; } @@ -117,7 +117,7 @@ class Mailer { 'X-Auto-Response-Suppress' => 'ALL, AutoReply', 'Auto-Submitted' => 'auto-replied'); - if($options['bulk']) + if($options['bulk']) $headers+= array('Precedence' => 'bulk'); else $headers+= array('Precedence' => 'auto_reply'); @@ -134,7 +134,7 @@ class Mailer { $mime->addAttachment($attachment['file'],$attachment['type'],$attachment['name']); } } - + //Desired encodings... $encodings=array( 'head_encoding' => 'quoted-printable', @@ -176,7 +176,7 @@ class Mailer { function logError($error) { global $ost; - //NOTE: Admin alert overwrite - don't email when having email trouble! + //NOTE: Admin alert override - don't email when having email trouble! $ost->logError('Mailer Error', $error, false); } diff --git a/include/class.mailfetch.php b/include/class.mailfetch.php index d1032ba506971dac36d97ba7ffeba41111213626..5e58ab5a51a1d73692db077f9a6cd7d6faf2f3ca 100644 --- a/include/class.mailfetch.php +++ b/include/class.mailfetch.php @@ -181,7 +181,9 @@ class MailFetcher { $text=imap_binary($text); break; case 3: - $text=imap_base64($text); + // imap_base64 implies strict mode. If it refuses to decode the + // data, then fallback to base64_decode in non-strict mode + $text = (($conv=imap_base64($text))) ? $conv : base64_decode($text); break; case 4: $text=imap_qprint($text); @@ -227,11 +229,41 @@ class MailFetcher { $sender=$headerinfo->from[0]; //Just what we need... $header=array('name' =>@$sender->personal, - 'email' =>(strtolower($sender->mailbox).'@'.$sender->host), + 'email' => trim(strtolower($sender->mailbox).'@'.$sender->host), 'subject'=>@$headerinfo->subject, - 'mid' =>$headerinfo->message_id + 'mid' => trim(@$headerinfo->message_id), + 'header' => $this->getHeader($mid), ); + if ($replyto = $headerinfo->reply_to) { + $header['reply-to'] = $replyto[0]->mailbox.'@'.$replyto[0]->host; + $header['reply-to-name'] = $replyto[0]->personal; + } + + //Try to determine target email - useful when fetched inbox has + // aliases that are independent emails within osTicket. + $emailId = 0; + $tolist = array(); + if($headerinfo->to) + $tolist = array_merge($tolist, $headerinfo->to); + if($headerinfo->cc) + $tolist = array_merge($tolist, $headerinfo->cc); + if($headerinfo->bcc) + $tolist = array_merge($tolist, $headerinfo->bcc); + + foreach($tolist as $addr) + if(($emailId=Email::getIdByEmail(strtolower($addr->mailbox).'@'.$addr->host))) + break; + + $header['emailId'] = $emailId; + + // Ensure we have a message-id. If unable to read it out of the + // email, use the hash of the entire email headers + if (!$header['mid'] && $header['header']) + if (!($header['mid'] = Mail_Parse::findHeaderEntry($header['header'], + 'message-id'))) + $header['mid'] = '<' . md5($header['header']) . '@local>'; + return $header; } @@ -340,13 +372,15 @@ class MailFetcher { function getBody($mid) { $body =''; - if(!($body = $this->getPart($mid,'TEXT/PLAIN', $this->charset))) { - 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 ($body = $this->getPart($mid,'TEXT/PLAIN', $this->charset)) + // The Content-Type was text/plain, so escape anything that + // looks like HTML + $body=Format::htmlchars($body); + elseif ($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. } return $body; @@ -359,10 +393,6 @@ class MailFetcher { if(!($mailinfo = $this->getHeaderInfo($mid))) return false; - //Make sure the email is NOT already fetched... (undeleted emails) - if($mailinfo['mid'] && ($id=Ticket::getIdByMessageId(trim($mailinfo['mid']), $mailinfo['email']))) - return true; //Reporting success so the email can be moved or deleted. - //Is the email address banned? if($mailinfo['email'] && TicketFilter::isBanned($mailinfo['email'])) { //We need to let admin know... @@ -370,15 +400,15 @@ class MailFetcher { return true; //Report success (moved or delete) } - $emailId = $this->getEmailId(); - $vars = array(); - $vars['email']=$mailinfo['email']; + //Make sure the email is NOT already fetched... (undeleted emails) + if($mailinfo['mid'] && ($id=Ticket::getIdByMessageId($mailinfo['mid'], $mailinfo['email']))) + return true; //Reporting success so the email can be moved or deleted. + + $vars = $mailinfo; $vars['name']=$this->mime_decode($mailinfo['name']); $vars['subject']=$mailinfo['subject']?$this->mime_decode($mailinfo['subject']):'[No Subject]'; $vars['message']=Format::stripEmptyLines($this->getBody($mid)); - $vars['header']=$this->getHeader($mid); - $vars['emailId']=$emailId?$emailId:$ost->getConfig()->getDefaultEmailId(); //ok to default? - $vars['mid']=$mailinfo['mid']; + $vars['emailId']=$mailinfo['emailId']?$mailinfo['emailId']:$this->getEmailId(); //Missing FROM name - use email address. if(!$vars['name']) @@ -386,7 +416,7 @@ class MailFetcher { //An email with just attachments can have empty body. if(!$vars['message']) - $vars['message'] = '(EMPTY)'; + $vars['message'] = '-'; if($ost->getConfig()->useEmailPriority()) $vars['priorityId']=$this->getPriority($mid); @@ -427,7 +457,6 @@ class MailFetcher { if($message && $ost->getConfig()->allowEmailAttachments() && ($struct = imap_fetchstructure($this->mbox, $mid)) - && $struct->parts && ($attachments=$this->getAttachments($struct))) { foreach($attachments as $a ) { @@ -545,7 +574,7 @@ class MailFetcher { "\nHost: ".$fetcher->getHost(). "\nError: ".$fetcher->getLastError(). "\n\n ".$errors.' consecutive errors. Maximum of '.$MAXERRORS. ' allowed'. - "\n\n This could be connection issues related to the mail server. Next delayed login attempt in aprox. $TIMEOUT minutes"; + "\n\n This could be connection issues related to the mail server. Next delayed login attempt in approx. $TIMEOUT minutes"; $ost->alertAdmin('Mail Fetch Failure Alert', $msg, true); } } diff --git a/include/class.mailparse.php b/include/class.mailparse.php index 3347451de99a1aa3f1b116b2098de60b2c3e0864..d224b9fe739a2f1db30cef8713cbe0ef4737d38b 100644 --- a/include/class.mailparse.php +++ b/include/class.mailparse.php @@ -106,6 +106,15 @@ class Mail_Parse { return $array; } + /* static */ + function findHeaderEntry($headers, $name) { + if (!is_array($headers)) + $headers = self::splitHeaders($headers); + foreach ($headers as $key=>$val) + if (strcasecmp($key, $name) === 0) + return $val; + return false; + } function getStruct(){ return $this->struct; @@ -143,16 +152,20 @@ class Mail_Parse { return $this->struct->headers['subject']; } + function getReplyTo() { + return Mail_Parse::parseAddressList($this->struct->headers['reply-to']); + } + function getBody(){ $body=''; - if(!($body=$this->getPart($this->struct,'text/plain'))) { - if(($body=$this->getPart($this->struct,'text/html'))) { - //Cleanup the html. - $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($body=$this->getPart($this->struct,'text/plain')) + $body = Format::htmlchars($body); + elseif($body=$this->getPart($this->struct,'text/html')) { + //Cleanup the html. + $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. } return $body; } @@ -202,9 +215,14 @@ class Mail_Parse { $file=array( 'name' => $filename, 'type' => strtolower($part->ctype_primary.'/'.$part->ctype_secondary), - 'data' => $this->mime_encode($part->body, $part->ctype_parameters['charset']) ); + if ($part->ctype_parameters['charset']) + $file['data'] = $this->mime_encode($part->body, + $part->ctype_parameters['charset']); + else + $file['data'] = $part->body; + if(!$this->decode_bodies && $part->headers['content-transfer-encoding']) $file['encoding'] = $part->headers['content-transfer-encoding']; @@ -323,6 +341,13 @@ class EmailDataParser { $data['priorityId'] = $parser->getPriority(); $data['emailId'] = $emailId; + if ($replyto = $parser->getReplyTo()) { + $replyto = $replyto[0]; + $data['reply-to'] = $replyto->mailbox.'@'.$replyto->host; + if ($replyto->personal) + $data['reply-to-name'] = trim($replyto->personal, " \t\n\r\0\x0B\x22"); + } + if($cfg && $cfg->allowEmailAttachments()) $data['attachments'] = $parser->getAttachments(); diff --git a/include/class.mcrypt.php b/include/class.mcrypt.php deleted file mode 100644 index 8381509ffd7abf4471e046af5deaa7d902036ba8..0000000000000000000000000000000000000000 --- a/include/class.mcrypt.php +++ /dev/null @@ -1,48 +0,0 @@ -<?php -/********************************************************************* - class.mcrypt.php - - Mcrypt wrapper.... nothing special at all. - - Peter Rotich <peter@osticket.com> - Copyright (c) 2006-2013 osTicket - http://www.osticket.com - - Released under the GNU General Public License WITHOUT ANY WARRANTY. - See LICENSE.TXT for details. - - vim: expandtab sw=4 ts=4 sts=4: -**********************************************************************/ -class Mcrypt { - - function encrypt($text, $salt) { - global $ost; - - //if mcrypt extension is not installed--simply return unencryted text and log a warning (if enabled). - if(!function_exists('mcrypt_encrypt') || !function_exists('mcrypt_decrypt')) { - if($ost) { - $msg='Cryptography extension mcrypt is not enabled or installed. Important text/data is being stored as plain text in database.'; - $ost->logWarning('mcrypt module missing', $msg); - } - - return $text; - } - - return trim(base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $salt, $text, MCRYPT_MODE_ECB, - mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND)))); - } - - function decrypt($text, $salt) { - - if(!function_exists('mcrypt_encrypt') || !function_exists('mcrypt_decrypt')) - return $text; - - return trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $salt, base64_decode($text), MCRYPT_MODE_ECB, - mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND))); - } - - function exists(){ - return (function_exists('mcrypt_encrypt') && function_exists('mcrypt_decrypt')); - } -} -?> diff --git a/include/class.migrater.php b/include/class.migrater.php index 4cc096be5533121c4ebb30aa9974438de993ad7d..b9dbdca2e663c01b9e7f506319367b4a16548175 100644 --- a/include/class.migrater.php +++ b/include/class.migrater.php @@ -3,7 +3,7 @@ class.migrater.php Migration utils required by upgrader. - + Jared Hancock <jared@osticket.com> Copyright (c) 2006-2013 osTicket http://www.osticket.com @@ -34,11 +34,11 @@ class DatabaseMigrater { var $sqldir; function DatabaseMigrater($start, $end, $sqldir) { - + $this->start = $start; $this->end = $end; $this->sqldir = $sqldir; - + } function getPatches($stop=null) { @@ -50,14 +50,13 @@ class DatabaseMigrater { while (true) { $next = glob($this->sqldir . substr($start, 0, 8) . '-*.patch.sql'); - if (count($next) == 1) { + if(!$next || empty($next)) { + # No patches leaving the current signature. + # Assume that we've applied all the available patches + break; + } elseif (count($next) == 1) { $patches[] = $next[0]; $start = substr(basename($next[0]), 9, 8); - } elseif (count($next) == 0) { - # There are no patches leaving the current signature. We - # have to assume that we've applied all the available - # patches. - break; } else { # Problem -- more than one patch exists from this snapshot. # We probably need a graph approach to solve this. @@ -69,220 +68,101 @@ class DatabaseMigrater { break; } - return $patches; + return array_filter($patches); } -} - - -/* - AttachmentMigrater - Attachment migration from file-based attachments in pre-1.7 to - database-backed attachments in osTicket v1.7. This class provides the - hardware to find and retrieve old attachments and move them into the new - database scheme with the data in the actual database. - - Copyright (c) 2006-2013 osTicket - http://www.osticket.com - - Released under the GNU General Public License WITHOUT ANY WARRANTY. - See LICENSE.TXT for details. - - vim: expandtab sw=4 ts=4 sts=4: -*/ - -include_once(INCLUDE_DIR.'class.file.php'); - -class AttachmentMigrater { - - - function AttachmentMigrater() { - $this->queue = &$_SESSION['ost_upgrader']['AttachmentMigrater']['queue']; - $this->skipList = &$_SESSION['ost_upgrader']['AttachmentMigrater']['skip']; - $this->errorList = array(); + /** + * Reads update stream information from UPGRADE_DIR/<streams>/streams.cfg. + * Each declared stream folder should contain a file under the name of the + * stream with an 'sig' extension. The file will be the hash of the + * signature of the tip of the stream. A corresponding config variable + * 'schema_signature' should exist in the namespace of the stream itself. + * If the hash file doesn't match the schema_signature on record, then an + * update is triggered and the patches in the stream folder are used to + * upgrade the database. + */ + /* static */ + function getUpgradeStreams($basedir) { + static $streams = array(); + if ($streams) return $streams; + + // TODO: Make the hash algo configurable in the streams + // configuration ( core : md5 ) + $config = @file_get_contents($basedir.'/streams.cfg'); + if (!$config) $config = 'core'; + foreach (explode("\n", $config) as $line) { + $line = trim(preg_replace('/#.*$/', '', $line)); + if (!$line) + continue; + else if (file_exists($basedir."$line.sig") && is_dir($basedir.$line)) + $streams[$line] = + trim(file_get_contents($basedir."$line.sig")); + } + return $streams; } +} +class MigrationTask { + var $description = "[Unnamed task]"; + var $status = "finished"; /** - * Process the migration for a unit of time. This will be used to - * overcome the execution time restriction of PHP. This instance can be - * stashed in a session and have this method called periodically to - * process another batch of attachments + * Function: run + * + * (Abstract) method which will perform the migration task. The task + * does not have to be completed in one invocation; however, if the + * run() method is called again, the task should not be restarted from the + * beginning. The ::isFinished() method will be used to determine if the + * migration task has more work to be done. * - * Returns: - * Number of pending attachments to migrate. + * If ::isFinished() returns boolean false, then the run method will be + * called again. Note that the next invocation may be in a separate + * request. Ensure that you properly capture the state of the task before + * exiting the ::run() method. The entire MigrationTask instance is stored + * in the migration session, so all instance variables will be preserved + * between calls. + * + * Parameters: + * max_time - (int) number of seconds the task should be allowed to run */ - function do_batch($time=30, $max=0) { - - if(!$this->queueAttachments($max) || !$this->getQueueLength()) - return 0; + /* abstract */ + function run($max_time) { } - $count = 0; - $start = Misc::micro_time(); - while ($this->getQueueLength() && (Misc::micro_time()-$start) < $time) - if($this->next() && $max && ++$count>=$max) - break; - - return $this->queueAttachments($max); - - } - - function getSkipList() { - return $this->skipList; - } - - function enqueue($fileinfo) { - $this->queue[] = $fileinfo; - } - - function getQueue() { - return $this->queue; - } - - function getQueueLength() { return count($this->queue); } /** - * Processes the next item on the work queue. Emits a JSON messages to - * indicate current progress. + * Function: isFinished * - * Returns: - * TRUE/NULL if the migration was successful + * Returns boolean TRUE if another call to ::run() is required, and + * false otherwise */ - function next() { - # Fetch next item -- use the last item so the array indices don't - # need to be recalculated for every shift() operation. - $info = array_pop($this->queue); - # Attach file to the ticket - if (!($info['data'] = @file_get_contents($info['path']))) { - # Continue with next file - return $this->skip($info['attachId'], - sprintf('%s: Cannot read file contents', $info['path'])); - } - # Get the mime/type of each file - # XXX: Use finfo_buffer for PHP 5.3+ - if(function_exists('mime_content_type')) { - //XXX: function depreciated in newer versions of PHP!!!!! - $info['type'] = mime_content_type($info['path']); - } elseif (function_exists('finfo_file')) { // PHP 5.3.0+ - $finfo = finfo_open(FILEINFO_MIME_TYPE); - $info['type'] = finfo_file($finfo, $info['path']); - } - # TODO: Add extension-based mime-type lookup + /* abstract */ + function isFinished() { return true; } - if (!($fileId = AttachmentFile::save($info))) { - return $this->skip($info['attachId'], - sprintf('%s: Unable to migrate attachment', $info['path'])); - } - # Update the ATTACHMENT_TABLE record to set file_id - db_query('update '.TICKET_ATTACHMENT_TABLE - .' set file_id='.db_input($fileId) - .' where attach_id='.db_input($info['attachId'])); - # Remove disk image of the file. If this fails, the migration for - # this file would not be retried, because the file_id in the - # TICKET_ATTACHMENT_TABLE has a nonzero value now - if (!@unlink($info['path'])) //XXX: what should we do on failure? - $this->error( - sprintf('%s: Unable to remove file from disk', - $info['path'])); - # TODO: Log an internal note to the ticket? - return true; - } /** - * From (class Ticket::fixAttachments), used to detect the locations of - * attachment files + * Funciton: sleep + * + * Called if isFinished() returns false. The data returned is passed to + * the ::wakeup() method before the ::run() method is called again */ - /* static */ function queueAttachments($limit=0){ - global $cfg, $ost; - - # Since the queue is persistent - we want to make sure we get to empty - # before we find more attachments. - if(($qc=$this->getQueueLength())) - return $qc; - - $sql='SELECT attach_id, file_name, file_key, Ti.created' - .' FROM '.TICKET_ATTACHMENT_TABLE.' TA' - .' INNER JOIN '.TICKET_TABLE.' Ti ON (Ti.ticket_id=TA.ticket_id)' - .' WHERE NOT file_id '; - - if(($skipList=$this->getSkipList())) - $sql.= ' AND attach_id NOT IN('.implode(',', db_input($skipList)).')'; - - if($limit && is_numeric($limit)) - $sql.=' LIMIT '.$limit; + function sleep() { return null; } - //XXX: Do a hard fail or error querying the database? - if(!($res=db_query($sql))) - return $this->error('Unable to query DB for attached files to migrate!'); - - $ost->logDebug('Found '.db_num_rows($res).' attachments to migrate'); - if(!db_num_rows($res)) - return 0; //Nothing else to do!! - - $dir=$cfg->getUploadDir(); - if(!$dir || !is_dir($dir)) //XXX: Abort the upgrade??? Attachments are obviously critical! - return $this->error("Attachment directory [$dir] is invalid - aborting attachment migration"); - - //Clear queue - $this->queue = array(); - while (list($id,$name,$key,$created)=db_fetch_row($res)) { - $month=date('my',strtotime($created)); - $info=array( - 'name'=> $name, - 'attachId'=> $id, - ); - $filename15=sprintf("%s/%s_%s",rtrim($dir,'/'),$key,$name); - $filename16=sprintf("%s/%s/%s_%s",rtrim($dir,'/'),$month,$key,$name); //new destination. - if (file_exists($filename15)) { - $info['path'] = $filename15; - } elseif (file_exists($filename16)) { - $info['path'] = $filename16; - } else { - # XXX Cannot find file for attachment - $this->skip($id, - sprintf('%s: Unable to locate attachment file', - $name)); - # No need to further process this file - continue; - } - # TODO: Get the size and mime/type of each file. - # - # NOTE: If filesize() fails and file_get_contents() doesn't, - # then the AttachmentFile::save() method will automatically - # estimate the filesize based on the length of the string data - # received in $info['data'] -- ie. no need to do that here. - # - # NOTE: The size is done here because it should be quick to - # lookup out of file inode already loaded. The mime/type may - # take a while because it will require a second IO to read the - # file data. To ensure this will finish before the - # max_execution_time, perform the type match in the ::next() - # method since the entire file content will be read there - # anyway. - $info['size'] = @filesize($info['path']); - # Coroutines would be nice .. - $this->enqueue($info); - } - - return $this->queueAttachments($limit); - } - - function skip($attachId, $error) { - - $this->skipList[] = $attachId; + /** + * Function: wakeup + * + * Called before the ::run() method if the migration task was saved in + * the session and run in multiple requests + */ + function wakeup($data) { } - return $this->error($error." (ID #$attachId)"); + function getDescription() { + return $this->description; } - function error($what) { - global $ost; - - $this->errors++; - $this->errorList[] = $what; - $ost->logDebug('Upgrader: Attachment Migrater', $what); - # Assist in returning FALSE for inline returns with this method - return false; + function getStatus() { + return $this->status; } - function getErrors() { - return $this->errorList; + function setStatus($message) { + $this->status = $message; } } + ?> diff --git a/include/class.misc.php b/include/class.misc.php index b6d9a673f6e311a085ce4ab07991e4c3b286f1f9..e913a8de0fdd052b0e9a87ec9693b720cc36e03b 100644 --- a/include/class.misc.php +++ b/include/class.misc.php @@ -14,26 +14,40 @@ vim: expandtab sw=4 ts=4 sts=4: **********************************************************************/ class Misc { - - function randCode($len=8) { - return substr(strtoupper(base_convert(microtime(),10,16)),0,$len); + + function randCode($count=8, $chars=false) { + $chars = $chars ? $chars + : 'abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $data = ''; + $m = strlen($chars) - 1; + for ($i=0; $i < $count; $i++) + $data .= $chars[mt_rand(0,$m)]; + return $data; } - + + function __rand_seed($value=0) { + // Form a 32-bit figure for the random seed with the lower 16-bits + // the microseconds of the current time, and the upper 16-bits from + // received value + $seed = ((int) $value % 65535) << 16; + $seed += (int) ((double) microtime() * 1000000) % 65535; + mt_srand($seed); + } + /* Helper used to generate ticket IDs */ function randNumber($len=6,$start=false,$end=false) { - mt_srand ((double) microtime() * 1000000); $start=(!$len && $start)?$start:str_pad(1,$len,"0",STR_PAD_RIGHT); $end=(!$len && $end)?$end:str_pad(9,$len,"9",STR_PAD_RIGHT); - + return mt_rand($start,$end); } - /* misc date helpers...this will go away once we move to php 5 */ + /* misc date helpers...this will go away once we move to php 5 */ function db2gmtime($var){ global $cfg; if(!$var) return; - + $dbtime=is_int($var)?$var:strtotime($var); return $dbtime-($cfg->getDBTZoffset()*3600); } @@ -41,7 +55,7 @@ class Misc { //Take user time or gmtime and return db (mysql) time. function dbtime($var=null){ global $cfg; - + if(is_null($var) || !$var) $time=Misc::gmtime(); //gm time. else{ //user time to GM. @@ -52,7 +66,7 @@ class Misc { //gm to db time return $time+($cfg->getDBTZoffset()*3600); } - + /*Helper get GM time based on timezone offset*/ function gmtime() { return time()-date('Z'); @@ -67,7 +81,7 @@ class Misc { //Current page function currentURL() { - + $str = 'http'; if ($_SERVER['HTTPS'] == 'on') { $str .='s'; @@ -78,7 +92,7 @@ class Misc { if (isset($_SERVER['QUERY_STRING'])) { $_SERVER['REQUEST_URI'].='?'.$_SERVER['QUERY_STRING']; } - } + } if ($_SERVER['SERVER_PORT']!=80) { $str .= $_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'].$_SERVER['REQUEST_URI']; } else { @@ -92,7 +106,7 @@ class Misc { $hr =is_null($hr)?0:$hr; $min =is_null($min)?0:$min; - //normalize; + //normalize; if($hr>=24) $hr=$hr%24; elseif($hr<0) @@ -106,7 +120,7 @@ class Misc { $min=15; else $min=0; - + ob_start(); echo sprintf('<select name="%s" id="%s">',$name,$name); echo '<option value="" selected>Time</option>'; diff --git a/include/class.nav.php b/include/class.nav.php index ad30e1219f8eb33f597867f64615773a710e2c90..a92a5629ec149579cd3d7883be2aa8ff2ef661fc 100644 --- a/include/class.nav.php +++ b/include/class.nav.php @@ -196,6 +196,7 @@ class AdminNav extends StaffNav{ $subnav[]=array('desc'=>'System Preferences','href'=>'settings.php?t=system','iconclass'=>'preferences'); $subnav[]=array('desc'=>'Tickets','href'=>'settings.php?t=tickets','iconclass'=>'ticket-settings'); $subnav[]=array('desc'=>'Emails','href'=>'settings.php?t=emails','iconclass'=>'email-settings'); + $subnav[]=array('desc'=>'Pages','href'=>'settings.php?t=pages','iconclass'=>'pages'); $subnav[]=array('desc'=>'Knowledgebase','href'=>'settings.php?t=kb','iconclass'=>'kb-settings'); $subnav[]=array('desc'=>'Autoresponder','href'=>'settings.php?t=autoresp','iconclass'=>'email-autoresponders'); $subnav[]=array('desc'=>'Alerts & Notices','href'=>'settings.php?t=alerts','iconclass'=>'alert-settings'); @@ -206,6 +207,7 @@ class AdminNav extends StaffNav{ 'title'=>'Ticket Filters','iconclass'=>'ticketFilters'); $subnav[]=array('desc'=>'SLA Plans','href'=>'slas.php','iconclass'=>'sla'); $subnav[]=array('desc'=>'API Keys','href'=>'apikeys.php','iconclass'=>'api'); + $subnav[]=array('desc'=>'Site Pages', 'href'=>'pages.php','title'=>'Pages','iconclass'=>'pages'); break; case 'emails': $subnav[]=array('desc'=>'Emails','href'=>'emails.php', 'title'=>'Email Addresses', 'iconclass'=>'emailSettings'); diff --git a/include/class.osticket.php b/include/class.osticket.php index 512d390e284bdd64b7587f523f71c2ec75712fe4..8755d3278fda537bd02e4454251b35a1dcbc1e58 100644 --- a/include/class.osticket.php +++ b/include/class.osticket.php @@ -20,6 +20,7 @@ require_once(INCLUDE_DIR.'class.config.php'); //Config helper require_once(INCLUDE_DIR.'class.csrf.php'); //CSRF token class. +require_once(INCLUDE_DIR.'class.migrater.php'); define('LOG_WARN',LOG_WARNING); @@ -46,15 +47,11 @@ class osTicket { var $session; var $csrf; - function osTicket($cfgId) { + function osTicket() { - $this->config = Config::lookup($cfgId); + $this->session = osTicketSession::start(SESSION_TTL); // start DB based session - //DB based session storage was added starting with v1.7 - if($this->config && !$this->getConfig()->getDBVersion()) - $this->session = osTicketSession::start(SESSION_TTL); // start DB based session - else - session_start(); + $this->config = new OsticketConfig(); $this->csrf = new CSRF('__CSRFToken__'); } @@ -64,7 +61,11 @@ class osTicket { } function isUpgradePending() { - return (defined('SCHEMA_SIGNATURE') && strcasecmp($this->getDBSignature(), SCHEMA_SIGNATURE)); + foreach (DatabaseMigrater::getUpgradeStreams(UPGRADE_DIR.'streams/') as $stream=>$hash) + if (strcasecmp($hash, + $this->getConfig()->getSchemaSignature($stream))) + return true; + return false; } function getSession() { @@ -75,13 +76,8 @@ class osTicket { return $this->config; } - function getConfigId() { - - return $this->getConfig()?$this->getConfig()->getId():0; - } - - function getDBSignature() { - return $this->getConfig()->getSchemaSignature(); + function getDBSignature($namespace='core') { + return $this->getConfig()->getSchemaSignature($namespace); } function getVersion() { @@ -237,7 +233,7 @@ class osTicket { if($email) { $email->sendAlert($to, $subject, $message); } else {//no luck - try the system mail. - Email::sendmail($to, $subject, $message, sprintf('"osTicket Alerts"<%s>',$to)); + Mailer::sendmail($to, $subject, $message, sprintf('"osTicket Alerts"<%s>',$to)); } //log the alert? Watch out for loops here. @@ -246,8 +242,8 @@ class osTicket { } - function logDebug($title, $message, $alert=false) { - return $this->log(LOG_DEBUG, $title, $message, $alert); + function logDebug($title, $message, $force=false) { + return $this->log(LOG_DEBUG, $title, $message, false, $force); } function logInfo($title, $message, $alert=false) { @@ -270,7 +266,7 @@ class osTicket { return $this->log(LOG_ERR, $title, $error, $alert); } - function log($priority, $title, $message, $alert=false) { + function log($priority, $title, $message, $alert=false, $force=false) { //We are providing only 3 levels of logs. Windows style. switch($priority) { @@ -296,18 +292,18 @@ class osTicket { $this->alertAdmin($title, $message); //Logging everything during upgrade. - if($this->getConfig()->getLogLevel()<$level && !$this->isUpgradePending()) + if($this->getConfig()->getLogLevel()<$level && !$force) return false; //Save log based on system log level settings. $loglevel=array(1=>'Error','Warning','Debug'); - $sql='INSERT INTO '.SYSLOG_TABLE.' SET created=NOW(), updated=NOW() '. - ',title='.db_input($title). - ',log_type='.db_input($loglevel[$level]). - ',log='.db_input($message). - ',ip_address='.db_input($_SERVER['REMOTE_ADDR']); + $sql='INSERT INTO '.SYSLOG_TABLE.' SET created=NOW(), updated=NOW() ' + .',title='.db_input(Format::sanitize($title, true)) + .',log_type='.db_input($loglevel[$level]) + .',log='.db_input(Format::sanitize($message, false)) + .',ip_address='.db_input($_SERVER['REMOTE_ADDR']); - mysql_query($sql); //don't use db_query to avoid possible loop. + db_query($sql, false); return true; } @@ -356,6 +352,16 @@ class osTicket { return null; } + /** + * Returns TRUE if the request was made via HTTPS and false otherwise + */ + function is_https() { + return (isset($_SERVER['HTTPS']) + && strtolower($_SERVER['HTTPS']) == 'on') + || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) + && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https'); + } + /* returns true if script is being executed via commandline */ function is_cli() { return (!strcasecmp(substr(php_sapi_name(), 0, 3), 'cli') @@ -364,12 +370,12 @@ class osTicket { } /**** static functions ****/ - function start($configId) { + function start() { - if(!$configId || !($ost = new osTicket($configId)) || $ost->getConfigId()!=$configId) + if(!($ost = new osTicket())) return null; - //Set default time zone... user/staff settting will overwrite it (on login). + //Set default time zone... user/staff settting will override it (on login). $_SESSION['TZ_OFFSET'] = $ost->getConfig()->getTZoffset(); $_SESSION['TZ_DST'] = $ost->getConfig()->observeDaylightSaving(); diff --git a/include/class.ostsession.php b/include/class.ostsession.php index efcca58afb9683dd082a0f3e0b6c6075161c31ff..7541e19ec04aba50072abb7e495e48725316f5c7 100644 --- a/include/class.ostsession.php +++ b/include/class.ostsession.php @@ -17,67 +17,75 @@ class osTicketSession { var $ttl = SESSION_TTL; + var $data = ''; + var $id = ''; function osTicketSession($ttl=0){ - $this->ttl =$ttl?$ttl:get_cfg_var('session.gc_maxlifetime'); if(!$this->ttl) $this->ttl=SESSION_TTL; - //Set handlers. - session_set_save_handler( - array(&$this, 'open'), - array(&$this, 'close'), - array(&$this, 'read'), - array(&$this, 'write'), - array(&$this, 'destroy'), - array(&$this, 'gc') - ); - //Forced cleanup. - register_shutdown_function('session_write_close'); + if (!defined('DISABLE_SESSION') && !OsticketConfig::getDBVersion()) { + //Set handlers. + session_set_save_handler( + array(&$this, 'open'), + array(&$this, 'close'), + array(&$this, 'read'), + array(&$this, 'write'), + array(&$this, 'destroy'), + array(&$this, 'gc') + ); + //Forced cleanup. + register_shutdown_function('session_write_close'); + } //Start the session. + session_name('OSTSESSID'); session_start(); } - + function regenerate_id(){ $oldId = session_id(); session_regenerate_id(); $this->destroy($oldId); } - + function open($save_path, $session_name){ return (true); } - + function close(){ return (true); } - + function read($id){ - $data=""; - $sql='SELECT session_data FROM '.SESSION_TABLE - .' WHERE session_id='.db_input($id) - .' AND session_expire>NOW()'; - if(($res=db_query($sql)) && db_num_rows($res)) - list($data)=db_fetch_row($res); - - return $data; + if (!$this->data || $this->id != $id) { + $sql='SELECT session_data FROM '.SESSION_TABLE + .' WHERE session_id='.db_input($id) + .' AND session_expire>NOW()'; + if(!($res=db_query($sql))) + return false; + elseif (db_num_rows($res)) + list($this->data)=db_fetch_row($res); + $this->id = $id; + } + return $this->data; } function write($id, $data){ global $thisstaff; - $ttl = ($this && get_class($this) == 'osTicketSession') + $ttl = ($this && get_class($this) == 'osTicketSession') ? $this->getTTL() : SESSION_TTL; $sql='REPLACE INTO '.SESSION_TABLE.' SET session_updated=NOW() '. ',session_id='.db_input($id). - ',session_data='.db_input($data). + ',session_data=0x'.bin2hex($data). ',session_expire=(NOW() + INTERVAL '.$ttl.' SECOND)'. ',user_id='.db_input($thisstaff?$thisstaff->getId():0). ',user_ip='.db_input($_SERVER['REMOTE_ADDR']). ',user_agent='.db_input($_SERVER['HTTP_USER_AGENT']); + $this->data = ''; return (db_query($sql) && db_affected_rows()); } @@ -85,7 +93,7 @@ class osTicketSession { $sql='DELETE FROM '.SESSION_TABLE.' WHERE session_id='.db_input($id); return (db_query($sql) && db_affected_rows()); } - + function gc($maxlife){ $sql='DELETE FROM '.SESSION_TABLE.' WHERE session_expire<NOW()'; db_query($sql); diff --git a/include/class.page.php b/include/class.page.php new file mode 100644 index 0000000000000000000000000000000000000000..c5b2f83bdf1f9c06028dbed47bd1a83cb12e895d --- /dev/null +++ b/include/class.page.php @@ -0,0 +1,261 @@ +<?php +/********************************************************************* + class.page.php + + Page class + + Copyright (c) 2006-2013 osTicket + http://www.osticket.com + + Released under the GNU General Public License WITHOUT ANY WARRANTY. + See LICENSE.TXT for details. + + vim: expandtab sw=4 ts=4 sts=4: +**********************************************************************/ + +class Page { + + var $id; + var $ht; + + function Page($id) { + $this->id=0; + $this->ht = array(); + $this->load($id); + } + + function load($id=0) { + + if(!$id && !($id=$this->getId())) + return false; + + $sql='SELECT page.*, count(topic.page_id) as topics ' + .' FROM '.PAGE_TABLE.' page ' + .' LEFT JOIN '.TOPIC_TABLE. ' topic ON(topic.page_id=page.id) ' + .' WHERE page.id='.db_input($id) + .' GROUP By page.id'; + + if (!($res=db_query($sql)) || !db_num_rows($res)) + return false; + + $this->ht = db_fetch_array($res); + $this->id = $this->ht['id']; + + return true; + } + + function reload() { + return $this->load(); + } + + function getId() { + return $this->id; + } + + function getHashtable() { + return $this->ht; + } + + function getType() { + return $this->ht['type']; + } + + function getName() { + return $this->ht['name']; + } + + function getBody() { + return $this->ht['body']; + } + + function getNotes() { + return $this->ht['notes']; + } + + function isActive() { + return ($this->ht['isactive']); + } + + function isInUse() { + global $cfg; + + return ($this->getNumTopics() + || in_array($this->getId(), $cfg->getDefaultPages())); + } + + + function getCreateDate() { + return $this->ht['created']; + } + + function getUpdateDate() { + return $this->ht['updated']; + } + + function getNumTopics() { + return $this->ht['topics']; + } + + function update($vars, &$errors) { + + if(!$vars['isactive'] && $this->isInUse()) { + $errors['err'] = 'A page currently in-use CANNOT be disabled!'; + $errors['isactive'] = 'Page is in-use!'; + } + + if($errors || !$this->save($this->getId(), $vars, $errors)) + return false; + + $this->reload(); + + return true; + } + + function disable() { + + if(!$this->isActive()) + return true; + + if($this->isInUse()) + return false; + + + $sql=' UPDATE '.PAGE_TABLE.' SET isactive=0 ' + .' WHERE id='.db_input($this->getId()); + + if(!db_query($sql) || !db_affected_rows()) + return false; + + $this->reload(); + + return true; + } + + function delete() { + + if($this->isInUse()) + return false; + + $sql='DELETE FROM '.PAGE_TABLE + .' WHERE id='.db_input($this->getId()) + .' LIMIT 1'; + + if(!db_query($sql) || !db_affected_rows()) + return false; + + db_query('UPDATE '.TOPIC_TABLE.' SET page_id=0 WHERE page_id='.db_input($this->getId())); + + return true; + } + + /* ------------------> Static methods <--------------------- */ + + function add($vars, &$errors) { + if(!($id=self::create($vars, $errors))) + return false; + + return self::lookup($id); + } + + function create($vars, &$errors) { + return self::save(0, $vars, $errors); + } + + function getPages($criteria=array()) { + + $sql = ' SELECT id FROM '.PAGE_TABLE.' WHERE 1'; + if(isset($criteria['active'])) + $sql.=' AND isactive='.db_input($criteria['active']?1:0); + if(isset($criteria['type'])) + $sql.=' AND `type`='.db_input($criteria['type']); + + $sql.=' ORDER BY name'; + + $pages = array(); + if(($res=db_query($sql)) && db_num_rows($res)) + while(list($id) = db_fetch_row($res)) + $pages[] = Page::lookup($id); + + return array_filter($pages); + } + + function getActivePages($criteria=array()) { + + $criteria = array_merge($criteria, array('active'=>true)); + + return self::getPages($criteria); + } + + function getActiveThankYouPages() { + return self::getActivePages(array('type' => 'thank-you')); + } + + function getIdByName($name) { + + $id = 0; + $sql = ' SELECT id FROM '.PAGE_TABLE.' WHERE name='.db_input($name); + if(($res=db_query($sql)) && db_num_rows($res)) + list($id) = db_fetch_row($res); + + return $id; + } + + function lookup($id) { + return ($id + && is_numeric($id) + && ($p= new Page($id)) + && $p->getId()==$id) + ? $p : null; + } + + function save($id, $vars, &$errors) { + + //Cleanup. + $vars['name']=Format::striptags(trim($vars['name'])); + + //validate + if($id && $id!=$vars['id']) + $errors['err'] = 'Internal error. Try again'; + + if(!$vars['type']) + $errors['type'] = 'Type required'; + elseif(!in_array($vars['type'], array('landing', 'offline', 'thank-you', 'other'))) + $errors['type'] = 'Invalid selection'; + + if(!$vars['name']) + $errors['name'] = 'Name required'; + elseif(($pid=self::getIdByName($vars['name'])) && $pid!=$id) + $errors['name'] = 'Name already exists'; + + if(!$vars['body']) + $errors['body'] = 'Page body is required'; + + if($errors) return false; + + //save + $sql=' updated=NOW() ' + .', `type`='.db_input($vars['type']) + .', name='.db_input($vars['name']) + .', body='.db_input(Format::safe_html($vars['body'])) + .', isactive='.db_input($vars['isactive'] ? 1 : 0) + .', notes='.db_input($vars['notes']); + + if($id) { + $sql='UPDATE '.PAGE_TABLE.' SET '.$sql.' WHERE id='.db_input($id); + if(db_query($sql)) + return true; + + $errors['err']='Unable to update page.'; + + } else { + $sql='INSERT INTO '.PAGE_TABLE.' SET '.$sql.', created=NOW()'; + if(db_query($sql) && ($id=db_insert_id())) + return $id; + + $errors['err']='Unable to create page. Internal error'; + } + + return false; + } +} +?> diff --git a/include/class.setup.php b/include/class.setup.php index af44e4d22703f82fe2f9e32dbfb1294a4d3bfac0..4b73c257e4922dffc3ee4c8d60035f9c20d55473 100644 --- a/include/class.setup.php +++ b/include/class.setup.php @@ -21,7 +21,7 @@ Class SetupWizard { 'mysql' => '4.4'); //Version info - same as the latest version. - + var $version =THIS_VERSION; var $version_verbose = THIS_VERSION; @@ -30,12 +30,12 @@ Class SetupWizard { function SetupWizard(){ $this->errors=array(); - $this->version_verbose = ('osTicket v'. strtoupper(THIS_VERSION)); + $this->version_verbose = ('osTicket '. strtoupper(THIS_VERSION)); } function load_sql_file($file, $prefix, $abort=true, $debug=false) { - + if(!file_exists($file) || !($schema=file_get_contents($file))) return $this->abort('Error accessing SQL file '.basename($file), $debug); @@ -49,18 +49,17 @@ Class SetupWizard { # Strip comments and remarks $schema=preg_replace('%^\s*(#|--).*$%m', '', $schema); - # Replace table prefis + # Replace table prefix $schema = str_replace('%TABLE_PREFIX%', $prefix, $schema); - # Split by semicolons - and cleanup + # Split by semicolons - and cleanup if(!($statements = array_filter(array_map('trim', @explode(';', $schema))))) return $this->abort('Error parsing SQL schema', $debug); - @mysql_query('SET SESSION SQL_MODE =""'); + db_query('SET SESSION SQL_MODE =""', false); foreach($statements as $k=>$sql) { - //Note that we're not using db_query - because we want to control how errors are reported. - if(mysql_query($sql)) continue; - $error = "[$sql] ".mysql_error(); + if(db_query($sql, false)) continue; + $error = "[$sql] ".db_error(); if($abort) return $this->abort($error, $debug); } @@ -100,7 +99,7 @@ Class SetupWizard { @error is a mixed var. */ function abort($error, $debug=false) { - + if($debug) echo $error; $this->onError($error); @@ -108,7 +107,7 @@ Class SetupWizard { } function setError($error) { - + if($error && is_array($error)) $this->errors = array_merge($this->errors, $error); elseif($error) diff --git a/include/class.signal.php b/include/class.signal.php new file mode 100644 index 0000000000000000000000000000000000000000..f931acebc50c8beaf020859279c2ba0cba56f1de --- /dev/null +++ b/include/class.signal.php @@ -0,0 +1,104 @@ +<?php +/********************************************************************* + class.signal.php + + Simple interface for a publish and subscribe signal model + + Jared Hancock <jared@osticket.com> + Copyright (c) 2006-2013 osTicket + http://www.osticket.com + + Released under the GNU General Public License WITHOUT ANY WARRANTY. + See LICENSE.TXT for details. + + vim: expandtab sw=4 ts=4 sts=4: +**********************************************************************/ + +/** + * Signals implement a simple publish/subscribe event model. To keep things + * simplistic between classes and to maintain compatible with PHP version 4, + * signals will not be explicitly defined or registered. Instead, signals + * are connected to callbacks via a string signal name. + * + * The system is proofed with a static inspection test which will ensure + * that for every given Signal::connect() function call, somewhere else in + * the codebase there exists a Signal::send() for the same named signal. + */ +class Signal { + /** + * Subscribe to a signal. + * + * Signal::connect('user.auth', 'function'); + * + * The subscribed function should receive a two arguments and will have + * this signature: + * + * function callback($object, $data); + * + * Where the $object argument is the object originating the signal, and + * the $options is a hash-array of other information originating from- + * and pertaining to the signal. + * + * The value of the $data argument is not defined. It is signal + * specific. It should be a hash-array of data; however, no runtime + * checks are made to ensure such an interface. + * + * Optionally, if $object is a class and is passed into the ::connect() + * method, only instances of the named class or subclass will actually + * be connected to the callable function. + * + * A predicate function, $check, can be used to filter calls to the + * signal handler. The function will receive the signal data and should + * return true if the signal handler should be called. + */ + /*static*/ function connect($signal, $callable, $object=null, + $check=null) { + global $_subscribers; + if (!isset($_subscribers[$signal])) $_subscribers[$signal] = array(); + // XXX: Ensure $object if set is a class + if ($object && !is_string($object)) + trigger_error("Invalid object: $object: Expected class"); + elseif ($check && !is_callable($check)) { + trigger_error("Invalid check function: Must be callable"); + $check = null; + } + $_subscribers[$signal][] = array($object, $callable, $check); + } + + /** + * Publish a signal. + * + * Signal::send('user.login', $this, array('username'=>'blah')); + * + * All subscribers to the signal will be called in the order they + * connect()ed to the signal. Subscribers do not have the opportunity to + * interrupt or discontinue delivery of the signal to other subscribers. + * The $object argument is required and should almost always be ($this). + * Its interpretation is the object originating or sending the signal. + * It could also be interpreted as the context of the signal. + * + * $data if sent should be a hash-array of data included with the signal + * event. There is otherwise no definition for what should or could be + * included in the $data array. The received data is received by + * reference and can be passed to the callable by reference if the + * callable is defined to receive it by reference. Therefore, it is + * possible to propogate changes in the signal handlers back to the + * originating context. + */ + /*static*/ function send($signal, $object, &$data=null) { + global $_subscribers; + if (!isset($_subscribers[$signal])) + return; + foreach ($_subscribers[$signal] as $sub) { + list($s, $callable, $check) = $sub; + if ($s && !is_a($object, $s)) + continue; + elseif ($check && !call_user_func($check, $data)) + continue; + call_user_func($callable, $object, $data); + } + } +} + +$_subscribers = array(); +?> diff --git a/include/class.sla.php b/include/class.sla.php index a04d1d7c56bc1d417bcc0cab720ff9be0398931e..59f320472e59c95e543c7e02fca8abe8f8710a18 100644 --- a/include/class.sla.php +++ b/include/class.sla.php @@ -18,6 +18,7 @@ class SLA { var $id; var $info; + var $config; function SLA($id) { $this->id=0; @@ -53,23 +54,33 @@ class SLA { function getGracePeriod() { return $this->ht['grace_period']; } - + function getNotes() { return $this->ht['notes']; } function getHashtable() { - return $this->ht; + return array_merge($this->getConfig()->getInfo(), $this->ht); } function getInfo() { return $this->getHashtable(); } + function getConfig() { + if (!isset($this->config)) + $this->config = new SlaConfig($this->getId()); + return $this->config; + } + function isActive() { return ($this->ht['isactive']); } + function isTransient() { + return $this->getConfig()->get('transient', false); + } + function sendAlerts() { return (!$this->ht['disable_overdue_alerts']); } @@ -83,11 +94,12 @@ class SLA { } function update($vars,&$errors) { - + if(!SLA::save($this->getId(),$vars,$errors)) return false; $this->reload(); + $this->getConfig()->set('transient', isset($vars['transient']) ? 1 : 0); return true; } @@ -111,7 +123,10 @@ class SLA { /** static functions **/ function create($vars,&$errors) { - return SLA::save(0,$vars,$errors); + if (($id = SLA::save(0,$vars,$errors)) && ($sla = self::lookup($id))) + $sla->getConfig()->set('transient', + isset($vars['transient']) ? 1 : 0); + return $id; } function getSLAs() { @@ -121,8 +136,8 @@ class SLA { if(($res=db_query($sql)) && db_num_rows($res)) { while($row=db_fetch_array($res)) $slas[$row['id']] = sprintf('%s (%d hrs - %s)', - $row['name'], - $row['grace_period'], + $row['name'], + $row['grace_period'], $row['isactive']?'Active':'Disabled'); } @@ -150,7 +165,7 @@ class SLA { $errors['grace_period']='Grace period required'; elseif(!is_numeric($vars['grace_period'])) $errors['grace_period']='Numeric value required (in hours)'; - + if(!$vars['name']) $errors['name']='Name required'; elseif(($sid=SLA::getIdByName($vars['name'])) && $sid!=$id) @@ -183,4 +198,13 @@ class SLA { return false; } } + +require_once(INCLUDE_DIR.'class.config.php'); +class SlaConfig extends Config { + var $table = CONFIG_TABLE; + + function SlaConfig($id) { + parent::Config("sla.$id"); + } +} ?> diff --git a/include/class.staff.php b/include/class.staff.php index 414c835cccd07522626eef9ab0f2f9fa573da32d..e0220120c0ca497bc12482296d6e08ffe84c257f 100644 --- a/include/class.staff.php +++ b/include/class.staff.php @@ -15,12 +15,13 @@ **********************************************************************/ include_once(INCLUDE_DIR.'class.ticket.php'); include_once(INCLUDE_DIR.'class.dept.php'); +include_once(INCLUDE_DIR.'class.error.php'); include_once(INCLUDE_DIR.'class.team.php'); include_once(INCLUDE_DIR.'class.group.php'); include_once(INCLUDE_DIR.'class.passwd.php'); class Staff { - + var $ht; var $id; @@ -30,7 +31,7 @@ class Staff { var $teams; var $timezone; var $stats; - + function Staff($var) { $this->id =0; return ($this->load($var)); @@ -41,16 +42,22 @@ class Staff { if(!$var && !($var=$this->getId())) return false; - $sql='SELECT staff.*, staff.created as added, grp.* ' + $sql='SELECT staff.created as added, grp.*, staff.* ' .' FROM '.STAFF_TABLE.' staff ' - .' LEFT JOIN '.GROUP_TABLE.' grp ON(grp.group_id=staff.group_id) '; + .' LEFT JOIN '.GROUP_TABLE.' grp ON(grp.group_id=staff.group_id) + WHERE '; - $sql.=sprintf(' WHERE %s=%s',is_numeric($var)?'staff_id':'username',db_input($var)); + if (is_numeric($var)) + $sql .= 'staff_id='.db_input($var); + elseif (Validator::is_email($var)) + $sql .= 'email='.db_input($var); + else + $sql .= 'username='.db_input($var); if(!($res=db_query($sql)) || !db_num_rows($res)) return NULL; - + $this->ht=db_fetch_array($res); $this->id = $this->ht['staff_id']; $this->teams = $this->ht['teams'] = array(); @@ -117,7 +124,7 @@ class Staff { /* check if passwd reset is due. */ function isPasswdResetDue() { global $cfg; - return ($cfg && $cfg->getPasswdResetPeriod() + return ($cfg && $cfg->getPasswdResetPeriod() && $this->ht['passwd_change']>($cfg->getPasswdResetPeriod()*30*24*60*60)); } @@ -160,15 +167,15 @@ class Staff { function getName() { return ucfirst($this->ht['firstname'].' '.$this->ht['lastname']); } - + function getFirstName() { return $this->ht['firstname']; } - + function getLastName() { return $this->ht['lastname']; } - + function getSignature() { return $this->ht['signature']; } @@ -221,13 +228,13 @@ class Staff { array('manager' => $this->getId()) ))?array_keys($depts):array(); } - + function getGroupId() { return $this->ht['group_id']; } function getGroup() { - + if(!$this->group && $this->getGroupId()) $this->group = Group::lookup($this->getGroupId()); @@ -266,7 +273,7 @@ class Staff { function isVisible() { return ($this->ht['isvisible']); } - + function onVacation() { return ($this->ht['onvacation']); } @@ -282,7 +289,7 @@ class Staff { function isAccessLimited() { return $this->showAssignedOnly(); } - + function isAdmin() { return ($this->ht['isadmin']); } @@ -306,7 +313,7 @@ class Staff { function canDeleteTickets() { return ($this->ht['can_delete_tickets']); } - + function canCloseTickets() { return ($this->ht['can_close_tickets']); } @@ -330,10 +337,10 @@ class Staff { function canBanEmails() { return ($this->ht['can_ban_emails']); } - + function canManageTickets() { - return ($this->isAdmin() - || $this->canDeleteTickets() + return ($this->isAdmin() + || $this->canDeleteTickets() || $this->canCloseTickets()); } @@ -359,7 +366,7 @@ class Staff { } function getTeams() { - + if(!$this->teams) { $sql='SELECT team_id FROM '.TEAM_MEMBER_TABLE .' WHERE staff_id='.db_input($this->getId()); @@ -395,6 +402,7 @@ class Staff { //Staff profile update...unfortunately we have to separate it from admin update to avoid potential issues function updateProfile($vars, &$errors) { + global $cfg; $vars['firstname']=Format::striptags($vars['firstname']); $vars['lastname']=Format::striptags($vars['lastname']); @@ -405,7 +413,7 @@ class Staff { if(!$vars['firstname']) $errors['firstname']='First name required'; - + if(!$vars['lastname']) $errors['lastname']='Last name required'; @@ -430,8 +438,18 @@ class Staff { $errors['passwd1']='Must be at least 6 characters'; elseif($vars['passwd1'] && strcmp($vars['passwd1'], $vars['passwd2'])) $errors['passwd2']='Password(s) do not match'; - - if(!$vars['cpasswd']) + + if (($rtoken = $_SESSION['_staff']['reset-token'])) { + $_config = new Config('pwreset'); + if ($_config->get($rtoken) != $this->getId()) + $errors['err'] = + 'Invalid reset token. Logout and try again'; + elseif (!($ts = $_config->lastModified($rtoken)) + && ($cfg->getPwResetWindow() < (time() - strtotime($ts)))) + $errors['err'] = + 'Invalid reset token. Logout and try again'; + } + elseif(!$vars['cpasswd']) $errors['cpasswd']='Current password required'; elseif(!$this->cmp_passwd($vars['cpasswd'])) $errors['cpasswd']='Invalid current password!'; @@ -464,8 +482,12 @@ class Staff { .' ,default_paper_size='.db_input($vars['default_paper_size']); - if($vars['passwd1']) + if($vars['passwd1']) { $sql.=' ,change_passwd=0, passwdreset=NOW(), passwd='.db_input(Passwd::hash($vars['passwd1'])); + $info = array('password' => $vars['passwd1']); + Signal::send('auth.pwchange', $this, $info); + $this->cancelResetTokens(); + } $sql.=' WHERE staff_id='.db_input($this->getId()); @@ -488,7 +510,7 @@ class Staff { $sql='DELETE FROM '.TEAM_MEMBER_TABLE.' WHERE staff_id='.db_input($this->getId()); if($teams) $sql.=' AND team_id NOT IN('.implode(',', db_input($teams)).')'; - + db_query($sql); return true; @@ -501,7 +523,9 @@ class Staff { $this->updateTeams($vars['teams']); $this->reload(); - + + Signal::send('model.modified', $this); + return true; } @@ -520,6 +544,8 @@ class Staff { db_query('DELETE FROM '.TEAM_MEMBER_TABLE.' WHERE staff_id='.db_input($id)); } + Signal::send('model.deleted', $this); + return $num; } @@ -557,7 +583,7 @@ class Staff { return $id; } function getIdByEmail($email) { - + $sql='SELECT staff_id FROM '.STAFF_TABLE.' WHERE email='.db_input($email); if(($res=db_query($sql)) && db_num_rows($res)) list($id) = db_fetch_row($res); @@ -566,7 +592,7 @@ class Staff { } function lookup($id) { - return ($id && is_numeric($id) && ($staff= new Staff($id)) && $staff->getId()==$id)?$staff:null; + return ($id && ($staff= new Staff($id)) && $staff->getId()) ? $staff : null; } function login($username, $passwd, &$errors, $strike=true) { @@ -584,39 +610,23 @@ class Staff { } } - if(!$username || !$passwd) + if(!$username || !$passwd || is_numeric($username)) $errors['err'] = 'Username and password required'; if($errors) return false; - + if(($user=new StaffSession(trim($username))) && $user->getId() && $user->check_passwd($passwd)) { - //update last login && password reset stuff. - $sql='UPDATE '.STAFF_TABLE.' SET lastlogin=NOW() '; - if($user->isPasswdResetDue() && !$user->isAdmin()) - $sql.=',change_passwd=1'; - $sql.=' WHERE staff_id='.db_input($user->getId()); - db_query($sql); - //Now set session crap and lets roll baby! - $_SESSION['_staff'] = array(); //clear. - $_SESSION['_staff']['userID'] = $username; - $user->refreshSession(); //set the hash. - $_SESSION['TZ_OFFSET'] = $user->getTZoffset(); - $_SESSION['TZ_DST'] = $user->observeDaylight(); - - //Log debug info. - $ost->logDebug('Staff login', - sprintf("%s logged in [%s]", $user->getUserName(), $_SERVER['REMOTE_ADDR'])); //Debug. - - //Regenerate session id. - $sid=session_id(); //Current id - session_regenerate_id(TRUE); - //Destroy old session ID - needed for PHP version < 5.1.0 TODO: remove when we move to php 5.3 as min. requirement. - if(($session=$ost->getSession()) && is_object($session) && $sid!=session_id()) - $session->destroy($sid); - + self::_do_login($user, $username); + + Signal::send('auth.login.succeeded', $user); + $user->cancelResetTokens(); + return $user; } - + + $info = array('username'=>$username, 'password'=>$passwd); + Signal::send('auth.login.failed', null, $info); + //If we get to this point we know the login failed. $_SESSION['_staff']['strikes']+=1; if(!$errors && $_SESSION['_staff']['strikes']>$cfg->getStaffMaxLogins()) { @@ -626,7 +636,7 @@ class Staff { 'Username: '.$username."\n".'IP: '.$_SERVER['REMOTE_ADDR']."\n".'TIME: '.date('M j, Y, g:i a T')."\n\n". 'Attempts #'.$_SESSION['_staff']['strikes']."\n".'Timeout: '.($cfg->getStaffLoginTimeout()/60)." minutes \n\n"; $ost->logWarning('Excessive login attempts ('.$username.')', $alert, ($cfg->alertONLoginError())); - + } elseif($_SESSION['_staff']['strikes']%2==0) { //Log every other failed login attempt as a warning. $alert='Username: '.$username."\n".'IP: '.$_SERVER['REMOTE_ADDR']. "\n".'TIME: '.date('M j, Y, g:i a T')."\n\n".'Attempts #'.$_SESSION['_staff']['strikes']; @@ -636,15 +646,89 @@ class Staff { return false; } + function _do_login($user, $username) { + global $ost; + + //update last login && password reset stuff. + $sql='UPDATE '.STAFF_TABLE.' SET lastlogin=NOW() '; + if($user->isPasswdResetDue() && !$user->isAdmin()) + $sql.=',change_passwd=1'; + $sql.=' WHERE staff_id='.db_input($user->getId()); + db_query($sql); + //Now set session crap and lets roll baby! + $_SESSION['_staff'] = array(); //clear. + $_SESSION['_staff']['userID'] = $user->getId(); + $user->refreshSession(); //set the hash. + $_SESSION['TZ_OFFSET'] = $user->getTZoffset(); + $_SESSION['TZ_DST'] = $user->observeDaylight(); + + //Log debug info. + $ost->logDebug('Staff login', + sprintf("%s logged in [%s]", $user->getUserName(), $_SERVER['REMOTE_ADDR'])); //Debug. + + //Regenerate session id. + $sid=session_id(); //Current id + session_regenerate_id(TRUE); + //Destroy old session ID - needed for PHP version < 5.1.0 TODO: remove when we move to php 5.3 as min. requirement. + if(($session=$ost->getSession()) && is_object($session) && $sid!=session_id()) + $session->destroy($sid); + + return $user; + } + function create($vars, &$errors) { - if(($id=self::save(0, $vars, $errors)) && $vars['teams'] && ($staff=Staff::lookup($id))) + if(($id=self::save(0, $vars, $errors)) && $vars['teams'] && ($staff=Staff::lookup($id))) { $staff->updateTeams($vars['teams']); + Signal::send('model.created', $staff); + } return $id; } + function cancelResetTokens() { + // TODO: Drop password-reset tokens from the config table for + // this user id + $sql = 'DELETE FROM '.CONFIG_TABLE.' WHERE `namespace`="pwreset" + AND `value`='.db_input($this->getId()); + db_query($sql); + unset($_SESSION['_staff']['reset-token']); + } + + function sendResetEmail() { + global $ost, $cfg; + + if(!($tpl = $this->getDept()->getTemplate())) + $tpl= $ost->getConfig()->getDefaultTemplate(); + + $token = Misc::randCode(48); // 290-bits + if (!($template = $tpl->getMsgTemplate('staff.pwreset'))) + return new Error('Unable to retrieve password reset email template'); + + $vars = array( + 'url' => $ost->getConfig()->getBaseUrl(), + 'token' => $token, + 'reset_link' => sprintf( + "%s/scp/pwreset.php?token=%s", + $ost->getConfig()->getBaseUrl(), + $token), + ); + + if(!($email=$cfg->getAlertEmail())) + $email = $cfg->getDefaultEmail(); + + $info = array('email' => $email, 'vars' => &$vars); + Signal::send('auth.pwreset.email', $this, $info); + + $msg = $ost->replaceTemplateVariables($template->asArray(), $vars); + + $_config = new Config('pwreset'); + $_config->set($vars['token'], $this->getId()); + + $email->send($this->getEmail(), $msg['subj'], $msg['body']); + } + function save($id, $vars, &$errors) { - + $vars['username']=Format::striptags($vars['username']); $vars['firstname']=Format::striptags($vars['firstname']); $vars['lastname']=Format::striptags($vars['lastname']); @@ -652,27 +736,28 @@ class Staff { if($id && $id!=$vars['id']) $errors['err']='Internal Error'; - + if(!$vars['firstname']) $errors['firstname']='First name required'; if(!$vars['lastname']) $errors['lastname']='Last name required'; - - if(!$vars['username'] || strlen($vars['username'])<2) - $errors['username']='Username required'; + + $error = ''; + if(!$vars['username'] || !Validator::is_username($vars['username'], $error)) + $errors['username']=($error) ? $error : 'Username required'; elseif(($uid=Staff::getIdByUsername($vars['username'])) && $uid!=$id) - $errors['username']='Username already in-use'; - + $errors['username']='Username already in use'; + if(!$vars['email'] || !Validator::is_email($vars['email'])) $errors['email']='Valid email required'; elseif(Email::getIdByEmail($vars['email'])) $errors['email']='Already in-use system email'; elseif(($uid=Staff::getIdByEmail($vars['email'])) && $uid!=$id) - $errors['email']='Email already in-use by another staff member'; + $errors['email']='Email already in use by another staff member'; if($vars['phone'] && !Validator::is_phone($vars['phone'])) $errors['phone']='Valid number required'; - + if($vars['mobile'] && !Validator::is_phone($vars['mobile'])) $errors['mobile']='Valid number required'; @@ -686,10 +771,10 @@ class Staff { $errors['passwd2']='Password(s) do not match'; } } - + if(!$vars['dept_id']) $errors['dept_id']='Department required'; - + if(!$vars['group_id']) $errors['group_id']='Group required'; @@ -698,7 +783,7 @@ class Staff { if($errors) return false; - + $sql='SET updated=NOW() ' .' ,isadmin='.db_input($vars['isadmin']) .' ,isactive='.db_input($vars['isactive']) @@ -718,24 +803,25 @@ class Staff { .' ,mobile="'.db_input(Format::phone($vars['mobile']),false).'"' .' ,signature='.db_input($vars['signature']) .' ,notes='.db_input($vars['notes']); - - if($vars['passwd1']) + + if($vars['passwd1']) { $sql.=' ,passwd='.db_input(Passwd::hash($vars['passwd1'])); - + } + if(isset($vars['change_passwd'])) $sql.=' ,change_passwd=1'; - + if($id) { $sql='UPDATE '.STAFF_TABLE.' '.$sql.' WHERE staff_id='.db_input($id); if(db_query($sql) && db_affected_rows()) return true; - + $errors['err']='Unable to update the user. Internal error occurred'; } else { $sql='INSERT INTO '.STAFF_TABLE.' '.$sql.', created=NOW()'; if(db_query($sql) && ($uid=db_insert_id())) return $uid; - + $errors['err']='Unable to create user. Internal error'; } diff --git a/include/class.template.php b/include/class.template.php index 629f76f2991243f7dea20224e83bdc00cae369cb..fd068d7a86047a8f8e6ad1a657827985f2494f44 100644 --- a/include/class.template.php +++ b/include/class.template.php @@ -13,13 +13,57 @@ vim: expandtab sw=4 ts=4 sts=4: **********************************************************************/ +require_once INCLUDE_DIR.'class.yaml.php'; -class Template { +class EmailTemplateGroup { var $id; var $ht; - - function Template($id){ + var $_templates; + var $all_names=array( + 'ticket.autoresp'=>array( + 'name'=>'New Ticket Auto-response', + 'desc'=>'Autoresponse sent to user, if enabled, on new ticket.'), + 'ticket.autoreply'=>array( + 'name'=>'New Ticket Auto-reply', + 'desc'=>'Canned Auto-reply sent to user on new ticket, based on filter matches. Overwrites "normal" auto-response.'), + 'message.autoresp'=>array( + 'name'=>'New Message Auto-response', + 'desc'=>'Confirmation sent to user when a new message is appended to an existing ticket.'), + 'ticket.notice'=>array( + 'name'=>'New Ticket Notice', + 'desc'=>'Notice sent to user, if enabled, on new ticket created by staff on their behalf (e.g phone calls).'), + 'ticket.overlimit'=>array( + 'name'=>'Over Limit Notice', + 'desc'=>'A one-time notice sent, if enabled, when user has reached the maximum allowed open tickets.'), + 'ticket.reply'=>array( + 'name'=>'Response/Reply Template', + 'desc'=>'Template used on ticket response/reply'), + 'ticket.alert'=>array( + 'name'=>'New Ticket Alert', + 'desc'=>'Alert sent to staff, if enabled, on new ticket.'), + 'message.alert'=>array( + 'name'=>'New Message Alert', + 'desc'=>'Alert sent to staff, if enabled, when user replies to an existing ticket.'), + 'note.alert'=>array( + 'name'=>'Internal Note Alert', + 'desc'=>'Alert sent to selected staff, if enabled, on new internal note.'), + 'assigned.alert'=>array( + 'name'=>'Ticket Assignment Alert', + 'desc'=>'Alert sent to staff on ticket assignment.'), + 'transfer.alert'=>array( + 'name'=>'Ticket Transfer Alert', + 'desc'=>'Alert sent to staff on ticket transfer.'), + 'ticket.overdue'=>array( + 'name'=>'Overdue Ticket Alert', + 'desc'=>'Alert sent to staff on stale or overdue tickets.'), + 'staff.pwreset' => array( + 'name' => 'Staff Password Reset', + 'desc' => 'Notice sent to staff with the password reset link.', + 'default' => 'templates/staff.pwreset.txt'), + ); + + function EmailTemplateGroup($id){ $this->id=0; $this->load($id); } @@ -28,9 +72,9 @@ class Template { if(!$id && !($id=$this->getId())) return false; - + $sql='SELECT tpl.*,count(dept.tpl_id) as depts ' - .' FROM '.EMAIL_TEMPLATE_TABLE.' tpl ' + .' FROM '.EMAIL_TEMPLATE_GRP_TABLE.' tpl ' .' LEFT JOIN '.DEPT_TABLE.' dept USING(tpl_id) ' .' WHERE tpl.tpl_id='.db_input($id) .' GROUP BY tpl.tpl_id'; @@ -38,21 +82,21 @@ class Template { if(!($res=db_query($sql))|| !db_num_rows($res)) return false; - + $this->ht=db_fetch_array($res); $this->id=$this->ht['tpl_id']; return true; } - + function reload() { return $this->load($this->getId()); } - + function getId(){ return $this->id; } - + function getName(){ return $this->ht['name']; } @@ -69,9 +113,13 @@ class Template { return $this->isEnabled(); } + function getLanguage() { + return 'en_US'; + } + function isInUse(){ global $cfg; - + return ($this->ht['depts'] || ($cfg && $this->getId()==$cfg->getDefaultTemplateId())); } @@ -85,184 +133,108 @@ class Template { function setStatus($status){ - $sql='UPDATE '.EMAIL_TEMPLATE_TABLE.' SET updated=NOW(), isactive='.db_input($status?1:0) + $sql='UPDATE '.EMAIL_TEMPLATE_GRP_TABLE.' SET updated=NOW(), isactive='.db_input($status?1:0) .' WHERE tpl_id='.db_input($this->getId()); return (db_query($sql) && db_affected_rows()); } + function getTemplateDescription($name) { + return $this->all_names[$name]; + } + function getMsgTemplate($name) { global $ost; - //TODO: Don't preload - do ondemand fetch! - $tpl=array(); - switch(strtolower($name)) { - case 'ticket_autoresp': - $tpl=array('subj'=>$this->ht['ticket_autoresp_subj'],'body'=>$this->ht['ticket_autoresp_body']); - break; - case 'ticket_autoreply': - $tpl=array('subj'=>$this->ht['ticket_autoreply_subj'],'body'=>$this->ht['ticket_autoreply_body']); - break; - case 'msg_autoresp': - $tpl=array('subj'=>$this->ht['message_autoresp_subj'],'body'=>$this->ht['message_autoresp_body']); - break; - case 'ticket_notice': - $tpl=array('subj'=>$this->ht['ticket_notice_subj'],'body'=>$this->ht['ticket_notice_body']); - break; - case 'overlimit_notice': - $tpl=array('subj'=>$this->ht['ticket_overlimit_subj'],'body'=>$this->ht['ticket_overlimit_body']); - break; - case 'ticket_reply': - $tpl=array('subj'=>$this->ht['ticket_reply_subj'],'body'=>$this->ht['ticket_reply_body']); - break; - case 'ticket_alert': - $tpl=array('subj'=>$this->ht['ticket_alert_subj'],'body'=>$this->ht['ticket_alert_body']); - break; - case 'msg_alert': - $tpl=array('subj'=>$this->ht['message_alert_subj'],'body'=>$this->ht['message_alert_body']); - break; - case 'note_alert': - $tpl=array('subj'=>$this->ht['note_alert_subj'],'body'=>$this->ht['note_alert_body']); - break; - case 'assigned_alert': - $tpl=array('subj'=>$this->ht['assigned_alert_subj'],'body'=>$this->ht['assigned_alert_body']); - break; - case 'transfer_alert': - $tpl=array('subj'=>$this->ht['transfer_alert_subj'],'body'=>$this->ht['transfer_alert_body']); - break; - case 'overdue_alert': - $tpl=array('subj'=>$this->ht['ticket_overdue_subj'],'body'=>$this->ht['ticket_overdue_body']); - break; - default: - $ost->logWarning('Template Fetch Error', "Unable to fetch '$name' template - id #".$this->getId()); - $tpl=array(); + if ($tpl=EmailTemplate::lookupByName($this->getId(), $name, $this)) + return $tpl; + + if ($tpl=EmailTemplate::fromInitialData($name, $this)) + return $tpl; + + $ost->logWarning('Template Fetch Error', "Unable to fetch '$name' template - id #".$this->getId()); + return false; + } + + function getTemplates() { + if (!$this->_tempates) { + $this->_templates = array(); + $sql = 'SELECT id, code_name FROM '.EMAIL_TEMPLATE_TABLE + .' WHERE tpl_id='.db_input($this->getId()) + .' ORDER BY code_name'; + $res = db_query($sql); + while (list($id, $cn)=db_fetch_row($res)) + $this->_templates[$cn] = EmailTemplate::lookup($id, $this); } + return $this->_templates; + } - return $tpl; + function getUndefinedTemplateNames() { + $list = $this->all_names; + foreach ($this->getTemplates() as $cn=>$tpl) + unset($list[$cn]); + return $list; } - + function getNewTicketAlertMsgTemplate() { - return $this->getMsgTemplate('ticket_alert'); + return $this->getMsgTemplate('ticket.alert'); } function getNewMessageAlertMsgTemplate() { - return $this->getMsgTemplate('msg_alert'); + return $this->getMsgTemplate('message.alert'); } function getNewTicketNoticeMsgTemplate() { - return $this->getMsgTemplate('ticket_notice'); + return $this->getMsgTemplate('ticket.notice'); } function getNewMessageAutorepMsgTemplate() { - return $this->getMsgTemplate('msg_autoresp'); + return $this->getMsgTemplate('message.autoresp'); } function getAutoRespMsgTemplate() { - return $this->getMsgTemplate('ticket_autoresp'); + return $this->getMsgTemplate('ticket.autoresp'); } function getAutoReplyMsgTemplate() { - return $this->getMsgTemplate('ticket_autoreply'); + return $this->getMsgTemplate('ticket.autoreply'); } function getReplyMsgTemplate() { - return $this->getMsgTemplate('ticket_reply'); + return $this->getMsgTemplate('ticket.reply'); } function getOverlimitMsgTemplate() { - return $this->getMsgTemplate('overlimit_notice'); + return $this->getMsgTemplate('ticket.overlimit'); } function getNoteAlertMsgTemplate() { - return $this->getMsgTemplate('note_alert'); + return $this->getMsgTemplate('note.alert'); } function getTransferAlertMsgTemplate() { - return $this->getMsgTemplate('transfer_alert'); + return $this->getMsgTemplate('transfer.alert'); } function getAssignedAlertMsgTemplate() { - return $this->getMsgTemplate('assigned_alert'); + return $this->getMsgTemplate('assigned.alert'); } function getOverdueAlertMsgTemplate() { - return $this->getMsgTemplate('overdue_alert'); - } - - function updateMsgTemplate($vars, &$errors) { - - if(!($tpls=Template::message_templates()) || !$tpls[$vars['tpl']]) - $errors['tpl']='Unknown or invalid template'; - - if(!$vars['subj']) - $errors['subj']='Message subject required'; - - if(!$vars['body']) - $errors['body']='Message body required'; - - - if($errors) return false; - - $sql='UPDATE '.EMAIL_TEMPLATE_TABLE.' SET updated=NOW() '; - switch(strtolower($vars['tpl'])) { - case 'ticket_autoresp': - $sql.=',ticket_autoresp_subj='.db_input($vars['subj']).',ticket_autoresp_body='.db_input($vars['body']); - break; - case 'ticket_autoreply': - $sql.=',ticket_autoreply_subj='.db_input($vars['subj']).',ticket_autoreply_body='.db_input($vars['body']); - break; - case 'msg_autoresp': - $sql.=',message_autoresp_subj='.db_input($vars['subj']).',message_autoresp_body='.db_input($vars['body']); - break; - case 'ticket_notice': - $sql.=',ticket_notice_subj='.db_input($vars['subj']).',ticket_notice_body='.db_input($vars['body']); - break; - case 'overlimit_notice': - $sql.=',ticket_overlimit_subj='.db_input($vars['subj']).',ticket_overlimit_body='.db_input($vars['body']); - break; - case 'ticket_reply': - $sql.=',ticket_reply_subj='.db_input($vars['subj']).',ticket_reply_body='.db_input($vars['body']); - break; - case 'ticket_alert': - $sql.=',ticket_alert_subj='.db_input($vars['subj']).',ticket_alert_body='.db_input($vars['body']); - break; - case 'msg_alert': - $sql.=',message_alert_subj='.db_input($vars['subj']).',message_alert_body='.db_input($vars['body']); - break; - case 'note_alert': - $sql.=',note_alert_subj='.db_input($vars['subj']).',note_alert_body='.db_input($vars['body']); - break; - case 'assigned_alert': - $sql.=',assigned_alert_subj='.db_input($vars['subj']).',assigned_alert_body='.db_input($vars['body']); - break; - case 'transfer_alert': - $sql.=',transfer_alert_subj='.db_input($vars['subj']).',transfer_alert_body='.db_input($vars['body']); - break; - case 'overdue_alert': - $sql.=',ticket_overdue_subj='.db_input($vars['subj']).',ticket_overdue_body='.db_input($vars['body']); - break; - default: - $errors['tpl']='Unknown or invalid template'; - return false; - } - - $sql.=' WHERE tpl_id='.db_input($this->getId()); - - return (db_query($sql)); - + return $this->getMsgTemplate('ticket.overdue'); } function update($vars,&$errors) { if(!$vars['isactive'] && $this->isInUse()) - $errors['isactive']='Template in-use can not be disabled!'; + $errors['isactive']='Template in use cannot be disabled!'; if(!$this->save($this->getId(),$vars,$errors)) return false; - + $this->reload(); - + return true; } @@ -280,55 +252,28 @@ class Template { if($this->isInUse() || $cfg->getDefaultTemplateId()==$this->getId()) return 0; - $sql='DELETE FROM '.EMAIL_TEMPLATE_TABLE.' WHERE tpl_id='.db_input($this->getId()).' LIMIT 1'; + $sql='DELETE FROM '.EMAIL_TEMPLATE_GRP_TABLE + .' WHERE tpl_id='.db_input($this->getId()).' LIMIT 1'; if(db_query($sql) && ($num=db_affected_rows())) { - //isInuse check is enough - but it doesn't hurt make sure deleted tpl is not in-use. + //isInuse check is enough - but it doesn't hurt make sure deleted tpl is not in-use. db_query('UPDATE '.DEPT_TABLE.' SET tpl_id=0 WHERE tpl_id='.db_input($this->getId())); + db_query('DELETE FROM '.EMAIL_TEMPLATE_TABLE + .' WHERE tpl_id='.db_input($this->getId())); } return $num; } - /*** Static functions ***/ - function message_templates(){ - - //TODO: Make it database driven and dynamic - $messages=array('ticket_autoresp'=>array('name'=>'New Ticket Auto-response', - 'desc'=>'Autoresponse sent to user, if enabled, on new ticket.'), - 'ticket_autoreply'=>array('name'=>'New Ticket Auto-reply', - 'desc'=>'Canned Auto-reply sent to user on new ticket, based on filter matches. Overwrites "normal" auto-response.'), - 'msg_autoresp'=>array('name'=>'New Message Auto-response', - 'desc'=>'Confirmation sent to user when a new message is appended to an existing ticket.'), - 'ticket_notice'=>array('name'=>'New Ticket Notice', - 'desc'=>'Notice sent to user, if enabled, on new ticket created by staff on their behalf (e.g phone calls).'), - 'overlimit_notice'=>array('name'=>'Over Limit Notice', - 'desc'=>'A one time notice sent, if enabled, when user has reached the maximum allowed open tickets.'), - 'ticket_reply'=>array('name'=>'Response/Reply Template', - 'desc'=>'Template used on ticket response/reply'), - 'ticket_alert'=>array('name'=>'New Ticket Alert', - 'desc'=>'Alert sent to staff, if enabled, on new ticket.'), - 'msg_alert'=>array('name'=>'New Message Alert', - 'desc'=>'Alert sent to staff, if enabled, when user replies to an existing ticket.'), - 'note_alert'=>array('name'=>'Internal Note Alert', - 'desc'=>'Alert sent to selected staff, if enabled, on new internal note.'), - 'assigned_alert'=>array('name'=>'Ticket Assignment Alert', - 'desc'=>'Alert sent to staff on ticket assignment.'), - 'transfer_alert'=>array('name'=>'Ticket Transfer Alert', - 'desc'=>'Alert sent to staff on ticket transfer.'), - 'overdue_alert'=>array('name'=>'Overdue Ticket Alert', - 'desc'=>'Alert sent to staff on stale or overdue tickets.') - ); - return $messages; - } - - - function create($vars,&$errors) { - - return Template::save(0,$vars,$errors); + function create($vars,&$errors) { + return EmailTemplateGroup::save(0,$vars,$errors); + } + + function add($vars, &$errors) { + return self::lookup(self::create($vars, $errors)); } function getIdByName($name){ - $sql='SELECT tpl_id FROM '.EMAIL_TEMPLATE_TABLE.' WHERE name='.db_input($name); + $sql='SELECT tpl_id FROM '.EMAIL_TEMPLATE_GRP_TABLE.' WHERE name='.db_input($name); if(($res=db_query($sql)) && db_num_rows($res)) list($id)=db_fetch_row($res); @@ -336,7 +281,7 @@ class Template { } function lookup($id){ - return ($id && is_numeric($id) && ($t= new Template($id)) && $t->getId()==$id)?$t:null; + return ($id && is_numeric($id) && ($t= new EmailTemplateGroup($id)) && $t->getId()==$id)?$t:null; } function save($id, $vars, &$errors) { @@ -345,67 +290,212 @@ class Template { $tpl=null; $vars['name']=Format::striptags(trim($vars['name'])); - if($id && $id!=$vars['id']) + if($id && $id!=$vars['tpl_id']) $errors['err']='Internal error. Try again'; if(!$vars['name']) $errors['name']='Name required'; - elseif(($tid=Template::getIdByName($vars['name'])) && $tid!=$id) + elseif(($tid=EmailTemplateGroup::getIdByName($vars['name'])) && $tid!=$id) $errors['name']='Template name already exists'; - if(!$id && (!$vars['tpl_id'] || !($tpl=Template::lookup($vars['tpl_id'])))) + if(!$id && (!$vars['tpl_id'] || !($tpl=EmailTemplateGroup::lookup($vars['tpl_id'])))) $errors['tpl_id']='Selection required'; - + if($errors) return false; $sql=' updated=NOW() ' .' ,name='.db_input($vars['name']) .' ,isactive='.db_input($vars['isactive']) .' ,notes='.db_input($vars['notes']); - + if($id) { - $sql='UPDATE '.EMAIL_TEMPLATE_TABLE.' SET '.$sql.' WHERE tpl_id='.db_input($id); + $sql='UPDATE '.EMAIL_TEMPLATE_GRP_TABLE.' SET '.$sql.' WHERE tpl_id='.db_input($id); if(db_query($sql)) return true; $errors['err']='Unable to update the template. Internal error occurred'; - + } elseif($tpl && ($info=$tpl->getInfo())) { - $sql='INSERT INTO '.EMAIL_TEMPLATE_TABLE.' SET '.$sql - .' ,created=NOW() ' - .' ,cfg_id='.db_input($ost->getConfigId()) - .' ,ticket_autoresp_subj='.db_input($info['ticket_autoresp_subj']) - .' ,ticket_autoresp_body='.db_input($info['ticket_autoresp_body']) - .' ,ticket_autoreply_subj='.db_input($info['ticket_autoreply_subj']) - .' ,ticket_autoreply_body='.db_input($info['ticket_autoreply_body']) - .' ,ticket_notice_subj='.db_input($info['ticket_notice_subj']) - .' ,ticket_notice_body='.db_input($info['ticket_notice_body']) - .' ,ticket_alert_subj='.db_input($info['ticket_alert_subj']) - .' ,ticket_alert_body='.db_input($info['ticket_alert_body']) - .' ,message_autoresp_subj='.db_input($info['message_autoresp_subj']) - .' ,message_autoresp_body='.db_input($info['message_autoresp_body']) - .' ,message_alert_subj='.db_input($info['message_alert_subj']) - .' ,message_alert_body='.db_input($info['message_alert_body']) - .' ,note_alert_subj='.db_input($info['note_alert_subj']) - .' ,note_alert_body='.db_input($info['note_alert_body']) - .' ,transfer_alert_subj='.db_input($info['transfer_alert_subj']) - .' ,transfer_alert_body='.db_input($info['transfer_alert_body']) - .' ,assigned_alert_subj='.db_input($info['assigned_alert_subj']) - .' ,assigned_alert_body='.db_input($info['assigned_alert_body']) - .' ,ticket_overdue_subj='.db_input($info['ticket_overdue_subj']) - .' ,ticket_overdue_body='.db_input($info['ticket_overdue_body']) - .' ,ticket_overlimit_subj='.db_input($info['ticket_overlimit_subj']) - .' ,ticket_overlimit_body='.db_input($info['ticket_overlimit_body']) - .' ,ticket_reply_subj='.db_input($info['ticket_reply_subj']) - .' ,ticket_reply_body='.db_input($info['ticket_reply_body']); - - if(db_query($sql) && ($id=db_insert_id())) + $sql='INSERT INTO '.EMAIL_TEMPLATE_GRP_TABLE + .' SET created=NOW(), '.$sql; + if(!db_query($sql) || !($new_id=db_insert_id())) { + $errors['err']='Unable to create template. Internal error'; + return false; + } + + $sql='INSERT INTO '.EMAIL_TEMPLATE_TABLE.' + (created, updated, tpl_id, code_name, subject, body) + SELECT NOW() as created, NOW() as updated, '.db_input($new_id) + .' as tpl_id, code_name, subject, body + FROM '.EMAIL_TEMPLATE_TABLE + .' WHERE tpl_id='.db_input($tpl->getId()); + + if(db_query($sql) && db_insert_id()) + return $new_id; + } + + return false; + } +} + +class EmailTemplate { + + var $id; + var $ht; + var $_group; + + function EmailTemplate($id, $group=null){ + $this->id=0; + if ($id) $this->load($id); + if ($group) $this->_group = $group; + } + + function load($id) { + + if(!$id && !($id=$this->getId())) + return false; + + $sql='SELECT * FROM '.EMAIL_TEMPLATE_TABLE + .' WHERE id='.db_input($id); + + if(!($res=db_query($sql))|| !db_num_rows($res)) + return false; + + + $this->ht=db_fetch_array($res); + $this->id=$this->ht['id']; + + return true; + } + + function reload() { + return $this->load($this->getId()); + } + + function getId(){ + return $this->id; + } + + function asArray() { + return array( + 'id' => $this->getId(), + 'subj' => $this->getSubject(), + 'body' => $this->getBody(), + ); + } + + function getSubject() { + return $this->ht['subject']; + } + + function getBody() { + return $this->ht['body']; + } + + function getCodeName() { + return $this->ht['code_name']; + } + + function getTplId() { + return $this->ht['tpl_id']; + } + + function getGroup() { + if (!isset($this->_group)) + $this->_group = EmailTemplateGroup::lookup($this->getTplId()); + return $this->_group; + + } + + function getDescription() { + return $this->getGroup()->getTemplateDescription($this->ht['code_name']); + } + + function update($vars, &$errors) { + + if(!$this->save($this->getId(),$vars,$errors)) + return false; + + $this->reload(); + + return true; + } + + function save($id, $vars, &$errors) { + if(!$vars['subj']) + $errors['subj']='Message subject required'; + + if(!$vars['body']) + $errors['body']='Message body required'; + + if (!$id) { + if (!$vars['tpl_id']) + $errors['tpl_id']='Template group required'; + if (!$vars['code_name']) + $errprs['code_name']='Code name required'; + } + + if ($errors) + return false; + + if ($id) { + $sql='UPDATE '.EMAIL_TEMPLATE_TABLE.' SET updated=NOW() ' + .', subject='.db_input($vars['subj']) + .', body='.db_input($vars['body']) + .' WHERE id='.db_input($this->getId()); + + return (db_query($sql)); + } else { + $sql='INSERT INTO '.EMAIL_TEMPLATE_TABLE.' SET created=NOW(), + updated=NOW(), tpl_id='.db_input($vars['tpl_id']) + .', code_name='.db_input($vars['code_name']) + .', subject='.db_input($vars['subj']) + .', body='.db_input($vars['body']); + if (db_query($sql) && ($id=db_insert_id())) return $id; - - $errors['err']='Unable to create template. Internal error'; } - + return null; + } + + function create($vars, &$errors) { + return self::save(0, $vars, $errors); + } + + function add($vars, &$errors) { + return self::lookup(self::create($vars, $errors)); + } + + function lookupByName($tpl_id, $name, $group=null) { + $sql = 'SELECT id FROM '.EMAIL_TEMPLATE_TABLE + .' WHERE tpl_id='.db_input($tpl_id) + .' AND code_name='.db_input($name); + if (($res=db_query($sql)) && ($id=db_result($res))) + return self::lookup($id, $group); + + return false; + } + + function lookup($id, $group=null) { + return ($id && is_numeric($id) && ($t= new EmailTemplate($id, $group)) && $t->getId()==$id)?$t:null; + } + + /** + * Load the template from the initial_data directory. The format of the + * file should be free flow text. The first line is the subject and the + * rest of the file is the body. + */ + function fromInitialData($name, $group=null) { + $templ = new EmailTemplate(0, $group); + $lang = ($group) ? $group->getLanguage() : 'en_US'; + $info = YamlDataParser::load(I18N_DIR . "$lang/templates/$name.yaml"); + if (isset($info['subject']) && isset($info['body'])) { + $templ->ht = $info; + return $templ; + } + raise_error("$lang/templates/$name.yaml: " + . 'Email templates must define both "subject" and "body" parts of the template', + 'InitialDataError'); return false; } } diff --git a/include/class.thread.php b/include/class.thread.php index f45c0e518bd179d3a41b5000ada56b7b5ccdc1b5..9b4853a420b77b67d74a51bebad8875065f349b3 100644 --- a/include/class.thread.php +++ b/include/class.thread.php @@ -146,6 +146,10 @@ class Thread { //Add ticket Id. $vars['ticketId'] = $this->getTicketId(); + // DELME: When HTML / rich-text is supported + $vars['title'] = Format::htmlchars($vars['title']); + $vars['body'] = Format::htmlchars($vars['body']); + return Note::create($vars, $errors); } @@ -154,6 +158,10 @@ class Thread { $vars['ticketId'] = $this->getTicketId(); $vars['staffId'] = 0; + // DELME: When HTML / rich-text is supported + $vars['title'] = Format::htmlchars($vars['title']); + $vars['body'] = Format::htmlchars($vars['body']); + return Message::create($vars, $errors); } @@ -161,6 +169,10 @@ class Thread { $vars['ticketId'] = $this->getTicketId(); + // DELME: When HTML / rich-text is supported + $vars['title'] = Format::htmlchars($vars['title']); + $vars['body'] = Format::htmlchars($vars['body']); + return Response::create($vars, $errors); } diff --git a/include/class.ticket.php b/include/class.ticket.php index 21a4bd8f1777e100d839dedaf44525c61455f381..f5731deddf41b267e222e33adc9ca82a0dccecf7 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -584,7 +584,7 @@ class Ticket { /** * Selects the appropriate service-level-agreement plan for this ticket. * When tickets are transfered between departments, the SLA of the new - * department should be applied to the ticket. This would be usefule, + * department should be applied to the ticket. This would be useful, * for instance, if the ticket is transferred to a different department * which has a shorter grace period, the ticket should be considered * overdue in the shorter window now that it is owned by the new @@ -597,7 +597,7 @@ class Ticket { */ function selectSLAId($trump=null) { global $cfg; - # XXX Should the SLA be overwritten if it was originally set via an + # XXX Should the SLA be overridden if it was originally set via an # email filter? This method doesn't consider such a case if ($trump && is_numeric($trump)) { $slaId = $trump; @@ -738,7 +738,7 @@ class Ticket { && $dept->autoRespONNewTicket() && ($msg=$tpl->getAutoRespMsgTemplate())) { - $msg = $this->replaceVars($msg, + $msg = $this->replaceVars($msg->asArray(), array('message' => $message, 'signature' => ($dept && $dept->isPublic())?$dept->getSignature():'') ); @@ -757,7 +757,7 @@ class Ticket { && $cfg->alertONNewTicket() && ($msg=$tpl->getNewTicketAlertMsgTemplate())) { - $msg = $this->replaceVars($msg, array('message' => $message)); + $msg = $this->replaceVars($msg->asArray(), array('message' => $message)); $recipients=$sentlist=array(); //Alert admin?? @@ -809,7 +809,7 @@ class Ticket { if($tpl && ($msg=$tpl->getOverlimitMsgTemplate()) && $email) { - $msg = $this->replaceVars($msg, + $msg = $this->replaceVars($msg->asArray(), array('signature' => ($dept && $dept->isPublic())?$dept->getSignature():'')); $email->sendAutoReply($this->getEmail(), $msg['subj'], $msg['body']); @@ -868,7 +868,7 @@ class Ticket { //If enabled...send confirmation to user. ( New Message AutoResponse) if($email && $tpl && ($msg=$tpl->getNewMessageAutorepMsgTemplate())) { - $msg = $this->replaceVars($msg, + $msg = $this->replaceVars($msg->asArray(), array('signature' => ($dept && $dept->isPublic())?$dept->getSignature():'')); //Reply separator tag. @@ -923,7 +923,7 @@ class Ticket { //Get the message template if($email && $recipients && $tpl && ($msg=$tpl->getAssignedAlertMsgTemplate())) { - $msg = $this->replaceVars($msg, + $msg = $this->replaceVars($msg->asArray(), array('comments' => $comments, 'assignee' => $assignee, 'assigner' => $assigner @@ -964,7 +964,8 @@ class Ticket { //Get the message template if($tpl && ($msg=$tpl->getOverdueAlertMsgTemplate()) && $email) { - $msg = $this->replaceVars($msg, array('comments' => $comments)); + $msg = $this->replaceVars($msg->asArray(), + array('comments' => $comments)); //recipients $recipients=array(); @@ -1133,7 +1134,7 @@ class Ticket { $this->reload(); // Set SLA of the new department - if(!$this->getSLAId()) + if(!$this->getSLAId() || $this->getSLA()->isTransient()) $this->selectSLAId(); /*** log the transfer comments as internal note - with alerts disabled - ***/ @@ -1158,7 +1159,8 @@ class Ticket { //Get the message template if($tpl && ($msg=$tpl->getTransferAlertMsgTemplate()) && $email) { - $msg = $this->replaceVars($msg, array('comments' => $comments, 'staff' => $thisstaff)); + $msg = $this->replaceVars($msg->asArray(), + array('comments' => $comments, 'staff' => $thisstaff)); //recipients $recipients=array(); //Assigned staff or team... if any @@ -1310,7 +1312,7 @@ class Ticket { //If enabled...send alert to staff (New Message Alert) if($cfg->alertONNewMessage() && $tpl && $email && ($msg=$tpl->getNewMessageAlertMsgTemplate())) { - $msg = $this->replaceVars($msg, array('message' => $message)); + $msg = $this->replaceVars($msg->asArray(), array('message' => $message)); //Build list of recipients and fire the alerts. $recipients=array(); @@ -1377,7 +1379,8 @@ class Ticket { else $signature=''; - $msg = $this->replaceVars($msg, array('response' => $response, 'signature' => $signature)); + $msg = $this->replaceVars($msg->asArray(), + array('response' => $response, 'signature' => $signature)); if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator())) $msg['body'] ="\n$tag\n\n".$msg['body']; @@ -1430,7 +1433,7 @@ class Ticket { else $signature=''; - $msg = $this->replaceVars($msg, + $msg = $this->replaceVars($msg->asArray(), array('response' => $response, 'signature' => $signature, 'staff' => $thisstaff)); if($cfg->stripQuotedReply() && ($tag=$cfg->getReplySeparator())) @@ -1529,7 +1532,8 @@ class Ticket { if($tpl && ($msg=$tpl->getNoteAlertMsgTemplate()) && $email) { - $msg = $this->replaceVars($msg, array('note' => $note)); + $msg = $this->replaceVars($msg->asArray(), + array('note' => $note)); // Alert recipients $recipients=array(); @@ -1549,8 +1553,11 @@ class Ticket { $attachments = $note->getAttachments(); $sentlist=array(); foreach( $recipients as $k=>$staff) { - if(!$staff || !is_object($staff) || !$staff->getEmail() || !$staff->isAvailable()) continue; - if(in_array($staff->getEmail(), $sentlist) || ($staffId && $staffId==$staff->getId())) continue; + if(!is_object($staff) + || !$staff->isAvailable() //Don't bother vacationing staff. + || in_array($staff->getEmail(), $sentlist) //No duplicates. + || $note->getStaffId() == $staff->getId()) //No need to alert the poster! + continue; $alert = str_replace('%{recipient}', $staff->getFirstName(), $msg['body']); $email->sendAlert($staff->getEmail(), $msg['subj'], $alert, $attachments); $sentlist[] = $staff->getEmail(); @@ -1606,11 +1613,11 @@ class Ticket { if($vars['duedate']) { if($this->isClosed()) - $errors['duedate']='Duedate can NOT be set on a closed ticket'; + $errors['duedate']='Due date can NOT be set on a closed ticket'; elseif(!$vars['time'] || strpos($vars['time'],':')===false) $errors['time']='Select time'; elseif(strtotime($vars['duedate'].' '.$vars['time'])===false) - $errors['duedate']='Invalid duedate'; + $errors['duedate']='Invalid due date'; elseif(strtotime($vars['duedate'].' '.$vars['time'])<=time()) $errors['duedate']='Due date must be in the future'; } @@ -1651,6 +1658,10 @@ class Ticket { $this->logNote('Ticket Updated', $vars['note'], $thisstaff); $this->reload(); + // Reselect SLA if transient + if(!$this->getSLAId() || $this->getSLA()->isTransient()) + $this->selectSLAId(); + //Clear overdue flag if duedate or SLA changes and the ticket is no longer overdue. if($this->isOverdue() && (!$this->getEstDueDate() //Duedate + SLA cleared @@ -1663,7 +1674,7 @@ class Ticket { } - /*============== Static functions. Use Ticket::function(params); ==================*/ + /*============== Static functions. Use Ticket::function(params); =============nolint*/ function getIdByExtId($extId, $email=null) { if(!$extId || !is_numeric($extId)) @@ -1738,7 +1749,7 @@ class Ticket { global $cfg; /* Unknown or invalid staff */ - if(!$staff || (!is_object($staff) && !($staff=Staff::lookup($staff))) || !$staff->isStaff() || $cfg->getDBVersion()) + if(!$staff || (!is_object($staff) && !($staff=Staff::lookup($staff))) || !$staff->isStaff()) return null; $sql='SELECT count(open.ticket_id) as open, count(answered.ticket_id) as answered ' @@ -1804,11 +1815,16 @@ class Ticket { /* * The mother of all functions...You break it you fix it! * - * $autorespond and $alertstaff overwrites config settings... + * $autorespond and $alertstaff overrides config settings... */ function create($vars, &$errors, $origin, $autorespond=true, $alertstaff=true) { global $ost, $cfg, $thisclient, $_FILES; + // Drop extra whitespace + foreach (array('email', 'phone', 'subject', 'name') as $f) + if (isset($vars[$f])) + $vars[$f] = trim($vars[$f]); + //Check for 403 if ($vars['email'] && Validator::is_email($vars['email'])) { @@ -1892,7 +1908,7 @@ class Ticket { if(!$vars['time'] || strpos($vars['time'],':')===false) $errors['time']='Select time'; elseif(strtotime($vars['duedate'].' '.$vars['time'])===false) - $errors['duedate']='Invalid duedate'; + $errors['duedate']='Invalid due date'; elseif(strtotime($vars['duedate'].' '.$vars['time'])<=time()) $errors['duedate']='Due date must be in the future'; } @@ -1912,7 +1928,7 @@ class Ticket { $priorityId=$vars['priorityId']; $source=ucfirst($vars['source']); $topic=NULL; - // Intenal mapping magic...see if we need to overwrite anything + // Intenal mapping magic...see if we need to override anything if(isset($vars['topicId']) && ($topic=Topic::lookup($vars['topicId']))) { //Ticket created via web by user/or staff $deptId=$deptId?$deptId:$topic->getDeptId(); $priorityId=$priorityId?$priorityId:$topic->getPriorityId(); @@ -1994,7 +2010,7 @@ class Ticket { $ticket->assignToTeam($vars['teamId'], 'Auto Assignment'); /********** double check auto-response ************/ - //Overwrite auto responder if the FROM email is one of the internal emails...loop control. + //Override auto responder if the FROM email is one of the internal emails...loop control. if($autorespond && (Email::getIdByEmail($ticket->getEmail()))) $autorespond=false; @@ -2105,7 +2121,7 @@ class Ticket { else $signature=''; - $msg = $ticket->replaceVars($msg, + $msg = $ticket->replaceVars($msg->asArray(), array('message' => $message, 'signature' => $signature)); if($cfg->stripQuotedReply() && ($tag=trim($cfg->getReplySeparator()))) diff --git a/include/class.topic.php b/include/class.topic.php index 9379e8ec95ec94a2e72f20bfe8a587da946f5398..8b02e5eeab5fe04344a3c81ea987f7577c51235a 100644 --- a/include/class.topic.php +++ b/include/class.topic.php @@ -16,10 +16,12 @@ class Topic { var $id; - + var $ht; + var $parent; - + var $page; + function Topic($id) { $this->id=0; $this->load($id); @@ -40,11 +42,14 @@ class Topic { return false; $this->ht = db_fetch_array($res); - $this->id=$this->ht['topic_id']; - + $this->id = $this->ht['topic_id']; + + $this->page = null; + + return true; } - + function reload() { return $this->load(); } @@ -52,7 +57,7 @@ class Topic { function asVar() { return $this->getName(); } - + function getId() { return $this->id; } @@ -71,7 +76,7 @@ class Topic { function getName() { return $this->ht['name']; } - + function getDeptId() { return $this->ht['dept_id']; } @@ -91,7 +96,18 @@ class Topic { function getTeamId() { return $this->ht['team_id']; } - + + function getPageId() { + return $this->ht['page_id']; + } + + function getPage() { + if(!$this->page && $this->getPageId()) + $this->page = Page::lookup($this->getPageId()); + + return $this->page; + } + function autoRespond() { return (!$this->ht['noautoresp']); } @@ -137,7 +153,7 @@ class Topic { return $num; } /*** Static functions ***/ - function create($vars,&$errors) { + function create($vars, &$errors) { return self::save(0, $vars, $errors); } @@ -195,10 +211,10 @@ class Topic { if(!$vars['dept_id']) $errors['dept_id']='You must select a department'; - + if(!$vars['priority_id']) $errors['priority_id']='You must select a priority'; - + if($errors) return false; $sql=' updated=NOW() ' @@ -207,6 +223,7 @@ class Topic { .',dept_id='.db_input($vars['dept_id']) .',priority_id='.db_input($vars['priority_id']) .',sla_id='.db_input($vars['sla_id']) + .',page_id='.db_input($vars['page_id']) .',isactive='.db_input($vars['isactive']) .',ispublic='.db_input($vars['ispublic']) .',noautoresp='.db_input(isset($vars['noautoresp'])?1:0) @@ -219,7 +236,7 @@ class Topic { $sql.=',staff_id=0, team_id='.db_input(preg_replace("/[^0-9]/", "", $vars['assign'])); else $sql.=',staff_id=0, team_id=0 '; //no auto-assignment! - + if($id) { $sql='UPDATE '.TOPIC_TABLE.' SET '.$sql.' WHERE topic_id='.db_input($id); if(db_query($sql)) @@ -230,10 +247,10 @@ class Topic { $sql='INSERT INTO '.TOPIC_TABLE.' SET '.$sql.',created=NOW()'; if(db_query($sql) && ($id=db_insert_id())) return $id; - + $errors['err']='Unable to create the topic. Internal error'; } - + return false; } } diff --git a/include/class.upgrader.php b/include/class.upgrader.php index 8b86b9e5724f07ea3fbb4bf3132a554e79ca7586..b71a3ff6bc4c121c52acef36ed2bc4a229e0c7b0 100644 --- a/include/class.upgrader.php +++ b/include/class.upgrader.php @@ -17,7 +17,165 @@ require_once INCLUDE_DIR.'class.setup.php'; require_once INCLUDE_DIR.'class.migrater.php'; -class Upgrader extends SetupWizard { +class Upgrader { + function Upgrader($prefix, $basedir) { + global $ost; + + $this->streams = array(); + foreach (DatabaseMigrater::getUpgradeStreams($basedir) as $stream=>$hash) { + $signature = $ost->getConfig()->getSchemaSignature($stream); + $this->streams[$stream] = new StreamUpgrader($signature, $hash, $stream, + $prefix, $basedir.$stream.'/', $this); + } + + //Init persistent state of upgrade. + $this->state = &$_SESSION['ost_upgrader']['state']; + + $this->mode = &$_SESSION['ost_upgrader']['mode']; + + $this->current = &$_SESSION['ost_upgrader']['stream']; + if (!$this->current || $this->getCurrentStream()->isFinished()) { + $streams = array_keys($this->streams); + do { + $this->current = array_shift($streams); + } while ($this->current && $this->getCurrentStream()->isFinished()); + } + } + + function getCurrentStream() { + return $this->streams[$this->current]; + } + + function isUpgradable() { + if ($this->isAborted()) + return false; + + foreach ($this->streams as $s) + if (!$s->isUpgradable()) + return false; + + return true; + } + + function isAborted() { + return !strcasecmp($this->getState(), 'aborted'); + } + + function abort($msg, $debug=false) { + if ($this->getCurrentStream()) + $this->getCurrentStream()->abort($msg, $debug); + } + + function getState() { + return $this->state; + } + + function setState($state) { + $this->state = $state; + if ($state == 'done') + $this->createUpgradedTicket(); + } + + function createUpgradedTicket() { + global $cfg; + + //Create a ticket to make the system warm and happy. + $dept_id = $cfg->getDefaultDeptId(); + $prio_id = $cfg->getDefaultPriorityId(); + $sql='INSERT INTO '.TICKET_TABLE.' SET created=NOW(), status="open", source="Web" ' + ." ,priority_id=$prio_id, dept_id=$dept_id, topic_id=0 " + .' ,ticketID='.db_input(Misc::randNumber(6)) + .' ,email="support@osticket.com" ' + .' ,name="osTicket Support" ' + .' ,subject="osTicket Upgraded!"'; + + if(db_query($sql, false) && ($tid=db_insert_id())) { + if(!($msg=file_get_contents(UPGRADE_DIR.'msg/upgraded.txt'))) + $msg='Congratulations and Thank you for choosing osTicket!'; + + $sql='INSERT INTO '.TICKET_THREAD_TABLE.' SET created=NOW()' + .', source="Web" ' + .', thread_type="M" ' + .', ticket_id='.db_input($tid) + .', title='.db_input('osTicket Upgraded') + .', body='.db_input($msg); + db_query($sql, false); + } + } + + function getMode() { + return $this->mode; + } + + function setMode($mode) { + $this->mode = $mode; + } + + function upgrade() { + if (!$this->current) + return true; + + return $this->getCurrentStream()->upgrade(); + } + + function check_prereq() { + if ($this->getCurrentStream()) + return $this->getCurrentStream()->check_prereq(); + } + function check_php() { + if ($this->getCurrentStream()) + return $this->getCurrentStream()->check_php(); + } + function check_mysql() { + if ($this->getCurrentStream()) + return $this->getCurrentStream()->check_mysql(); + } + + + function getTask() { + if($this->getCurrentStream()) + return $this->getCurrentStream()->getTask(); + } + + function doTask() { + return $this->getCurrentStream()->doTask(); + } + + function getErrors() { + if ($this->getCurrentStream()) + return $this->getCurrentStream()->getErrors(); + } + + function getNextAction() { + if ($this->getCurrentStream()) + return $this->getCurrentStream()->getNextAction(); + } + + function getNextVersion() { + return $this->getCurrentStream()->getNextVersion(); + } + + function getSchemaSignature() { + if ($this->getCurrentStream()) + return $this->getCurrentStream()->getSchemaSignature(); + } + + function getSHash() { + if ($this->getCurrentStream()) + return $this->getCurrentStream()->getSHash(); + } +} + +/** + * Updates a single database stream. In the classical sense, osTicket only + * maintained a single database update stream. In that model, this + * represents upgrading that single stream. In multi-stream mode, + * customizations and plugins are supported to have their own respective + * database update streams. The Upgrader class is used to coordinate updates + * for all the streams, whereas the work to upgrade each stream is done in + * this class + */ +class StreamUpgrader extends SetupWizard { var $prefix; var $sqldir; @@ -26,29 +184,40 @@ class Upgrader extends SetupWizard { var $state; var $mode; - function Upgrader($signature, $prefix, $sqldir) { - - $this->signature = $signature; + var $phash; + + /** + * Parameters: + * schema_signature - (string<hash-hex>) Current database-reflected (via + * config table) version of the stream + * target - (stream<hash-hex>) Current stream tip, as reflected by + * streams/<stream>.sig + * stream - (string) Name of the stream (folder) + * prefix - (string) Database table prefix + * sqldir - (string<path>) Path of sql patches + * upgrader - (Upgrader) Parent coordinator of parallel stream updates + */ + function StreamUpgrader($schema_signature, $target, $stream, $prefix, $sqldir, $upgrader) { + + $this->signature = $schema_signature; + $this->target = $target; $this->prefix = $prefix; $this->sqldir = $sqldir; $this->errors = array(); $this->mode = 'ajax'; // + $this->upgrader = $upgrader; + $this->name = $stream; //Disable time limit if - safe mode is set. if(!ini_get('safe_mode')) set_time_limit(0); - //Init persistent state of upgrade. - $this->state = &$_SESSION['ost_upgrader']['state']; - - $this->mode = &$_SESSION['ost_upgrader']['mode']; - //Init the task Manager. if(!isset($_SESSION['ost_upgrader'][$this->getShash()])) - $_SESSION['ost_upgrader'][$this->getShash()]['tasks']=array(); + $_SESSION['ost_upgrader']['task'] = array(); //Tasks to perform - saved on the session. - $this->tasks = &$_SESSION['ost_upgrader'][$this->getShash()]['tasks']; + $this->phash = &$_SESSION['ost_upgrader']['phash']; //Database migrater $this->migrater = null; @@ -57,9 +226,10 @@ class Upgrader extends SetupWizard { function onError($error) { global $ost, $thisstaff; - $ost->logError('Upgrader Error', $error); + $subject = '['.$this->name.']: Upgrader Error'; + $ost->logError($subject, $error); $this->setError($error); - $this->setState('aborted'); + $this->upgrader->setState('aborted'); //Alert staff upgrading the system - if the email is not same as admin's // admin gets alerted on error log (above) @@ -70,7 +240,6 @@ class Upgrader extends SetupWizard { if(!($email=$ost->getConfig()->getAlertEmail())) $email=$ost->getConfig()->getDefaultEmail(); //will take the default email. - $subject = 'Upgrader Error'; if($email) { $email->sendAlert($thisstaff->getEmail(), $subject, $error); } else {//no luck - try the system mail. @@ -80,11 +249,7 @@ class Upgrader extends SetupWizard { } function isUpgradable() { - return (!$this->isAborted() && $this->getNextPatch()); - } - - function isAborted() { - return !strcasecmp($this->getState(), 'aborted'); + return $this->getNextPatch(); } function getSchemaSignature() { @@ -103,25 +268,9 @@ class Upgrader extends SetupWizard { return $this->sqldir; } - function getState() { - return $this->state; - } - - function setState($state) { - $this->state = $state; - } - - function getMode() { - return $this->mode; - } - - function setMode($mode) { - $this->mode = $mode; - } - function getMigrater() { if(!$this->migrater) - $this->migrater = new DatabaseMigrater($this->signature, SCHEMA_SIGNATURE, $this->sqldir); + $this->migrater = new DatabaseMigrater($this->signature, $this->target, $this->sqldir); return $this->migrater; } @@ -146,12 +295,19 @@ class Upgrader extends SetupWizard { return $info['version']; } + function isFinished() { + # TODO: 1. Check if current and target hashes match, + # 2. Any pending tasks + return !($this->getNextPatch() || $this->getPendingTask()); + } + function readPatchInfo($patch) { - $info = array(); - if (preg_match('/\*(.*)\*/', file_get_contents($patch), $matches)) { - if (preg_match('/@([\w\d_-]+)\s+(.*)$/', $matches[0], $matches2)) + $info = $matches = $matches2 = array(); + if (preg_match(':/\*\*(.*)\*/:s', file_get_contents($patch), $matches)) { + if (preg_match_all('/@([\w\d_-]+)\s+(.*)$/m', $matches[0], + $matches2, PREG_SET_ORDER)) foreach ($matches2 as $match) - $info[$match[0]] = $match[1]; + $info[$match[1]] = $match[2]; } if (!isset($info['version'])) $info['version'] = substr(basename($patch), 9, 8); @@ -161,95 +317,81 @@ class Upgrader extends SetupWizard { function getNextAction() { $action='Upgrade osTicket to '.$this->getVersion(); - if($this->getNumPendingTasks() && ($task=$this->getNextTask())) { - $action = $task['desc']; - if($task['status']) //Progress report... - $action.=' ('.$task['status'].')'; + if($task=$this->getTask()) { + $action = $task->getDescription() .' ('.$task->getStatus().')'; } elseif($this->isUpgradable() && ($nextversion = $this->getNextVersion())) { $action = "Upgrade to $nextversion"; } - return $action; + return '['.$this->name.'] '.$action; } - function getNumPendingTasks() { - - return count($this->getPendingTasks()); - } - - function getPendingTasks() { + function getPendingTask() { $pending=array(); - if(($tasks=$this->getTasks())) { - foreach($tasks as $k => $task) { - if(!$task['done']) - $pending[$k] = $task; - } - } - - return $pending; - } + if ($task=$this->getTask()) + return ($task->isFinished()) ? 1 : 0; - function getTasks() { - return $this->tasks; + return false; } - function getNextTask() { + function getTask() { + global $ost; - if(!($tasks=$this->getPendingTasks())) + $task_file = $this->getSQLDir() . "{$this->phash}.task.php"; + if (!file_exists($task_file)) return null; - return current($tasks); - } - - function removeTask($tId) { - - if(isset($this->tasks[$tId])) - unset($this->tasks[$tId]); - - return (!$this->tasks[$tId]); + if (!isset($this->task)) { + $class = (include $task_file); + if (!is_string($class) || !class_exists($class)) + return $ost->logError("Bogus migration task", "{$this->phash}:{$class}") ; + $this->task = new $class(); + if (isset($_SESSION['ost_upgrader']['task'][$this->phash])) + $this->task->wakeup($_SESSION['ost_upgrader']['task'][$this->phash]); + } + return $this->task; } - function setTaskStatus($tId, $status) { - if(isset($this->tasks[$tId])) - $this->tasks[$tId]['status'] = $status; - } + function doTask() { - function doTasks() { + if(!($task = $this->getTask())) + return false; //Nothing to do. - global $ost; - if(!($tasks=$this->getPendingTasks())) - return true; //Nothing to do. - - $c = count($tasks); - $ost->logDebug( - sprintf('Upgrader - %s (%d pending tasks).', $this->getShash(), $c), - sprintf('There are %d pending upgrade tasks for %s patch', $c, $this->getShash()) + $this->log( + sprintf('Upgrader - %s (task pending).', $this->getShash()), + sprintf('The %s task reports there is work to do', + get_class($task)) ); - $start_time = Misc::micro_time(); - foreach($tasks as $k => $task) { - //TODO: check time used vs. max execution - break if need be - if(call_user_func(array($this, $task['func']), $k)===0) { - $this->tasks[$k]['done'] = true; - } else { //Task has pending items to process. - break; - } - } + if(!($max_time = ini_get('max_execution_time'))) + $max_time = 30; //Default to 30 sec batches. - return $this->getPendingTasks(); + $task->run($max_time); + if (!$task->isFinished()) { + $_SESSION['ost_upgrader']['task'][$this->phash] = $task->sleep(); + return true; + } + // Run the cleanup script, if any, and destroy the task's session + // data + $this->cleanup(); + unset($_SESSION['ost_upgrader']['task'][$this->phash]); + $this->phash = null; + unset($this->task); + return false; } function upgrade() { global $ost; - if($this->getPendingTasks() || !($patches=$this->getPatches())) + if($this->getPendingTask() || !($patches=$this->getPatches())) return false; $start_time = Misc::micro_time(); if(!($max_time = ini_get('max_execution_time'))) $max_time = 300; //Apache/IIS defaults. - foreach ($patches as $patch) { + // Apply up to five patches at a time + foreach (array_slice($patches, 0, 5) as $patch) { //TODO: check time used vs. max execution - break if need be if (!$this->load_sql_file($patch, $this->getTablePrefix())) return false; @@ -265,21 +407,22 @@ class Upgrader extends SetupWizard { if(($info = $this->readPatchInfo($patch)) && $info['version']) $logMsg.= ' ('.$info['version'].') '; - $ost->logDebug("Upgrader - $shash applied", $logMsg); + $this->log("Upgrader - $shash applied", $logMsg); $this->signature = $shash; //Update signature to the *new* HEAD + $this->phash = $phash; - //Check if the said patch has scripted tasks - if(!($tasks=$this->getTasksForPatch($phash))) { - //Break IF elapsed time is greater than 80% max time allowed. - if(($elapsedtime=(Misc::micro_time()-$start_time)) && $max_time && $elapsedtime>($max_time*0.80)) + //Break IF elapsed time is greater than 80% max time allowed. + if (!($task=$this->getTask())) { + $this->cleanup(); + if (($elapsedtime=(Misc::micro_time()-$start_time)) + && $max_time && $elapsedtime>($max_time*0.80)) break; - - continue; - + else + // Apply the next patch + continue; } //We have work to do... set the tasks and break. - $_SESSION['ost_upgrader'][$shash]['tasks'] = $tasks; $_SESSION['ost_upgrader'][$shash]['state'] = 'upgrade'; break; } @@ -288,117 +431,31 @@ class Upgrader extends SetupWizard { $this->migrater = null; return true; - } - function getTasksForPatch($phash) { - - $tasks=array(); - switch($phash) { //Add patch specific scripted tasks. - case 'c00511c7-7be60a84': //V1.6 ST- 1.7 * {{MD5('1.6 ST') -> c00511c7c1db65c0cfad04b4842afc57}} - $tasks[] = array('func' => 'migrateSessionFile2DB', - 'desc' => 'Transitioning to db-backed sessions'); - break; - case '98ae1ed2-e342f869': //v1.6 RC1-4 -> v1.6 RC5 - $tasks[] = array('func' => 'migrateAPIKeys', - 'desc' => 'Migrating API keys to a new table'); - break; - case '435c62c3-2e7531a2': - $tasks[] = array('func' => 'migrateGroupDeptAccess', - 'desc' => 'Migrating group\'s department access to a new table'); - break; - case '15b30765-dd0022fb': - $tasks[] = array('func' => 'migrateAttachments2DB', - 'desc' => 'Migrating attachments to database, it might take a while depending on the number of files.'); - break; - } - - //Check IF SQL cleanup exists. - $file=$this->getSQLDir().$phash.'.cleanup.sql'; - if(file_exists($file)) - $tasks[] = array('func' => 'cleanup', - 'desc' => 'Post-upgrade cleanup!', - 'phash' => $phash); - - return $tasks; + function log($title, $message, $level=LOG_DEBUG) { + global $ost; + // Never alert the admin, and force the write to the database + $ost->log($level, $title, $message, false, true); } /************* TASKS **********************/ - function cleanup($taskId) { - global $ost; - - $phash = $this->tasks[$taskId]['phash']; - $file=$this->getSQLDir().$phash.'.cleanup.sql'; + function cleanup() { + $file = $this->getSQLDir().$this->phash.'.cleanup.sql'; if(!file_exists($file)) //No cleanup script. return 0; //We have a cleanup script ::XXX: Don't abort on error? - if($this->load_sql_file($file, $this->getTablePrefix(), false, true)) - return 0; - - $ost->logDebug('Upgrader', sprintf("%s: Unable to process cleanup file", - $phash)); - return 0; - } - - function migrateAttachments2DB($taskId) { - global $ost; - - if(!($max_time = ini_get('max_execution_time'))) - $max_time = 30; //Default to 30 sec batches. - - $att_migrater = new AttachmentMigrater(); - if($att_migrater->do_batch(($max_time*0.9), 100)===0) + if($this->load_sql_file($file, $this->getTablePrefix(), false, true)) { + $this->log("Upgrader - {$this->phash} cleanup", + "Applied cleanup script {$file}"); return 0; - - return $att_migrater->getQueueLength(); - } - - function migrateSessionFile2DB($taskId) { - # How about 'dis for a hack? - osTicketSession::write(session_id(), session_encode()); - return 0; - } - - function migrateAPIKeys($taskId) { - - $res = db_query('SELECT api_whitelist, api_key FROM '.CONFIG_TABLE.' WHERE id=1'); - if(!$res || !db_num_rows($res)) - return 0; //Reporting success. - - list($whitelist, $key) = db_fetch_row($res); - - $ips=array_filter(array_map('trim', explode(',', $whitelist))); - foreach($ips as $ip) { - $sql='INSERT INTO '.API_KEY_TABLE.' SET created=NOW(), updated=NOW(), isactive=1 ' - .',ipaddr='.db_input($ip) - .',apikey='.db_input(strtoupper(md5($ip.md5($key)))); - db_query($sql); - } - - return 0; - } - - function migrateGroupDeptAccess($taskId) { - - $res = db_query('SELECT group_id, dept_access FROM '.GROUP_TABLE); - if(!$res || !db_num_rows($res)) - return 0; //No groups?? - - while(list($groupId, $access) = db_fetch_row($res)) { - $depts=array_filter(array_map('trim', explode(',', $access))); - foreach($depts as $deptId) { - $sql='INSERT INTO '.GROUP_DEPT_TABLE - .' SET dept_id='.db_input($deptId).', group_id='.db_input($groupId); - db_query($sql); - } } + $this->log('Upgrader', sprintf("%s: Unable to process cleanup file", + $this->phash)); return 0; - - - } } ?> diff --git a/include/class.usersession.php b/include/class.usersession.php index 5e10df43f2d1e3c6b028b788980543b121ca8aca..b596934cf0da76c95f681899935672f04aac3f74 100644 --- a/include/class.usersession.php +++ b/include/class.usersession.php @@ -147,7 +147,7 @@ class StaffSession extends Staff { function StaffSession($var){ parent::Staff($var); - $this->session= new UserSession($var); + $this->session= new UserSession($this->getId()); } function isValid(){ diff --git a/include/class.validator.php b/include/class.validator.php index 611d8032fe921361dd9af5c09caea62a0198629e..f01bcb38ca85f799c9a7d8a187624907228d8941 100644 --- a/include/class.validator.php +++ b/include/class.validator.php @@ -3,7 +3,7 @@ class.validator.php Input validation helper. This class contains collection of functions used for data validation. - + Peter Rotich <peter@osticket.com> Copyright (c) 2006-2013 osTicket http://www.osticket.com @@ -28,11 +28,11 @@ class Validator { $this->fields=$fields; return (true); endif; - + return (false); } - - + + function validate($source,$userinput=true){ $this->errors=array(); @@ -40,7 +40,7 @@ class Validator { if(!$source || !is_array($source)) $this->errors['err']='Invalid input'; elseif(!$this->fields || !is_array($this->fields)) - $this->errors['err']='No fields setup'; + $this->errors['err']='No fields set up'; //Abort on error if($this->errors) return false; @@ -56,7 +56,7 @@ class Validator { foreach($this->fields as $k=>$field){ if(!$field['required'] && !$this->input[$k]) //NOT required...and no data provided... continue; - + if($field['required'] && !isset($this->input[$k]) || (!$this->input[$k] && $field['type']!='int')){ //Required...and no data provided... $this->errors[$k]=$field['error']; continue; @@ -67,7 +67,9 @@ class Validator { case 'int': if(!is_numeric($this->input[$k])) $this->errors[$k]=$field['error']; - break; + elseif ($field['min'] && $this->input[$k] < $field['min']) + $this->errors[$k]=$field['error']; + break; case 'double': if(!is_numeric($this->input[$k])) $this->errors[$k]=$field['error']; @@ -109,12 +111,13 @@ class Validator { $this->errors[$k]=$field['error'].' (5 chars min)'; break; case 'username': - if(strlen($this->input[$k])<2) - $this->errors[$k]=$field['error'].' (2 chars min)'; + $error = ''; + if (!$this->is_username($this->input[$k], $error)) + $this->errors[$k]=$field['error'].": $error"; break; case 'zipcode': if(!is_numeric($this->input[$k]) || (strlen($this->input[$k])!=5)) - $this->errors[$k]=$field['error']; + $this->errors[$k]=$field['error']; break; default://If param type is not set...or handle..error out... $this->errors[$k]=$field['error'].' (type not set)'; @@ -122,35 +125,36 @@ class Validator { } return ($this->errors)?(FALSE):(TRUE); } - + function iserror(){ return $this->errors?true:false; } - + function errors(){ return $this->errors; } - - /*** Functions below can be called directly without class instance. Validator::func(var..); ***/ + + /*** Functions below can be called directly without class instance. + Validator::func(var..); (nolint) ***/ function is_email($email) { - return (preg_match('/^([*+!.&#$|\'\\%\/0-9a-z^_`{}=?~:-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})$/i',trim(stripslashes($email)))); + return preg_match('/^([*+!.&#$|\'\\%\/0-9a-z^_`{}=?~:-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})$/i',$email); } function is_phone($phone) { /* We're not really validating the phone number but just making sure it doesn't contain illegal chars and of acceptable len */ $stripped=preg_replace("(\(|\)|\-|\.|\+|[ ]+)","",$phone); return (!is_numeric($stripped) || ((strlen($stripped)<7) || (strlen($stripped)>16)))?false:true; } - + function is_url($url) { //XXX: parse_url is not ideal for validating urls but it's ideal for basic checks. return ($url && ($info=parse_url($url)) && $info['host']); } function is_ip($ip) { - + if(!$ip or empty($ip)) return false; - + $ip=trim($ip); # Thanks to http://stackoverflow.com/a/1934546 if (function_exists('inet_pton')) { # PHP 5.1.0 @@ -166,6 +170,14 @@ class Validator { return false; } + function is_username($username, &$error='') { + if (strlen($username)<2) + $error = 'At least two (2) characters'; + elseif (!preg_match('/^[\w._-]+$/', $username)) + $error = 'Username contains invalid characters'; + return $error == ''; + } + function process($fields,$vars,&$errors){ $val = new Validator(); diff --git a/include/class.variable.php b/include/class.variable.php index ce82dc868dc72219f14a4bfcfc78cb1e9b551f63..3a71bd66de4239c47439cbd196061d1557ebf0f6 100644 --- a/include/class.variable.php +++ b/include/class.variable.php @@ -2,8 +2,8 @@ /********************************************************************* class.variable.php - Variable replacer - + Variable replacer + Used to parse, resolve and replace variables. Peter Rotich <peter@osticket.com> @@ -42,17 +42,17 @@ class VariableReplacer { function getErrors() { return $this->errors; } - + function getObj($tag) { return @$this->objects[$tag]; } function assign($var, $val='') { - + if($val && is_object($val)) { $this->objects[$var] = $val; } elseif($var && is_array($var)) { - foreach($var as $k => $v) + foreach($var as $k => $v) $this->assign($k, $v); } elseif($var) { $this->variables[$var] = $val; @@ -74,7 +74,7 @@ class VariableReplacer { return $this->getVar($rv, $part); } - + if(!$var || !is_callable(array($obj, 'getVar'))) return ""; @@ -84,9 +84,9 @@ class VariableReplacer { if(!is_object($rv)) return $rv; - + list(, $part) = explode('.', $var, 2); - + return $this->getVar($rv, $part); } @@ -110,7 +110,7 @@ class VariableReplacer { $parts = explode('.', $var, 2); if($parts && ($obj=$this->getObj($parts[0]))) return $this->getVar($obj, $parts[1]); - elseif($parts[0] && @isset($this->variables[$parts[0]])) //root overwrite + elseif($parts[0] && @isset($this->variables[$parts[0]])) //root override return $this->variables[$parts[0]]; //Unknown object or variable - leavig it alone. diff --git a/include/class.yaml.php b/include/class.yaml.php new file mode 100644 index 0000000000000000000000000000000000000000..d21605ca0b0cb04d178b36f38c7cf34cf0f5d253 --- /dev/null +++ b/include/class.yaml.php @@ -0,0 +1,41 @@ +<?php +/********************************************************************* + class.yaml.php + + Parses YAML data files into PHP associative arrays. Useful for initial + data shipped with osTicket. + + Currently, this module uses the pure-php implementation Spyc, written by + - Chris Wanstrath + - Vlad Andersen + and released under an MIT license. The software is available at + https://github.com/mustangostang/spyc + + Jared Hancock <jared@osticket.com> + Copyright (c) 2006-2013 osTicket + http://www.osticket.com + + Released under the GNU General Public License WITHOUT ANY WARRANTY. + See LICENSE.TXT for details. + + vim: expandtab sw=4 ts=4 sts=4: + $Id: $ +**********************************************************************/ + +require_once "Spyc.php"; + +class YamlDataParser { + /* static */ + function load($file) { + if (!file_exists($file)) { + raise_error("$file: File does not exist", 'YamlParserError'); + return false; + } + return Spyc::YAMLLoad($file); + } +} + +class YamlParserError extends Error { + var $title = 'Error parsing YAML document'; +} +?> diff --git a/include/client/faq.inc.php b/include/client/faq.inc.php index d0be23c9fd41b1e9d9572bdcdd854623e5e51a0c..a588eff5ff3618828e682f7cd363e8ca15544362 100644 --- a/include/client/faq.inc.php +++ b/include/client/faq.inc.php @@ -9,7 +9,7 @@ $category=$faq->getCategory(); <a href="index.php">All Categories</a> » <a href="faq.php?cid=<?php echo $category->getId(); ?>"><?php echo $category->getName(); ?></a> </div> -<div style="width:700;padding-top:2px; float:left;"> +<div style="width:700px;padding-top:2px; float:left;"> <strong style="font-size:16px;"><?php echo $faq->getQuestion() ?></strong> </div> <div style="float:right;text-align:right;padding-top:5px;padding-right:5px;"></div> diff --git a/include/client/header.inc.php b/include/client/header.inc.php index d4c90ff1fcec0a0bb4ba179dd675c738cbc5a06e..6636c4ddeb1c18d2b478d695a4226e8a219239e3 100644 --- a/include/client/header.inc.php +++ b/include/client/header.inc.php @@ -20,7 +20,10 @@ header("Content-Type: text/html; charset=UTF-8\r\n"); <body> <div id="container"> <div id="header"> - <a id="logo" href="<?php echo ROOT_PATH; ?>index.php" title="Support Center"><img src="<?php echo ASSETS_PATH; ?>images/logo.png" border=0 alt="Support Center"></a> + <a id="logo" href="<?php echo ROOT_PATH; ?>index.php" + title="Support Center"><img src="<?php echo ROOT_PATH; ?>logo.php" border=0 alt="<?php + echo $ost->getConfig()->getTitle(); ?>" + style="height: 5em"></a> <p> <?php if($thisclient && is_object($thisclient) && $thisclient->isValid()) { diff --git a/include/client/knowledgebase.inc.php b/include/client/knowledgebase.inc.php index 2c34a9d82027044d1b824fc68a9e4f690e0a544f..4d75bcba5be080eb463695fb8508d425ee11d608 100644 --- a/include/client/knowledgebase.inc.php +++ b/include/client/knowledgebase.inc.php @@ -61,18 +61,18 @@ if($_REQUEST['q'] || $_REQUEST['cid'] || $_REQUEST['topicId']) { //Search. .' LEFT JOIN '.FAQ_CATEGORY_TABLE.' cat ON(cat.category_id=faq.category_id) ' .' LEFT JOIN '.FAQ_TOPIC_TABLE.' ft ON(ft.faq_id=faq.faq_id) ' .' WHERE faq.ispublished=1 AND cat.ispublic=1'; - + if($_REQUEST['cid']) $sql.=' AND faq.category_id='.db_input($_REQUEST['cid']); - + if($_REQUEST['topicId']) $sql.=' AND ft.topic_id='.db_input($_REQUEST['topicId']); if($_REQUEST['q']) { - $sql.=" AND question LIKE ('%".db_input($_REQUEST['q'],false)."%') - OR answer LIKE ('%".db_input($_REQUEST['q'],false)."%') - OR keywords LIKE ('%".db_input($_REQUEST['q'],false)."%')"; + $sql.=" AND (question LIKE ('%".db_input($_REQUEST['q'],false)."%') + OR answer LIKE ('%".db_input($_REQUEST['q'],false)."%') + OR keywords LIKE ('%".db_input($_REQUEST['q'],false)."%'))"; } $sql.=' GROUP BY faq.faq_id'; diff --git a/include/client/thankyou.inc.php b/include/client/thankyou.inc.php deleted file mode 100644 index 6e2fe3b4b7218ec1468d09ec71fd1b70624cd70f..0000000000000000000000000000000000000000 --- a/include/client/thankyou.inc.php +++ /dev/null @@ -1,20 +0,0 @@ -<?php -if(!defined('OSTCLIENTINC') || !is_object($ticket)) die('Kwaheri rafiki!'); -//Please customize the message below to fit your organization speak! -?> -<div style="margin:5px 100px 100px 0;"> - <?php echo Format::htmlchars($ticket->getName()); ?>,<br> - <p> - Thank you for contacting us.<br> - A support ticket request has been created and a representative will be getting back to you shortly if necessary.</p> - - <?php if($cfg->autoRespONNewTicket()){ ?> - <p>An email with the ticket number has been sent to <b><?php echo $ticket->getEmail(); ?></b>. - You'll need the ticket number along with your email to view status and progress online. - </p> - <p> - If you wish to send additional comments or information regarding same issue, please follow the instructions on the email. - </p> - <?php } ?> - <p>Support Team </p> -</div> diff --git a/include/client/view.inc.php b/include/client/view.inc.php index 6684413c753e61bcb3188b94f5c8581754e9aa2b..f6bac88409d136cb6495b588f5cfe3c80549dae2 100644 --- a/include/client/view.inc.php +++ b/include/client/view.inc.php @@ -17,9 +17,9 @@ if(!$dept || !$dept->isPublic()) <a href="view.php?id=<?php echo $ticket->getExtId(); ?>" title="Reload"><span class="Icon refresh"> </span></a> </h1> </td> - </tr> + </tr> <tr> - <td width="50%"> + <td width="50%"> <table class="infoTable" cellspacing="1" cellpadding="3" width="100%" border="0"> <tr> <th width="100">Ticket Status:</th> @@ -58,10 +58,13 @@ if(!$dept || !$dept->isPublic()) <br> <span class="Icon thread">Ticket Thread</span> <div id="ticketThread"> -<?php +<?php if($ticket->getThreadCount() && ($thread=$ticket->getClientThread())) { $threadType=array('M' => 'message', 'R' => 'response'); foreach($thread as $entry) { + if ($entry['body'] == '-') + $entry['body'] = '(EMPTY)'; + //Making sure internal notes are not displayed due to backend MISTAKES! if(!$threadType[$entry['thread_type']]) continue; $poster = $entry['poster']; diff --git a/include/fpdf/fpdf.php b/include/fpdf/fpdf.php index f96a20ecb678428111ff8a342e0153c5914c4a5a..b2fc21dd740e0109f2e2ea1b468ae58b0e92bb1a 100644 --- a/include/fpdf/fpdf.php +++ b/include/fpdf/fpdf.php @@ -1010,13 +1010,13 @@ function Output($name='', $dest='') case 'I': //Send to standard output if(ob_get_length()) - $this->Error('Some data has already been output, can\'t send PDF file'); + $this->Error('Some data has already been output. Can\'t send PDF file'); if(php_sapi_name()!='cli') { //We send to a browser header('Content-Type: application/pdf'); if(headers_sent()) - $this->Error('Some data has already been output, can\'t send PDF file'); + $this->Error('Some data has already been output. Can\'t send PDF file'); header('Content-Length: '.strlen($this->buffer)); header('Content-Disposition: inline; filename="'.$name.'"'); header('Cache-Control: private, max-age=0, must-revalidate'); @@ -1028,10 +1028,10 @@ function Output($name='', $dest='') case 'D': //Download file if(ob_get_length()) - $this->Error('Some data has already been output, can\'t send PDF file'); + $this->Error('Some data has already been output. Can\'t send PDF file'); header('Content-Type: application/x-download'); if(headers_sent()) - $this->Error('Some data has already been output, can\'t send PDF file'); + $this->Error('Some data has already been output. Can\'t send PDF file'); header('Content-Length: '.strlen($this->buffer)); header('Content-Disposition: attachment; filename="'.$name.'"'); header('Cache-Control: private, max-age=0, must-revalidate'); diff --git a/include/i18n/en_US/templates/staff.pwreset.yaml b/include/i18n/en_US/templates/staff.pwreset.yaml new file mode 100644 index 0000000000000000000000000000000000000000..2ed5b3da46bd6dc3ce7595d40243caf249db9591 --- /dev/null +++ b/include/i18n/en_US/templates/staff.pwreset.yaml @@ -0,0 +1,20 @@ +# +# Email template: staff.pwreset +# +# Sent when a staff member requests a password reset via the <a>Forgot my +# password</a> link in the staff control login screen +# +--- +subject: osTicket Staff Password Reset +body: | + You or perhaps somebody you know has submitted a password reset request on + your behalf for the helpdesk at %{url}. + + If you feel that this has been done in error. Delete and disregard this + email. Your account is still secure and no one has been given access to it. + It is not locked and your password has not been reset. Someone could have + mistakenly entered your email address. + + Follow the link below to login to the help desk and change your password. + + %{reset_link} diff --git a/include/mysql.php b/include/mysql.php index 3a86c7a8c5e5b04e3e0533596109018b992640e1..4e3bd7eb8caf242c92b3846fb1b7d5c845f31a87 100644 --- a/include/mysql.php +++ b/include/mysql.php @@ -2,7 +2,7 @@ /********************************************************************* mysql.php - Collection of MySQL helper interface functions. + Collection of MySQL helper interface functions. Mostly wrappers with error/resource checking. @@ -16,18 +16,19 @@ vim: expandtab sw=4 ts=4 sts=4: **********************************************************************/ - function db_connect($host, $user, $passwd, $db = "") { - + function db_connect($host, $user, $passwd, $options = array()) { + //Assert if(!strlen($user) || !strlen($passwd) || !strlen($host)) return NULL; //Connect + $start = (double) microtime() * 1000000; if(!($dblink =@mysql_connect($host, $user, $passwd))) return NULL; //Select the database, if any. - if($db) db_select_database($db); + if($options['db']) db_select_database($options['db']); //set desired encoding just in case mysql charset is not UTF-8 - Thanks to FreshMedia @mysql_query('SET NAMES "utf8"'); @@ -36,7 +37,10 @@ @db_set_variable('sql_mode', ''); - return $dblink; + // Use connection timing to seed the random number generator + Misc::__rand_seed(((double) microtime() * 1000000) - $start); + + return $dblink; } function db_close() { @@ -47,7 +51,7 @@ function db_version() { $version=0; - if(preg_match('/(\d{1,2}\.\d{1,2}\.\d{1,2})/', + 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 @@ -77,18 +81,14 @@ function db_create_database($database, $charset='utf8', $collate='utf8_general_ci') { return @mysql_query(sprintf('CREATE DATABASE %s DEFAULT CHARACTER SET %s COLLATE %s', $database, $charset, $collate)); } - + // execute sql query - function db_query($query, $database="", $conn="") { + function db_query($query, $logError=true) { global $ost; - - if($conn) { /* connection is provided*/ - $res = ($database)?mysql_db_query($database, $query, $conn):mysql_query($query, $conn); - } else { - $res = ($database)?mysql_db_query($database, $query):mysql_query($query); - } - - if(!$res && $ost) { //error reporting + + $res = mysql_query($query); + + if(!$res && $logError && $ost) { //error reporting $msg='['.$query.']'."\n\n".db_error(); $ost->logDBError('DB Error #'.db_errno(), $msg); //echo $msg; #uncomment during debuging or dev. @@ -98,7 +98,7 @@ } function db_squery($query) { //smart db query...utilizing args and sprintf - + $args = func_get_args(); $query = array_shift($args); $query = str_replace("?", "%s", $query); @@ -108,7 +108,7 @@ return db_query($query); } - function db_count($query) { + function db_count($query) { return db_result(db_query($query)); } @@ -126,7 +126,7 @@ function db_fetch_field($res) { return ($res)?mysql_fetch_field($res):NULL; - } + } function db_assoc_array($res, $mode=false) { if($res && db_num_rows($res)) { @@ -159,13 +159,13 @@ function db_free_result($res) { return mysql_free_result($res); } - + function db_output($var) { if(!function_exists('get_magic_quotes_runtime') || !get_magic_quotes_runtime()) //Sucker is NOT on - thanks. return $var; - if (is_array($var)) + if (is_array($var)) return array_map('db_output', $var); return (!is_numeric($var))?stripslashes($var):$var; @@ -192,10 +192,18 @@ } function db_error() { - return mysql_error(); + return mysql_error(); } - + + function db_connect_error() { + return db_error(); + } + function db_errno() { return mysql_errno(); } + + function db_field_type($res, $col=0) { + return mysql_field_type($res, $col); + } ?> diff --git a/include/mysqli.php b/include/mysqli.php new file mode 100644 index 0000000000000000000000000000000000000000..ad545a5dbfd8a9fae8206c0e6e14189809e1105b --- /dev/null +++ b/include/mysqli.php @@ -0,0 +1,242 @@ +<?php +/********************************************************************* + mysqli.php + + Collection of MySQL helper interface functions. + + Mostly wrappers with error/resource checking. + + Peter Rotich <peter@osticket.com> + Jared Hancock <jared@osticket.com> + Copyright (c) 2006-2013 osTicket + http://www.osticket.com + + Released under the GNU General Public License WITHOUT ANY WARRANTY. + See LICENSE.TXT for details. + + vim: expandtab sw=4 ts=4 sts=4: +**********************************************************************/ +$__db = null; + +function db_connect($host, $user, $passwd, $options = array()) { + global $__db; + + //Assert + if(!strlen($user) || !strlen($host)) + return NULL; + + if (!($__db = mysqli_init())) + return NULL; + + // Setup SSL if enabled + if (isset($options['ssl'])) + $__db->ssl_set( # nolint + $options['ssl']['key'], + $options['ssl']['cert'], + $options['ssl']['ca'], + null, null); + elseif(!$passwd) + return NULL; + + //Connectr + $start = microtime(true); + if(!@$__db->real_connect($host, $user, $passwd)) # nolint + return NULL; + + //Select the database, if any. + if(isset($options['db'])) $__db->select_db($options['db']); # nolint + + //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_set_variable('sql_mode', ''); + + // Use connection timing to seed the random number generator + Misc::__rand_seed((microtime(true) - $start) * 1000000); + + return $__db; +} + +function db_close() { + global $__db; + return @$__db->close(); +} + +function db_version() { + + $version=0; + if(preg_match('/(\d{1,2}\.\d{1,2}\.\d{1,2})/', + db_result(db_query('SELECT VERSION()')), + $matches)) # nolint + $version=$matches[1]; # nolint + + return $version; +} + +function db_timezone() { + return db_get_variable('time_zone'); +} + +function db_get_variable($variable, $type='session') { + $sql =sprintf('SELECT @@%s.%s', $type, $variable); + return db_result(db_query($sql)); +} + +function db_set_variable($variable, $value, $type='session') { + $sql =sprintf('SET %s %s=%s',strtoupper($type), $variable, db_input($value)); + return db_query($sql); +} + + +function db_select_database($database) { + global $__db; + return ($database && @$__db->select_db($database)); # nolint +} + +function db_create_database($database, $charset='utf8', + $collate='utf8_general_ci') { + global $__db; + return @$__db->query( # nolint + sprintf('CREATE DATABASE %s DEFAULT CHARACTER SET %s COLLATE %s', + $database, $charset, $collate)); +} + +// execute sql query +function db_query($query, $logError=true) { + global $ost, $__db; + + $res = $__db->query($query); + + if(!$res && $logError && $ost) { //error reporting + $msg='['.$query.']'."\n\n".db_error(); + $ost->logDBError('DB Error #'.db_errno(), $msg); + //echo $msg; #uncomment during debuging or dev. + } + + return $res; +} + +function db_squery($query) { //smart db query...utilizing args and sprintf + + $args = func_get_args(); + $query = array_shift($args); + $query = str_replace("?", "%s", $query); + $args = array_map('db_real_escape', $args); + array_unshift($args, $query); + $query = call_user_func_array('sprintf', $args); + return db_query($query); +} + +function db_count($query) { + return db_result(db_query($query)); +} + +function db_result($res, $row=0) { + if (!$res) + return NULL; + + $res->data_seek($row); # nolint + 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 +} + +function db_fetch_row($res) { + return ($res) ? db_output($res->fetch_row()) : NULL; # nolint +} + +function db_fetch_field($res) { + return ($res) ? $res->fetch_field() : NULL; # nolint +} + +function db_assoc_array($res, $mode=false) { + if($res && db_num_rows($res)) { + while ($row=db_fetch_array($res, $mode)) + $result[]=$row; + } + return $result; +} + +function db_num_rows($res) { + return ($res) ? $res->num_rows : 0; # nolint +} + +function db_affected_rows() { + global $__db; + return $__db->affected_rows; +} + +function db_data_seek($res, $row_number) { + return ($res && $res->data_seek($row_number)); # nolint +} + +function db_data_reset($res) { + return db_data_seek($res, 0); +} + +function db_insert_id() { + global $__db; + return $__db->insert_id; +} + +function db_free_result($res) { + return ($res && $res->free()); # nolint +} + +function db_output($var) { + + if(!function_exists('get_magic_quotes_runtime') || !get_magic_quotes_runtime()) //Sucker is NOT on - thanks. + return $var; + + if (is_array($var)) + return array_map('db_output', $var); + + return (!is_numeric($var))?stripslashes($var):$var; + +} + +//Do not call this function directly...use db_input +function db_real_escape($val, $quote=false) { + global $__db; + + //Magic quotes crap is taken care of in main.inc.php + $val=$__db->real_escape_string($val); + + return ($quote)?"'$val'":$val; +} + +function db_input($var, $quote=true) { + + if(is_array($var)) + return array_map('db_input', $var, array_fill(0, count($var), $quote)); + elseif($var && preg_match("/^\d+(\.\d+)?$/", $var)) + return $var; + + return db_real_escape($var, $quote); +} + +function db_field_type($res, $col=0) { + global $__db; + return $res->fetch_field_direct($col); # nolint +} + +function db_connect_error() { + global $__db; + return $__db->connect_error; +} + +function db_error() { + global $__db; + return $__db->error; +} + +function db_errno() { + global $__db; + return $__db->errno; +} +?> diff --git a/include/ost-sampleconfig.php b/include/ost-sampleconfig.php index 8a1f3b98eed8b51f6cbd535a8c181bebb5b7f312..d3cbb517a3b2922444a6ccdbd9fcb936439cf5a1 100644 --- a/include/ost-sampleconfig.php +++ b/include/ost-sampleconfig.php @@ -4,7 +4,7 @@ Static osTicket configuration file. Mainly useful for mysql login info. Created during installation process and shouldn't change even on upgrades. - + Peter Rotich <peter@osticket.com> Copyright (c) 2006-2010 osTicket http://www.osticket.com @@ -36,11 +36,31 @@ define('ADMIN_EMAIL','%ADMIN-EMAIL'); #Mysql Login info define('DBTYPE','mysql'); -define('DBHOST','%CONFIG-DBHOST'); +define('DBHOST','%CONFIG-DBHOST'); define('DBNAME','%CONFIG-DBNAME'); define('DBUSER','%CONFIG-DBUSER'); define('DBPASS','%CONFIG-DBPASS'); +# SSL Options +# --------------------------------------------------- +# SSL options for MySQL can be enabled by adding a certificate allowed by +# the database server here. To use SSL, you must have a client certificate +# signed by a CA (certificate authority). You can easily create this +# yourself with the EasyRSA suite. Give the public CA certificate, and both +# the public and private parts of your client certificate below. +# +# Once configured, you can ask MySQL to require the certificate for +# connections: +# +# > create user osticket; +# > grant all on osticket.* to osticket require subject '<subject>'; +# +# More information (to-be) available in doc/security/hardening.md + +# define('DBSSLCA','/path/to/ca.crt'); +# define('DBSSLCERT','/path/to/client.crt'); +# define('DBSSLKEY','/path/to/client.key'); + #Table prefix define('TABLE_PREFIX','%CONFIG-PREFIX'); diff --git a/include/pear/Auth/SASL/SCRAM.php b/include/pear/Auth/SASL/SCRAM.php index cbca500e47fcb522f8929265971c84af0b3b64f2..2607b61488cb7c71b953f1234887d4314ec5ffae 100644 --- a/include/pear/Auth/SASL/SCRAM.php +++ b/include/pear/Auth/SASL/SCRAM.php @@ -211,7 +211,7 @@ class Auth_SASL_SCRAM extends Auth_SASL_Common $channel_binding = 'c=' . base64_encode($this->gs2_header); // TODO: support channel binding. $final_message = $channel_binding . ',r=' . $nonce; // XXX: no extension. - // TODO: $password = $this->normalize($password); // SASLprep profile of stringprep. + // TODO: $password = $this->normalize($password); // SASLprep profile of stringprep. nolint $saltedPassword = $this->hi($password, $salt, $i); $this->saltedPassword = $saltedPassword; $clientKey = call_user_func($this->hmac, $saltedPassword, "Client Key", TRUE); diff --git a/include/pear/Crypt/AES.php b/include/pear/Crypt/AES.php new file mode 100644 index 0000000000000000000000000000000000000000..84de2d9acf8223294c6ce0f1e5fea7e4748ab900 --- /dev/null +++ b/include/pear/Crypt/AES.php @@ -0,0 +1,540 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * Pure-PHP implementation of AES. + * + * Uses mcrypt, if available, and an internal implementation, otherwise. + * + * PHP versions 4 and 5 + * + * If {@link Crypt_AES::setKeyLength() setKeyLength()} isn't called, it'll be calculated from + * {@link Crypt_AES::setKey() setKey()}. ie. if the key is 128-bits, the key length will be 128-bits. If it's 136-bits + * it'll be null-padded to 160-bits and 160 bits will be the key length until {@link Crypt_Rijndael::setKey() setKey()} + * is called, again, at which point, it'll be recalculated. + * + * Since Crypt_AES extends Crypt_Rijndael, some functions are available to be called that, in the context of AES, don't + * make a whole lot of sense. {@link Crypt_AES::setBlockLength() setBlockLength()}, for instance. Calling that function, + * however possible, won't do anything (AES has a fixed block length whereas Rijndael has a variable one). + * + * Here's a short example of how to use this library: + * <code> + * <?php + * include('Crypt/AES.php'); + * + * $aes = new Crypt_AES(); + * + * $aes->setKey('abcdefghijklmnop'); + * + * $size = 10 * 1024; + * $plaintext = ''; + * for ($i = 0; $i < $size; $i++) { + * $plaintext.= 'a'; + * } + * + * echo $aes->decrypt($aes->encrypt($plaintext)); + * ?> + * </code> + * + * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @category Crypt + * @package Crypt_AES + * @author Jim Wigginton <terrafrost@php.net> + * @copyright MMVIII Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +/** + * Include Crypt_Rijndael + */ +if (!class_exists('Crypt_Rijndael')) { + require_once 'Rijndael.php'; +} + +/**#@+ + * @access public + * @see Crypt_AES::encrypt() + * @see Crypt_AES::decrypt() + */ +/** + * Encrypt / decrypt using the Counter mode. + * + * Set to -1 since that's what Crypt/Random.php uses to index the CTR mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Counter_.28CTR.29 + */ +define('CRYPT_AES_MODE_CTR', -1); +/** + * Encrypt / decrypt using the Electronic Code Book mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29 + */ +define('CRYPT_AES_MODE_ECB', 1); +/** + * Encrypt / decrypt using the Code Book Chaining mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29 + */ +define('CRYPT_AES_MODE_CBC', 2); +/** + * Encrypt / decrypt using the Cipher Feedback mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher_feedback_.28CFB.29 + */ +define('CRYPT_AES_MODE_CFB', 3); +/** + * Encrypt / decrypt using the Cipher Feedback mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Output_feedback_.28OFB.29 + */ +define('CRYPT_AES_MODE_OFB', 4); +/**#@-*/ + +/**#@+ + * @access private + * @see Crypt_AES::Crypt_AES() + */ +/** + * Toggles the internal implementation + */ +define('CRYPT_AES_MODE_INTERNAL', 1); +/** + * Toggles the mcrypt implementation + */ +define('CRYPT_AES_MODE_MCRYPT', 2); +/**#@-*/ + +/** + * Pure-PHP implementation of AES. + * + * @author Jim Wigginton <terrafrost@php.net> + * @version 0.1.0 + * @access public + * @package Crypt_AES + */ +class Crypt_AES extends Crypt_Rijndael { + /** + * mcrypt resource for encryption + * + * The mcrypt resource can be recreated every time something needs to be created or it can be created just once. + * Since mcrypt operates in continuous mode, by default, it'll need to be recreated when in non-continuous mode. + * + * @see Crypt_AES::encrypt() + * @var String + * @access private + */ + var $enmcrypt; + + /** + * mcrypt resource for decryption + * + * The mcrypt resource can be recreated every time something needs to be created or it can be created just once. + * Since mcrypt operates in continuous mode, by default, it'll need to be recreated when in non-continuous mode. + * + * @see Crypt_AES::decrypt() + * @var String + * @access private + */ + var $demcrypt; + + /** + * mcrypt resource for CFB mode + * + * @see Crypt_AES::encrypt() + * @see Crypt_AES::decrypt() + * @var String + * @access private + */ + var $ecb; + + /** + * Default Constructor. + * + * Determines whether or not the mcrypt extension should be used. $mode should only, at present, be + * CRYPT_AES_MODE_ECB or CRYPT_AES_MODE_CBC. If not explictly set, CRYPT_AES_MODE_CBC will be used. + * + * @param optional Integer $mode + * @return Crypt_AES + * @access public + */ + function Crypt_AES($mode = CRYPT_AES_MODE_CBC) + { + if ( !defined('CRYPT_AES_MODE') ) { + switch (true) { + case extension_loaded('mcrypt') && in_array('rijndael-128', mcrypt_list_algorithms()): + define('CRYPT_AES_MODE', CRYPT_AES_MODE_MCRYPT); + break; + default: + define('CRYPT_AES_MODE', CRYPT_AES_MODE_INTERNAL); + } + } + + switch ( CRYPT_AES_MODE ) { + case CRYPT_AES_MODE_MCRYPT: + switch ($mode) { + case CRYPT_AES_MODE_ECB: + $this->paddable = true; + $this->mode = MCRYPT_MODE_ECB; + break; + case CRYPT_AES_MODE_CTR: + // ctr doesn't have a constant associated with it even though it appears to be fairly widely + // supported. in lieu of knowing just how widely supported it is, i've, for now, opted not to + // include a compatibility layer. the layer has been implemented but, for now, is commented out. + $this->mode = 'ctr'; + //$this->mode = in_array('ctr', mcrypt_list_modes()) ? 'ctr' : CRYPT_AES_MODE_CTR; + break; + case CRYPT_AES_MODE_CFB: + $this->mode = 'ncfb'; + break; + case CRYPT_AES_MODE_OFB: + $this->mode = MCRYPT_MODE_NOFB; + break; + case CRYPT_AES_MODE_CBC: + default: + $this->paddable = true; + $this->mode = MCRYPT_MODE_CBC; + } + + break; + default: + switch ($mode) { + case CRYPT_AES_MODE_ECB: + $this->paddable = true; + $this->mode = CRYPT_RIJNDAEL_MODE_ECB; + break; + case CRYPT_AES_MODE_CTR: + $this->mode = CRYPT_RIJNDAEL_MODE_CTR; + break; + case CRYPT_AES_MODE_CFB: + $this->mode = CRYPT_RIJNDAEL_MODE_CFB; + break; + case CRYPT_AES_MODE_OFB: + $this->mode = CRYPT_RIJNDAEL_MODE_OFB; + break; + case CRYPT_AES_MODE_CBC: + default: + $this->paddable = true; + $this->mode = CRYPT_RIJNDAEL_MODE_CBC; + } + } + + if (CRYPT_AES_MODE == CRYPT_AES_MODE_INTERNAL) { + parent::Crypt_Rijndael($this->mode); + } + + } + + /** + * Dummy function + * + * Since Crypt_AES extends Crypt_Rijndael, this function is, technically, available, but it doesn't do anything. + * + * @access public + * @param Integer $length + */ + function setBlockLength($length) + { + return; + } + + /** + * Sets the initialization vector. (optional) + * + * SetIV is not required when CRYPT_RIJNDAEL_MODE_ECB is being used. If not explictly set, it'll be assumed + * to be all zero's. + * + * @access public + * @param String $iv + */ + function setIV($iv) + { + parent::setIV($iv); + if ( CRYPT_AES_MODE == CRYPT_AES_MODE_MCRYPT ) { + $this->changed = true; + } + } + + /** + * Encrypts a message. + * + * $plaintext will be padded with up to 16 additional bytes. Other AES implementations may or may not pad in the + * same manner. Other common approaches to padding and the reasons why it's necessary are discussed in the following + * URL: + * + * {@link http://www.di-mgt.com.au/cryptopad.html http://www.di-mgt.com.au/cryptopad.html} + * + * An alternative to padding is to, separately, send the length of the file. This is what SSH, in fact, does. + * strlen($plaintext) will still need to be a multiple of 16, however, arbitrary values can be added to make it that + * length. + * + * @see Crypt_AES::decrypt() + * @access public + * @param String $plaintext + */ + function encrypt($plaintext) + { + if ( CRYPT_AES_MODE == CRYPT_AES_MODE_MCRYPT ) { + $this->_mcryptSetup(); + + // re: http://phpseclib.sourceforge.net/cfb-demo.phps + // using mcrypt's default handing of CFB the above would output two different things. using phpseclib's + // rewritten CFB implementation the above outputs the same thing twice. + if ($this->mode == 'ncfb' && $this->continuousBuffer) { + $iv = &$this->encryptIV; + $pos = &$this->enbuffer['pos']; + $len = strlen($plaintext); + $ciphertext = ''; + $i = 0; + if ($pos) { + $orig_pos = $pos; + $max = 16 - $pos; + if ($len >= $max) { + $i = $max; + $len-= $max; + $pos = 0; + } else { + $i = $len; + $pos+= $len; + $len = 0; + } + $ciphertext = substr($iv, $orig_pos) ^ $plaintext; + $iv = substr_replace($iv, $ciphertext, $orig_pos, $i); + $this->enbuffer['enmcrypt_init'] = true; + } + if ($len >= 16) { + if ($this->enbuffer['enmcrypt_init'] === false || $len > 280) { + if ($this->enbuffer['enmcrypt_init'] === true) { + mcrypt_generic_init($this->enmcrypt, $this->key, $iv); + $this->enbuffer['enmcrypt_init'] = false; + } + $ciphertext.= mcrypt_generic($this->enmcrypt, substr($plaintext, $i, $len - $len % 16)); + $iv = substr($ciphertext, -16); + $len%= 16; + } else { + while ($len >= 16) { + $iv = mcrypt_generic($this->ecb, $iv) ^ substr($plaintext, $i, 16); + $ciphertext.= $iv; + $len-= 16; + $i+= 16; + } + } + } + + if ($len) { + $iv = mcrypt_generic($this->ecb, $iv); + $block = $iv ^ substr($plaintext, -$len); + $iv = substr_replace($iv, $block, 0, $len); + $ciphertext.= $block; + $pos = $len; + } + + return $ciphertext; + } + + if ($this->paddable) { + $plaintext = $this->_pad($plaintext); + } + + $ciphertext = mcrypt_generic($this->enmcrypt, $plaintext); + + if (!$this->continuousBuffer) { + mcrypt_generic_init($this->enmcrypt, $this->key, $this->iv); + } + + return $ciphertext; + } + + return parent::encrypt($plaintext); + } + + /** + * Decrypts a message. + * + * If strlen($ciphertext) is not a multiple of 16, null bytes will be added to the end of the string until it is. + * + * @see Crypt_AES::encrypt() + * @access public + * @param String $ciphertext + */ + function decrypt($ciphertext) + { + if ( CRYPT_AES_MODE == CRYPT_AES_MODE_MCRYPT ) { + $this->_mcryptSetup(); + + if ($this->mode == 'ncfb' && $this->continuousBuffer) { + $iv = &$this->decryptIV; + $pos = &$this->debuffer['pos']; + $len = strlen($ciphertext); + $plaintext = ''; + $i = 0; + if ($pos) { + $orig_pos = $pos; + $max = 16 - $pos; + if ($len >= $max) { + $i = $max; + $len-= $max; + $pos = 0; + } else { + $i = $len; + $pos+= $len; + $len = 0; + } + // ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $blocksize + $plaintext = substr($iv, $orig_pos) ^ $ciphertext; + $iv = substr_replace($iv, substr($ciphertext, 0, $i), $orig_pos, $i); + } + if ($len >= 16) { + $cb = substr($ciphertext, $i, $len - $len % 16); + $plaintext.= mcrypt_generic($this->ecb, $iv . $cb) ^ $cb; + $iv = substr($cb, -16); + $len%= 16; + } + if ($len) { + $iv = mcrypt_generic($this->ecb, $iv); + $plaintext.= $iv ^ substr($ciphertext, -$len); + $iv = substr_replace($iv, substr($ciphertext, -$len), 0, $len); + $pos = $len; + } + + return $plaintext; + } + + if ($this->paddable) { + // we pad with chr(0) since that's what mcrypt_generic does. to quote from http://php.net/function.mcrypt-generic : + // "The data is padded with "\0" to make sure the length of the data is n * blocksize." + $ciphertext = str_pad($ciphertext, (strlen($ciphertext) + 15) & 0xFFFFFFF0, chr(0)); + } + + $plaintext = mdecrypt_generic($this->demcrypt, $ciphertext); + + if (!$this->continuousBuffer) { + mcrypt_generic_init($this->demcrypt, $this->key, $this->iv); + } + + return $this->paddable ? $this->_unpad($plaintext) : $plaintext; + } + + return parent::decrypt($ciphertext); + } + + /** + * Setup mcrypt + * + * Validates all the variables. + * + * @access private + */ + function _mcryptSetup() + { + if (!$this->changed) { + return; + } + + if (!$this->explicit_key_length) { + // this just copied from Crypt_Rijndael::_setup() + $length = strlen($this->key) >> 2; + if ($length > 8) { + $length = 8; + } else if ($length < 4) { + $length = 4; + } + $this->Nk = $length; + $this->key_size = $length << 2; + } + + switch ($this->Nk) { + case 4: // 128 + $this->key_size = 16; + break; + case 5: // 160 + case 6: // 192 + $this->key_size = 24; + break; + case 7: // 224 + case 8: // 256 + $this->key_size = 32; + } + + $this->key = str_pad(substr($this->key, 0, $this->key_size), $this->key_size, chr(0)); + $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($this->iv, 0, 16), 16, chr(0)); + + if (!isset($this->enmcrypt)) { + $mode = $this->mode; + //$mode = $this->mode == CRYPT_AES_MODE_CTR ? MCRYPT_MODE_ECB : $this->mode; + + $this->demcrypt = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', $mode, ''); + $this->enmcrypt = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', $mode, ''); + + if ($mode == 'ncfb') { + $this->ecb = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, ''); + } + + } // else should mcrypt_generic_deinit be called? + + mcrypt_generic_init($this->demcrypt, $this->key, $this->iv); + mcrypt_generic_init($this->enmcrypt, $this->key, $this->iv); + + if ($this->mode == 'ncfb') { + mcrypt_generic_init($this->ecb, $this->key, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"); + } + + $this->changed = false; + } + + /** + * Treat consecutive "packets" as if they are a continuous buffer. + * + * The default behavior. + * + * @see Crypt_Rijndael::disableContinuousBuffer() + * @access public + */ + function enableContinuousBuffer() + { + parent::enableContinuousBuffer(); + + if (CRYPT_AES_MODE == CRYPT_AES_MODE_MCRYPT) { + $this->enbuffer['enmcrypt_init'] = true; + $this->debuffer['demcrypt_init'] = true; + } + } + + /** + * Treat consecutive packets as if they are a discontinuous buffer. + * + * The default behavior. + * + * @see Crypt_Rijndael::enableContinuousBuffer() + * @access public + */ + function disableContinuousBuffer() + { + parent::disableContinuousBuffer(); + + if (CRYPT_AES_MODE == CRYPT_AES_MODE_MCRYPT) { + mcrypt_generic_init($this->enmcrypt, $this->key, $this->iv); + mcrypt_generic_init($this->demcrypt, $this->key, $this->iv); + } + } +} + +// vim: ts=4:sw=4:et: +// vim6: fdl=1: diff --git a/include/pear/Crypt/Hash.php b/include/pear/Crypt/Hash.php new file mode 100644 index 0000000000000000000000000000000000000000..3b506164ea93102292b2073119945ce893911e16 --- /dev/null +++ b/include/pear/Crypt/Hash.php @@ -0,0 +1,823 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * Pure-PHP implementations of keyed-hash message authentication codes (HMACs) and various cryptographic hashing functions. + * + * Uses hash() or mhash() if available and an internal implementation, otherwise. Currently supports the following: + * + * md2, md5, md5-96, sha1, sha1-96, sha256, sha384, and sha512 + * + * If {@link Crypt_Hash::setKey() setKey()} is called, {@link Crypt_Hash::hash() hash()} will return the HMAC as opposed to + * the hash. If no valid algorithm is provided, sha1 will be used. + * + * PHP versions 4 and 5 + * + * {@internal The variable names are the same as those in + * {@link http://tools.ietf.org/html/rfc2104#section-2 RFC2104}.}} + * + * Here's a short example of how to use this library: + * <code> + * <?php + * include('Crypt/Hash.php'); + * + * $hash = new Crypt_Hash('sha1'); + * + * $hash->setKey('abcdefg'); + * + * echo base64_encode($hash->hash('abcdefg')); + * ?> + * </code> + * + * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @category Crypt + * @package Crypt_Hash + * @author Jim Wigginton <terrafrost@php.net> + * @copyright MMVII Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +/**#@+ + * @access private + * @see Crypt_Hash::Crypt_Hash() + */ +/** + * Toggles the internal implementation + */ +define('CRYPT_HASH_MODE_INTERNAL', 1); +/** + * Toggles the mhash() implementation, which has been deprecated on PHP 5.3.0+. + */ +define('CRYPT_HASH_MODE_MHASH', 2); +/** + * Toggles the hash() implementation, which works on PHP 5.1.2+. + */ +define('CRYPT_HASH_MODE_HASH', 3); +/**#@-*/ + +/** + * Pure-PHP implementations of keyed-hash message authentication codes (HMACs) and various cryptographic hashing functions. + * + * @author Jim Wigginton <terrafrost@php.net> + * @version 0.1.0 + * @access public + * @package Crypt_Hash + */ +class Crypt_Hash { + /** + * Byte-length of compression blocks / key (Internal HMAC) + * + * @see Crypt_Hash::setAlgorithm() #nolint + * @var Integer + * @access private + */ + var $b; + + /** + * Byte-length of hash output (Internal HMAC) + * + * @see Crypt_Hash::setHash() + * @var Integer + * @access private + */ + var $l = false; + + /** + * Hash Algorithm + * + * @see Crypt_Hash::setHash() + * @var String + * @access private + */ + var $hash; + + /** + * Key + * + * @see Crypt_Hash::setKey() + * @var String + * @access private + */ + var $key = false; + + /** + * Outer XOR (Internal HMAC) + * + * @see Crypt_Hash::setKey() + * @var String + * @access private + */ + var $opad; + + /** + * Inner XOR (Internal HMAC) + * + * @see Crypt_Hash::setKey() + * @var String + * @access private + */ + var $ipad; + + /** + * Default Constructor. + * + * @param optional String $hash + * @return Crypt_Hash + * @access public + */ + function Crypt_Hash($hash = 'sha1') + { + if ( !defined('CRYPT_HASH_MODE') ) { + switch (true) { + case extension_loaded('hash'): + define('CRYPT_HASH_MODE', CRYPT_HASH_MODE_HASH); + break; + case extension_loaded('mhash'): + define('CRYPT_HASH_MODE', CRYPT_HASH_MODE_MHASH); + break; + default: + define('CRYPT_HASH_MODE', CRYPT_HASH_MODE_INTERNAL); + } + } + + $this->setHash($hash); + } + + /** + * Sets the key for HMACs + * + * Keys can be of any length. + * + * @access public + * @param optional String $key + */ + function setKey($key = false) + { + $this->key = $key; + } + + /** + * Sets the hash function. + * + * @access public + * @param String $hash + */ + function setHash($hash) + { + $hash = strtolower($hash); + switch ($hash) { + case 'md5-96': + case 'sha1-96': + $this->l = 12; // 96 / 8 = 12 + break; + case 'md2': + case 'md5': + $this->l = 16; + break; + case 'sha1': + $this->l = 20; + break; + case 'sha256': + $this->l = 32; + break; + case 'sha384': + $this->l = 48; + break; + case 'sha512': + $this->l = 64; + } + + switch ($hash) { + case 'md2': + $mode = CRYPT_HASH_MODE == CRYPT_HASH_MODE_HASH && in_array('md2', hash_algos()) ? + CRYPT_HASH_MODE_HASH : CRYPT_HASH_MODE_INTERNAL; + break; + case 'sha384': + case 'sha512': + $mode = CRYPT_HASH_MODE == CRYPT_HASH_MODE_MHASH ? CRYPT_HASH_MODE_INTERNAL : CRYPT_HASH_MODE; + break; + default: + $mode = CRYPT_HASH_MODE; + } + + switch ( $mode ) { + case CRYPT_HASH_MODE_MHASH: + switch ($hash) { + case 'md5': + case 'md5-96': + $this->hash = MHASH_MD5; + break; + case 'sha256': + $this->hash = MHASH_SHA256; + break; + case 'sha1': + case 'sha1-96': + default: + $this->hash = MHASH_SHA1; + } + return; + case CRYPT_HASH_MODE_HASH: + switch ($hash) { + case 'md5': + case 'md5-96': + $this->hash = 'md5'; + return; + case 'md2': + case 'sha256': + case 'sha384': + case 'sha512': + $this->hash = $hash; + return; + case 'sha1': + case 'sha1-96': + default: + $this->hash = 'sha1'; + } + return; + } + + switch ($hash) { + case 'md2': + $this->b = 16; + $this->hash = array($this, '_md2'); + break; + case 'md5': + case 'md5-96': + $this->b = 64; + $this->hash = array($this, '_md5'); + break; + case 'sha256': + $this->b = 64; + $this->hash = array($this, '_sha256'); + break; + case 'sha384': + case 'sha512': + $this->b = 128; + $this->hash = array($this, '_sha512'); + break; + case 'sha1': + case 'sha1-96': + default: + $this->b = 64; + $this->hash = array($this, '_sha1'); + } + + $this->ipad = str_repeat(chr(0x36), $this->b); + $this->opad = str_repeat(chr(0x5C), $this->b); + } + + /** + * Compute the HMAC. + * + * @access public + * @param String $text + * @return String + */ + function hash($text) + { + $mode = is_array($this->hash) ? CRYPT_HASH_MODE_INTERNAL : CRYPT_HASH_MODE; + + if (!empty($this->key) || is_string($this->key)) { + switch ( $mode ) { + case CRYPT_HASH_MODE_MHASH: + $output = mhash($this->hash, $text, $this->key); + break; + case CRYPT_HASH_MODE_HASH: + $output = hash_hmac($this->hash, $text, $this->key, true); + break; + case CRYPT_HASH_MODE_INTERNAL: + /* "Applications that use keys longer than B bytes will first hash the key using H and then use the + resultant L byte string as the actual key to HMAC." + + -- http://tools.ietf.org/html/rfc2104#section-2 */ + $key = strlen($this->key) > $this->b ? call_user_func($this->hash, $this->key) : $this->key; + + $key = str_pad($key, $this->b, chr(0)); // step 1 + $temp = $this->ipad ^ $key; // step 2 + $temp .= $text; // step 3 + $temp = call_user_func($this->hash, $temp); // step 4 + $output = $this->opad ^ $key; // step 5 + $output.= $temp; // step 6 + $output = call_user_func($this->hash, $output); // step 7 + } + } else { + switch ( $mode ) { + case CRYPT_HASH_MODE_MHASH: + $output = mhash($this->hash, $text); + break; + case CRYPT_HASH_MODE_HASH: + $output = hash($this->hash, $text, true); + break; + case CRYPT_HASH_MODE_INTERNAL: + $output = call_user_func($this->hash, $text); + } + } + + return substr($output, 0, $this->l); + } + + /** + * Returns the hash length (in bytes) + * + * @access public + * @return Integer + */ + function getLength() + { + return $this->l; + } + + /** + * Wrapper for MD5 + * + * @access private + * @param String $m + */ + function _md5($m) + { + return pack('H*', md5($m)); + } + + /** + * Wrapper for SHA1 + * + * @access private + * @param String $m + */ + function _sha1($m) + { + return pack('H*', sha1($m)); + } + + /** + * Pure-PHP implementation of MD2 + * + * See {@link http://tools.ietf.org/html/rfc1319 RFC1319}. + * + * @access private + * @param String $m + */ + function _md2($m) + { + static $s = array( + 41, 46, 67, 201, 162, 216, 124, 1, 61, 54, 84, 161, 236, 240, 6, + 19, 98, 167, 5, 243, 192, 199, 115, 140, 152, 147, 43, 217, 188, + 76, 130, 202, 30, 155, 87, 60, 253, 212, 224, 22, 103, 66, 111, 24, + 138, 23, 229, 18, 190, 78, 196, 214, 218, 158, 222, 73, 160, 251, + 245, 142, 187, 47, 238, 122, 169, 104, 121, 145, 21, 178, 7, 63, + 148, 194, 16, 137, 11, 34, 95, 33, 128, 127, 93, 154, 90, 144, 50, + 39, 53, 62, 204, 231, 191, 247, 151, 3, 255, 25, 48, 179, 72, 165, + 181, 209, 215, 94, 146, 42, 172, 86, 170, 198, 79, 184, 56, 210, + 150, 164, 125, 182, 118, 252, 107, 226, 156, 116, 4, 241, 69, 157, + 112, 89, 100, 113, 135, 32, 134, 91, 207, 101, 230, 45, 168, 2, 27, + 96, 37, 173, 174, 176, 185, 246, 28, 70, 97, 105, 52, 64, 126, 15, + 85, 71, 163, 35, 221, 81, 175, 58, 195, 92, 249, 206, 186, 197, + 234, 38, 44, 83, 13, 110, 133, 40, 132, 9, 211, 223, 205, 244, 65, + 129, 77, 82, 106, 220, 55, 200, 108, 193, 171, 250, 36, 225, 123, + 8, 12, 189, 177, 74, 120, 136, 149, 139, 227, 99, 232, 109, 233, + 203, 213, 254, 59, 0, 29, 57, 242, 239, 183, 14, 102, 88, 208, 228, + 166, 119, 114, 248, 235, 117, 75, 10, 49, 68, 80, 180, 143, 237, + 31, 26, 219, 153, 141, 51, 159, 17, 131, 20 + ); + + // Step 1. Append Padding Bytes + $pad = 16 - (strlen($m) & 0xF); + $m.= str_repeat(chr($pad), $pad); + + $length = strlen($m); + + // Step 2. Append Checksum + $c = str_repeat(chr(0), 16); + $l = chr(0); + for ($i = 0; $i < $length; $i+= 16) { + for ($j = 0; $j < 16; $j++) { + // RFC1319 incorrectly states that C[j] should be set to S[c xor L] + //$c[$j] = chr($s[ord($m[$i + $j] ^ $l)]); + // per <http://www.rfc-editor.org/errata_search.php?rfc=1319>, however, C[j] should be set to S[c xor L] xor C[j] + $c[$j] = chr($s[ord($m[$i + $j] ^ $l)] ^ ord($c[$j])); + $l = $c[$j]; + } + } + $m.= $c; + + $length+= 16; + + // Step 3. Initialize MD Buffer + $x = str_repeat(chr(0), 48); + + // Step 4. Process Message in 16-Byte Blocks + for ($i = 0; $i < $length; $i+= 16) { + for ($j = 0; $j < 16; $j++) { + $x[$j + 16] = $m[$i + $j]; + $x[$j + 32] = $x[$j + 16] ^ $x[$j]; + } + $t = chr(0); + for ($j = 0; $j < 18; $j++) { + for ($k = 0; $k < 48; $k++) { + $x[$k] = $t = $x[$k] ^ chr($s[ord($t)]); + //$t = $x[$k] = $x[$k] ^ chr($s[ord($t)]); + } + $t = chr(ord($t) + $j); + } + } + + // Step 5. Output + return substr($x, 0, 16); + } + + /** + * Pure-PHP implementation of SHA256 + * + * See {@link http://en.wikipedia.org/wiki/SHA_hash_functions#SHA-256_.28a_SHA-2_variant.29_pseudocode SHA-256 (a SHA-2 variant) pseudocode - Wikipedia}. + * + * @access private + * @param String $m + */ + function _sha256($m) + { + if (extension_loaded('suhosin')) { + return pack('H*', sha256($m)); + } + + // Initialize variables + $hash = array( + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 + ); + // Initialize table of round constants + // (first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311) + static $k = array( + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 + ); + + // Pre-processing + $length = strlen($m); + // to round to nearest 56 mod 64, we'll add 64 - (length + (64 - 56)) % 64 + $m.= str_repeat(chr(0), 64 - (($length + 8) & 0x3F)); + $m[$length] = chr(0x80); + // we don't support hashing strings 512MB long + $m.= pack('N2', 0, $length << 3); + + // Process the message in successive 512-bit chunks + $chunks = str_split($m, 64); + foreach ($chunks as $chunk) { + $w = array(); + for ($i = 0; $i < 16; $i++) { + extract(unpack('Ntemp', $this->_string_shift($chunk, 4))); + $w[] = $temp; + } + + // Extend the sixteen 32-bit words into sixty-four 32-bit words + for ($i = 16; $i < 64; $i++) { + $s0 = $this->_rightRotate($w[$i - 15], 7) ^ + $this->_rightRotate($w[$i - 15], 18) ^ + $this->_rightShift( $w[$i - 15], 3); + $s1 = $this->_rightRotate($w[$i - 2], 17) ^ + $this->_rightRotate($w[$i - 2], 19) ^ + $this->_rightShift( $w[$i - 2], 10); + $w[$i] = $this->_add($w[$i - 16], $s0, $w[$i - 7], $s1); + + } + + // Initialize hash value for this chunk + list($a, $b, $c, $d, $e, $f, $g, $h) = $hash; + + // Main loop + for ($i = 0; $i < 64; $i++) { + $s0 = $this->_rightRotate($a, 2) ^ + $this->_rightRotate($a, 13) ^ + $this->_rightRotate($a, 22); + $maj = ($a & $b) ^ + ($a & $c) ^ + ($b & $c); + $t2 = $this->_add($s0, $maj); + + $s1 = $this->_rightRotate($e, 6) ^ + $this->_rightRotate($e, 11) ^ + $this->_rightRotate($e, 25); + $ch = ($e & $f) ^ + ($this->_not($e) & $g); + $t1 = $this->_add($h, $s1, $ch, $k[$i], $w[$i]); + + $h = $g; + $g = $f; + $f = $e; + $e = $this->_add($d, $t1); + $d = $c; + $c = $b; + $b = $a; + $a = $this->_add($t1, $t2); + } + + // Add this chunk's hash to result so far + $hash = array( + $this->_add($hash[0], $a), + $this->_add($hash[1], $b), + $this->_add($hash[2], $c), + $this->_add($hash[3], $d), + $this->_add($hash[4], $e), + $this->_add($hash[5], $f), + $this->_add($hash[6], $g), + $this->_add($hash[7], $h) + ); + } + + // Produce the final hash value (big-endian) + return pack('N8', $hash[0], $hash[1], $hash[2], $hash[3], $hash[4], $hash[5], $hash[6], $hash[7]); + } + + /** + * Pure-PHP implementation of SHA384 and SHA512 + * + * @access private + * @param String $m + */ + function _sha512($m) + { + if (!class_exists('Math_BigInteger')) { + require_once('Math/BigInteger.php'); + } + + static $init384, $init512, $k; + + if (!isset($k)) { + // Initialize variables + $init384 = array( // initial values for SHA384 + 'cbbb9d5dc1059ed8', '629a292a367cd507', '9159015a3070dd17', '152fecd8f70e5939', + '67332667ffc00b31', '8eb44a8768581511', 'db0c2e0d64f98fa7', '47b5481dbefa4fa4' + ); + $init512 = array( // initial values for SHA512 + '6a09e667f3bcc908', 'bb67ae8584caa73b', '3c6ef372fe94f82b', 'a54ff53a5f1d36f1', + '510e527fade682d1', '9b05688c2b3e6c1f', '1f83d9abfb41bd6b', '5be0cd19137e2179' + ); + + for ($i = 0; $i < 8; $i++) { + $init384[$i] = new Math_BigInteger($init384[$i], 16); + $init384[$i]->setPrecision(64); + $init512[$i] = new Math_BigInteger($init512[$i], 16); + $init512[$i]->setPrecision(64); + } + + // Initialize table of round constants + // (first 64 bits of the fractional parts of the cube roots of the first 80 primes 2..409) + $k = array( + '428a2f98d728ae22', '7137449123ef65cd', 'b5c0fbcfec4d3b2f', 'e9b5dba58189dbbc', + '3956c25bf348b538', '59f111f1b605d019', '923f82a4af194f9b', 'ab1c5ed5da6d8118', + 'd807aa98a3030242', '12835b0145706fbe', '243185be4ee4b28c', '550c7dc3d5ffb4e2', + '72be5d74f27b896f', '80deb1fe3b1696b1', '9bdc06a725c71235', 'c19bf174cf692694', + 'e49b69c19ef14ad2', 'efbe4786384f25e3', '0fc19dc68b8cd5b5', '240ca1cc77ac9c65', + '2de92c6f592b0275', '4a7484aa6ea6e483', '5cb0a9dcbd41fbd4', '76f988da831153b5', + '983e5152ee66dfab', 'a831c66d2db43210', 'b00327c898fb213f', 'bf597fc7beef0ee4', + 'c6e00bf33da88fc2', 'd5a79147930aa725', '06ca6351e003826f', '142929670a0e6e70', + '27b70a8546d22ffc', '2e1b21385c26c926', '4d2c6dfc5ac42aed', '53380d139d95b3df', + '650a73548baf63de', '766a0abb3c77b2a8', '81c2c92e47edaee6', '92722c851482353b', + 'a2bfe8a14cf10364', 'a81a664bbc423001', 'c24b8b70d0f89791', 'c76c51a30654be30', + 'd192e819d6ef5218', 'd69906245565a910', 'f40e35855771202a', '106aa07032bbd1b8', + '19a4c116b8d2d0c8', '1e376c085141ab53', '2748774cdf8eeb99', '34b0bcb5e19b48a8', + '391c0cb3c5c95a63', '4ed8aa4ae3418acb', '5b9cca4f7763e373', '682e6ff3d6b2b8a3', + '748f82ee5defb2fc', '78a5636f43172f60', '84c87814a1f0ab72', '8cc702081a6439ec', + '90befffa23631e28', 'a4506cebde82bde9', 'bef9a3f7b2c67915', 'c67178f2e372532b', + 'ca273eceea26619c', 'd186b8c721c0c207', 'eada7dd6cde0eb1e', 'f57d4f7fee6ed178', + '06f067aa72176fba', '0a637dc5a2c898a6', '113f9804bef90dae', '1b710b35131c471b', + '28db77f523047d84', '32caab7b40c72493', '3c9ebe0a15c9bebc', '431d67c49c100d4c', + '4cc5d4becb3e42b6', '597f299cfc657e2a', '5fcb6fab3ad6faec', '6c44198c4a475817' + ); + + for ($i = 0; $i < 80; $i++) { + $k[$i] = new Math_BigInteger($k[$i], 16); + } + } + + $hash = $this->l == 48 ? $init384 : $init512; + + // Pre-processing + $length = strlen($m); + // to round to nearest 112 mod 128, we'll add 128 - (length + (128 - 112)) % 128 + $m.= str_repeat(chr(0), 128 - (($length + 16) & 0x7F)); + $m[$length] = chr(0x80); + // we don't support hashing strings 512MB long + $m.= pack('N4', 0, 0, 0, $length << 3); + + // Process the message in successive 1024-bit chunks + $chunks = str_split($m, 128); + foreach ($chunks as $chunk) { + $w = array(); + for ($i = 0; $i < 16; $i++) { + $temp = new Math_BigInteger($this->_string_shift($chunk, 8), 256); + $temp->setPrecision(64); + $w[] = $temp; + } + + // Extend the sixteen 32-bit words into eighty 32-bit words + for ($i = 16; $i < 80; $i++) { + $temp = array( + $w[$i - 15]->bitwise_rightRotate(1), + $w[$i - 15]->bitwise_rightRotate(8), + $w[$i - 15]->bitwise_rightShift(7) + ); + $s0 = $temp[0]->bitwise_xor($temp[1]); + $s0 = $s0->bitwise_xor($temp[2]); + $temp = array( + $w[$i - 2]->bitwise_rightRotate(19), + $w[$i - 2]->bitwise_rightRotate(61), + $w[$i - 2]->bitwise_rightShift(6) + ); + $s1 = $temp[0]->bitwise_xor($temp[1]); + $s1 = $s1->bitwise_xor($temp[2]); + $w[$i] = $w[$i - 16]->copy(); + $w[$i] = $w[$i]->add($s0); + $w[$i] = $w[$i]->add($w[$i - 7]); + $w[$i] = $w[$i]->add($s1); + } + + // Initialize hash value for this chunk + $a = $hash[0]->copy(); + $b = $hash[1]->copy(); + $c = $hash[2]->copy(); + $d = $hash[3]->copy(); + $e = $hash[4]->copy(); + $f = $hash[5]->copy(); + $g = $hash[6]->copy(); + $h = $hash[7]->copy(); + + // Main loop + for ($i = 0; $i < 80; $i++) { + $temp = array( + $a->bitwise_rightRotate(28), + $a->bitwise_rightRotate(34), + $a->bitwise_rightRotate(39) + ); + $s0 = $temp[0]->bitwise_xor($temp[1]); + $s0 = $s0->bitwise_xor($temp[2]); + $temp = array( + $a->bitwise_and($b), + $a->bitwise_and($c), + $b->bitwise_and($c) + ); + $maj = $temp[0]->bitwise_xor($temp[1]); + $maj = $maj->bitwise_xor($temp[2]); + $t2 = $s0->add($maj); + + $temp = array( + $e->bitwise_rightRotate(14), + $e->bitwise_rightRotate(18), + $e->bitwise_rightRotate(41) + ); + $s1 = $temp[0]->bitwise_xor($temp[1]); + $s1 = $s1->bitwise_xor($temp[2]); + $temp = array( + $e->bitwise_and($f), + $g->bitwise_and($e->bitwise_not()) + ); + $ch = $temp[0]->bitwise_xor($temp[1]); + $t1 = $h->add($s1); + $t1 = $t1->add($ch); + $t1 = $t1->add($k[$i]); + $t1 = $t1->add($w[$i]); + + $h = $g->copy(); + $g = $f->copy(); + $f = $e->copy(); + $e = $d->add($t1); + $d = $c->copy(); + $c = $b->copy(); + $b = $a->copy(); + $a = $t1->add($t2); + } + + // Add this chunk's hash to result so far + $hash = array( + $hash[0]->add($a), + $hash[1]->add($b), + $hash[2]->add($c), + $hash[3]->add($d), + $hash[4]->add($e), + $hash[5]->add($f), + $hash[6]->add($g), + $hash[7]->add($h) + ); + } + + // Produce the final hash value (big-endian) + // (Crypt_Hash::hash() trims the output for hashes but not for HMACs. as such, we trim the output here) + $temp = $hash[0]->toBytes() . $hash[1]->toBytes() . $hash[2]->toBytes() . $hash[3]->toBytes() . + $hash[4]->toBytes() . $hash[5]->toBytes(); + if ($this->l != 48) { + $temp.= $hash[6]->toBytes() . $hash[7]->toBytes(); + } + + return $temp; + } + + /** + * Right Rotate + * + * @access private + * @param Integer $int + * @param Integer $amt + * @see _sha256() + * @return Integer + */ + function _rightRotate($int, $amt) + { + $invamt = 32 - $amt; + $mask = (1 << $invamt) - 1; + return (($int << $invamt) & 0xFFFFFFFF) | (($int >> $amt) & $mask); + } + + /** + * Right Shift + * + * @access private + * @param Integer $int + * @param Integer $amt + * @see _sha256() + * @return Integer + */ + function _rightShift($int, $amt) + { + $mask = (1 << (32 - $amt)) - 1; + return ($int >> $amt) & $mask; + } + + /** + * Not + * + * @access private + * @param Integer $int + * @see _sha256() + * @return Integer + */ + function _not($int) + { + return ~$int & 0xFFFFFFFF; + } + + /** + * Add + * + * _sha256() adds multiple unsigned 32-bit integers. Since PHP doesn't support unsigned integers and since the + * possibility of overflow exists, care has to be taken. Math_BigInteger() could be used but this should be faster. + * + * @param Integer $... + * @return Integer + * @see _sha256() + * @access private + */ + function _add() + { + static $mod; + if (!isset($mod)) { + $mod = pow(2, 32); + } + + $result = 0; + $arguments = func_get_args(); + foreach ($arguments as $argument) { + $result+= $argument < 0 ? ($argument & 0x7FFFFFFF) + 0x80000000 : $argument; + } + + return fmod($result, $mod); + } + + /** + * String Shift + * + * Inspired by array_shift + * + * @param String $string + * @param optional Integer $index + * @return String + * @access private + */ + function _string_shift(&$string, $index = 1) + { + $substr = substr($string, 0, $index); + $string = substr($string, $index); + return $substr; + } +} diff --git a/include/pear/Crypt/Random.php b/include/pear/Crypt/Random.php new file mode 100644 index 0000000000000000000000000000000000000000..cc89dff582c9de2463f34219253adca0882c34b5 --- /dev/null +++ b/include/pear/Crypt/Random.php @@ -0,0 +1,249 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * Random Number Generator + * + * PHP versions 4 and 5 + * + * Here's a short example of how to use this library: + * <code> + * <?php + * include('Crypt/Random.php'); + * + * echo bin2hex(crypt_random_string(8)); + * ?> + * </code> + * + * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @category Crypt + * @package Crypt_Random + * @author Jim Wigginton <terrafrost@php.net> + * @copyright MMVII Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +/** + * "Is Windows" test + * + * @access private + */ +define('CRYPT_RANDOM_IS_WINDOWS', strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'); + +/** + * Generate a random string. + * + * Although microoptimizations are generally discouraged as they impair readability this function is ripe with + * microoptimizations because this function has the potential of being called a huge number of times. + * eg. for RSA key generation. + * + * @param Integer $length + * @return String + * @access public + */ +function crypt_random_string($length) +{ + if (CRYPT_RANDOM_IS_WINDOWS) { + // method 1. prior to PHP 5.3 this would call rand() on windows hence the function_exists('class_alias') call. + // ie. class_alias is a function that was introduced in PHP 5.3 + if (function_exists('mcrypt_create_iv') && function_exists('class_alias')) { + return mcrypt_create_iv($length); + } + // method 2. openssl_random_pseudo_bytes was introduced in PHP 5.3.0 but prior to PHP 5.3.4 there was, + // to quote <http://php.net/ChangeLog-5.php#5.3.4>, "possible blocking behavior". as of 5.3.4 + // openssl_random_pseudo_bytes and mcrypt_create_iv do the exact same thing on Windows. ie. they both + // call php_win32_get_random_bytes(): + // + // https://github.com/php/php-src/blob/7014a0eb6d1611151a286c0ff4f2238f92c120d6/ext/openssl/openssl.c#L5008 + // https://github.com/php/php-src/blob/7014a0eb6d1611151a286c0ff4f2238f92c120d6/ext/mcrypt/mcrypt.c#L1392 + // + // php_win32_get_random_bytes() is defined thusly: + // + // https://github.com/php/php-src/blob/7014a0eb6d1611151a286c0ff4f2238f92c120d6/win32/winutil.c#L80 + // + // we're calling it, all the same, in the off chance that the mcrypt extension is not available + if (function_exists('openssl_random_pseudo_bytes') && version_compare(PHP_VERSION, '5.3.4', '>=')) { + return openssl_random_pseudo_bytes($length); + } + } else { + // method 1. the fastest + if (function_exists('openssl_random_pseudo_bytes')) { + return openssl_random_pseudo_bytes($length); + } + // method 2 + static $fp = true; + if ($fp === true) { + // warning's will be output unles the error suppression operator is used. errors such as + // "open_basedir restriction in effect", "Permission denied", "No such file or directory", etc. + $fp = @fopen('/dev/urandom', 'rb'); + } + if ($fp !== true && $fp !== false) { // surprisingly faster than !is_bool() or is_resource() + return fread($fp, $length); + } + // method 3. pretty much does the same thing as method 2 per the following url: + // https://github.com/php/php-src/blob/7014a0eb6d1611151a286c0ff4f2238f92c120d6/ext/mcrypt/mcrypt.c#L1391 + // surprisingly slower than method 2. maybe that's because mcrypt_create_iv does a bunch of error checking that we're + // not doing. regardless, this'll only be called if this PHP script couldn't open /dev/urandom due to open_basedir + // restrictions or some such + if (function_exists('mcrypt_create_iv')) { + return mcrypt_create_iv($length, MCRYPT_DEV_URANDOM); + } + } + // at this point we have no choice but to use a pure-PHP CSPRNG + + // cascade entropy across multiple PHP instances by fixing the session and collecting all + // environmental variables, including the previous session data and the current session + // data. + // + // mt_rand seeds itself by looking at the PID and the time, both of which are (relatively) + // easy to guess at. linux uses mouse clicks, keyboard timings, etc, as entropy sources, but + // PHP isn't low level to be able to use those as sources and on a web server there's not likely + // going to be a ton of keyboard or mouse action. web servers do have one thing that we can use + // however. a ton of people visiting the website. obviously you don't want to base your seeding + // soley on parameters a potential attacker sends but (1) not everything in $_SERVER is controlled + // by the user and (2) this isn't just looking at the data sent by the current user - it's based + // on the data sent by all users. one user requests the page and a hash of their info is saved. + // another user visits the page and the serialization of their data is utilized along with the + // server envirnment stuff and a hash of the previous http request data (which itself utilizes + // a hash of the session data before that). certainly an attacker should be assumed to have + // full control over his own http requests. he, however, is not going to have control over + // everyone's http requests. + static $crypto = false, $v; + if ($crypto === false) { + // save old session data + $old_session_id = session_id(); + $old_use_cookies = ini_get('session.use_cookies'); + $old_session_cache_limiter = session_cache_limiter(); + if (isset($_SESSION)) { + $_OLD_SESSION = $_SESSION; + } + if ($old_session_id != '') { + session_write_close(); + } + + session_id(1); + ini_set('session.use_cookies', 0); + session_cache_limiter(''); + session_start(); + + $v = $seed = $_SESSION['seed'] = pack('H*', sha1( + serialize($_SERVER) . + serialize($_POST) . + serialize($_GET) . + serialize($_COOKIE) . + serialize($GLOBALS) . + serialize($_SESSION) . + serialize($_OLD_SESSION) + )); + if (!isset($_SESSION['count'])) { + $_SESSION['count'] = 0; + } + $_SESSION['count']++; + + session_write_close(); + + // restore old session data + if ($old_session_id != '') { + session_id($old_session_id); + session_start(); + ini_set('session.use_cookies', $old_use_cookies); + session_cache_limiter($old_session_cache_limiter); + } else { + if (isset($_OLD_SESSION)) { + $_SESSION = $_OLD_SESSION; + unset($_OLD_SESSION); + } else { + unset($_SESSION); + } + } + + // in SSH2 a shared secret and an exchange hash are generated through the key exchange process. + // the IV client to server is the hash of that "nonce" with the letter A and for the encryption key it's the letter C. + // if the hash doesn't produce enough a key or an IV that's long enough concat successive hashes of the + // original hash and the current hash. we'll be emulating that. for more info see the following URL: + // + // http://tools.ietf.org/html/rfc4253#section-7.2 + // + // see the is_string($crypto) part for an example of how to expand the keys + $key = pack('H*', sha1($seed . 'A')); + $iv = pack('H*', sha1($seed . 'C')); + + // ciphers are used as per the nist.gov link below. also, see this link: + // + // http://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator#Designs_based_on_cryptographic_primitives + switch (true) { + case class_exists('Crypt_AES'): + $crypto = new Crypt_AES(CRYPT_AES_MODE_CTR); + break; + case class_exists('Crypt_TripleDES'): + $crypto = new Crypt_TripleDES(CRYPT_DES_MODE_CTR); + break; + case class_exists('Crypt_DES'): + $crypto = new Crypt_DES(CRYPT_DES_MODE_CTR); + break; + case class_exists('Crypt_RC4'): + $crypto = new Crypt_RC4(); + break; + default: + $crypto = $seed; + return crypt_random_string($length); + } + + $crypto->setKey($key); + $crypto->setIV($iv); + $crypto->enableContinuousBuffer(); + } + + if (is_string($crypto)) { + // the following is based off of ANSI X9.31: + // + // http://csrc.nist.gov/groups/STM/cavp/documents/rng/931rngext.pdf + // + // OpenSSL uses that same standard for it's random numbers: + // + // http://www.opensource.apple.com/source/OpenSSL/OpenSSL-38/openssl/fips-1.0/rand/fips_rand.c + // (do a search for "ANS X9.31 A.2.4") + // + // ANSI X9.31 recommends ciphers be used and phpseclib does use them if they're available (see + // later on in the code) but if they're not we'll use sha1 + $result = ''; + while (strlen($result) < $length) { // each loop adds 20 bytes + // microtime() isn't packed as "densely" as it could be but then neither is that the idea. + // the idea is simply to ensure that each "block" has a unique element to it. + $i = pack('H*', sha1(microtime())); + $r = pack('H*', sha1($i ^ $v)); + $v = pack('H*', sha1($r ^ $i)); + $result.= $r; + } + return substr($result, 0, $length); + } + + //return $crypto->encrypt(str_repeat("\0", $length)); + + $result = ''; + while (strlen($result) < $length) { + $i = $crypto->encrypt(microtime()); + $r = $crypto->encrypt($i ^ $v); + $v = $crypto->encrypt($r ^ $i); + $result.= $r; + } + return substr($result, 0, $length); +} diff --git a/include/pear/Crypt/Rijndael.php b/include/pear/Crypt/Rijndael.php new file mode 100644 index 0000000000000000000000000000000000000000..a8510007afe1abac110b552c0d4a5fa3ce02c892 --- /dev/null +++ b/include/pear/Crypt/Rijndael.php @@ -0,0 +1,2062 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * Pure-PHP implementation of Rijndael. + * + * Does not use mcrypt, even when available, for reasons that are explained below. + * + * PHP versions 4 and 5 + * + * If {@link Crypt_Rijndael::setBlockLength() setBlockLength()} isn't called, it'll be assumed to be 128 bits. If + * {@link Crypt_Rijndael::setKeyLength() setKeyLength()} isn't called, it'll be calculated from + * {@link Crypt_Rijndael::setKey() setKey()}. ie. if the key is 128-bits, the key length will be 128-bits. If it's + * 136-bits it'll be null-padded to 160-bits and 160 bits will be the key length until + * {@link Crypt_Rijndael::setKey() setKey()} is called, again, at which point, it'll be recalculated. + * + * Not all Rijndael implementations may support 160-bits or 224-bits as the block length / key length. mcrypt, for example, + * does not. AES, itself, only supports block lengths of 128 and key lengths of 128, 192, and 256. + * {@link http://csrc.nist.gov/archive/aes/rijndael/Rijndael-ammended.pdf#page=10 Rijndael-ammended.pdf#page=10} defines the + * algorithm for block lengths of 192 and 256 but not for block lengths / key lengths of 160 and 224. Indeed, 160 and 224 + * are first defined as valid key / block lengths in + * {@link http://csrc.nist.gov/archive/aes/rijndael/Rijndael-ammended.pdf#page=44 Rijndael-ammended.pdf#page=44}: + * Extensions: Other block and Cipher Key lengths. + * + * {@internal The variable names are the same as those in + * {@link http://www.csrc.nist.gov/publications/fips/fips197/fips-197.pdf#page=10 fips-197.pdf#page=10}.}} + * + * Here's a short example of how to use this library: + * <code> + * <?php + * include('Crypt/Rijndael.php'); + * + * $rijndael = new Crypt_Rijndael(); + * + * $rijndael->setKey('abcdefghijklmnop'); + * + * $size = 10 * 1024; + * $plaintext = ''; + * for ($i = 0; $i < $size; $i++) { + * $plaintext.= 'a'; + * } + * + * echo $rijndael->decrypt($rijndael->encrypt($plaintext)); + * ?> + * </code> + * + * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @category Crypt + * @package Crypt_Rijndael + * @author Jim Wigginton <terrafrost@php.net> + * @copyright MMVIII Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +/**#@+ + * @access public + * @see Crypt_Rijndael::encrypt() + * @see Crypt_Rijndael::decrypt() + */ +/** + * Encrypt / decrypt using the Counter mode. + * + * Set to -1 since that's what Crypt/Random.php uses to index the CTR mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Counter_.28CTR.29 + */ +define('CRYPT_RIJNDAEL_MODE_CTR', -1); +/** + * Encrypt / decrypt using the Electronic Code Book mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29 + */ +define('CRYPT_RIJNDAEL_MODE_ECB', 1); +/** + * Encrypt / decrypt using the Code Book Chaining mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29 + */ +define('CRYPT_RIJNDAEL_MODE_CBC', 2); +/** + * Encrypt / decrypt using the Cipher Feedback mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher_feedback_.28CFB.29 + */ +define('CRYPT_RIJNDAEL_MODE_CFB', 3); +/** + * Encrypt / decrypt using the Cipher Feedback mode. + * + * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Output_feedback_.28OFB.29 + */ +define('CRYPT_RIJNDAEL_MODE_OFB', 4); +/**#@-*/ + +/**#@+ + * @access private + * @see Crypt_Rijndael::Crypt_Rijndael() + */ +/** + * Toggles the internal implementation + */ +define('CRYPT_RIJNDAEL_MODE_INTERNAL', 1); +/** + * Toggles the mcrypt implementation + */ +define('CRYPT_RIJNDAEL_MODE_MCRYPT', 2); +/**#@-*/ + +/** + * Pure-PHP implementation of Rijndael. + * + * @author Jim Wigginton <terrafrost@php.net> + * @version 0.1.0 + * @access public + * @package Crypt_Rijndael + */ +class Crypt_Rijndael { + /** + * The Encryption Mode + * + * @see Crypt_Rijndael::Crypt_Rijndael() + * @var Integer + * @access private + */ + var $mode; + + /** + * The Key + * + * @see Crypt_Rijndael::setKey() + * @var String + * @access private + */ + var $key = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; + + /** + * The Initialization Vector + * + * @see Crypt_Rijndael::setIV() + * @var String + * @access private + */ + var $iv = ''; + + /** + * A "sliding" Initialization Vector + * + * @see Crypt_Rijndael::enableContinuousBuffer() + * @var String + * @access private + */ + var $encryptIV = ''; + + /** + * A "sliding" Initialization Vector + * + * @see Crypt_Rijndael::enableContinuousBuffer() + * @var String + * @access private + */ + var $decryptIV = ''; + + /** + * Continuous Buffer status + * + * @see Crypt_Rijndael::enableContinuousBuffer() + * @var Boolean + * @access private + */ + var $continuousBuffer = false; + + /** + * Padding status + * + * @see Crypt_Rijndael::enablePadding() + * @var Boolean + * @access private + */ + var $padding = true; + + /** + * Does the key schedule need to be (re)calculated? + * + * @see setKey() + * @see setBlockLength() + * @see setKeyLength() + * @var Boolean + * @access private + */ + var $changed = true; + + /** + * Has the key length explicitly been set or should it be derived from the key, itself? + * + * @see setKeyLength() + * @var Boolean + * @access private + */ + var $explicit_key_length = false; + + /** + * The Key Schedule + * + * @see _setup() + * @var Array + * @access private + */ + var $w; + + /** + * The Inverse Key Schedule + * + * @see _setup() + * @var Array + * @access private + */ + var $dw; + + /** + * The Block Length + * + * @see setBlockLength() + * @var Integer + * @access private + * @internal The max value is 32, the min value is 16. All valid values are multiples of 4. Exists in conjunction with + * $Nb because we need this value and not $Nb to pad strings appropriately. + */ + var $block_size = 16; + + /** + * The Block Length divided by 32 + * + * @see setBlockLength() + * @var Integer + * @access private + * @internal The max value is 256 / 32 = 8, the min value is 128 / 32 = 4. Exists in conjunction with $block_size + * because the encryption / decryption / key schedule creation requires this number and not $block_size. We could + * derive this from $block_size or vice versa, but that'd mean we'd have to do multiple shift operations, so in lieu + * of that, we'll just precompute it once. + * + */ + var $Nb = 4; + + /** + * The Key Length + * + * @see setKeyLength() + * @var Integer + * @access private + * @internal The max value is 256 / 8 = 32, the min value is 128 / 8 = 16. Exists in conjunction with $key_size + * because the encryption / decryption / key schedule creation requires this number and not $key_size. We could + * derive this from $key_size or vice versa, but that'd mean we'd have to do multiple shift operations, so in lieu + * of that, we'll just precompute it once. + */ + var $key_size = 16; + + /** + * The Key Length divided by 32 + * + * @see setKeyLength() + * @var Integer + * @access private + * @internal The max value is 256 / 32 = 8, the min value is 128 / 32 = 4 + */ + var $Nk = 4; + + /** + * The Number of Rounds + * + * @var Integer + * @access private + * @internal The max value is 14, the min value is 10. + */ + var $Nr; + + /** + * Shift offsets + * + * @var Array + * @access private + */ + var $c; + + /** + * Precomputed mixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $t0; + + /** + * Precomputed mixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $t1; + + /** + * Precomputed mixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $t2; + + /** + * Precomputed mixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $t3; + + /** + * Precomputed invMixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $dt0; + + /** + * Precomputed invMixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $dt1; + + /** + * Precomputed invMixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $dt2; + + /** + * Precomputed invMixColumns table + * + * @see Crypt_Rijndael() + * @var Array + * @access private + */ + var $dt3; + + /** + * The SubByte S-Box + * + * @see Crypt_Rijndael::_encryptBlock() + * @var Array + * @access private + */ + var $sbox; + + /** + * The inverse SubByte S-Box + * + * @see Crypt_Rijndael::_decryptBlock() + * @var Array + * @access private + */ + var $isbox; + + /** + * Performance-optimized callback function for en/decrypt() + * + * @see Crypt_Rijndael::encrypt() + * @see Crypt_Rijndael::decrypt() + * @see Crypt_Rijndael::inline_crypt_setup() + * @see Crypt_Rijndael::$use_inline_crypt + * @var Callback + * @access private + */ + var $inline_crypt; + + /** + * Holds whether performance-optimized $inline_crypt should be used or not. + * + * @see Crypt_Rijndael::Crypt_Rijndael() + * @see Crypt_Rijndael::inline_crypt_setup() + * @see Crypt_Rijndael::$inline_crypt + * @var Boolean + * @access private + */ + var $use_inline_crypt = true; + + /** + * Is the mode one that is paddable? + * + * @see Crypt_Rijndael::Crypt_Rijndael() + * @var Boolean + * @access private + */ + var $paddable = false; + + /** + * Encryption buffer for CTR, OFB and CFB modes + * + * @see Crypt_Rijndael::encrypt() + * @var String + * @access private + */ + var $enbuffer = array('encrypted' => '', 'xor' => '', 'pos' => 0); + + /** + * Decryption buffer for CTR, OFB and CFB modes + * + * @see Crypt_Rijndael::decrypt() + * @var String + * @access private + */ + var $debuffer = array('ciphertext' => '', 'xor' => '', 'pos' => 0); + + /** + * Default Constructor. + * + * Determines whether or not the mcrypt extension should be used. $mode should only, at present, be + * CRYPT_RIJNDAEL_MODE_ECB or CRYPT_RIJNDAEL_MODE_CBC. If not explictly set, CRYPT_RIJNDAEL_MODE_CBC will be used. + * + * @param optional Integer $mode + * @return Crypt_Rijndael + * @access public + */ + function Crypt_Rijndael($mode = CRYPT_RIJNDAEL_MODE_CBC) + { + switch ($mode) { + case CRYPT_RIJNDAEL_MODE_ECB: + case CRYPT_RIJNDAEL_MODE_CBC: + $this->paddable = true; + $this->mode = $mode; + break; + case CRYPT_RIJNDAEL_MODE_CTR: + case CRYPT_RIJNDAEL_MODE_CFB: + case CRYPT_RIJNDAEL_MODE_OFB: + $this->mode = $mode; + break; + default: + $this->paddable = true; + $this->mode = CRYPT_RIJNDAEL_MODE_CBC; + } + + $t3 = &$this->t3; + $t2 = &$this->t2; + $t1 = &$this->t1; + $t0 = &$this->t0; + + $dt3 = &$this->dt3; + $dt2 = &$this->dt2; + $dt1 = &$this->dt1; + $dt0 = &$this->dt0; + + // according to <http://csrc.nist.gov/archive/aes/rijndael/Rijndael-ammended.pdf#page=19> (section 5.2.1), + // precomputed tables can be used in the mixColumns phase. in that example, they're assigned t0...t3, so + // those are the names we'll use. + $t3 = array( + 0x6363A5C6, 0x7C7C84F8, 0x777799EE, 0x7B7B8DF6, 0xF2F20DFF, 0x6B6BBDD6, 0x6F6FB1DE, 0xC5C55491, + 0x30305060, 0x01010302, 0x6767A9CE, 0x2B2B7D56, 0xFEFE19E7, 0xD7D762B5, 0xABABE64D, 0x76769AEC, + 0xCACA458F, 0x82829D1F, 0xC9C94089, 0x7D7D87FA, 0xFAFA15EF, 0x5959EBB2, 0x4747C98E, 0xF0F00BFB, + 0xADADEC41, 0xD4D467B3, 0xA2A2FD5F, 0xAFAFEA45, 0x9C9CBF23, 0xA4A4F753, 0x727296E4, 0xC0C05B9B, + 0xB7B7C275, 0xFDFD1CE1, 0x9393AE3D, 0x26266A4C, 0x36365A6C, 0x3F3F417E, 0xF7F702F5, 0xCCCC4F83, + 0x34345C68, 0xA5A5F451, 0xE5E534D1, 0xF1F108F9, 0x717193E2, 0xD8D873AB, 0x31315362, 0x15153F2A, + 0x04040C08, 0xC7C75295, 0x23236546, 0xC3C35E9D, 0x18182830, 0x9696A137, 0x05050F0A, 0x9A9AB52F, + 0x0707090E, 0x12123624, 0x80809B1B, 0xE2E23DDF, 0xEBEB26CD, 0x2727694E, 0xB2B2CD7F, 0x75759FEA, + 0x09091B12, 0x83839E1D, 0x2C2C7458, 0x1A1A2E34, 0x1B1B2D36, 0x6E6EB2DC, 0x5A5AEEB4, 0xA0A0FB5B, + 0x5252F6A4, 0x3B3B4D76, 0xD6D661B7, 0xB3B3CE7D, 0x29297B52, 0xE3E33EDD, 0x2F2F715E, 0x84849713, + 0x5353F5A6, 0xD1D168B9, 0x00000000, 0xEDED2CC1, 0x20206040, 0xFCFC1FE3, 0xB1B1C879, 0x5B5BEDB6, + 0x6A6ABED4, 0xCBCB468D, 0xBEBED967, 0x39394B72, 0x4A4ADE94, 0x4C4CD498, 0x5858E8B0, 0xCFCF4A85, + 0xD0D06BBB, 0xEFEF2AC5, 0xAAAAE54F, 0xFBFB16ED, 0x4343C586, 0x4D4DD79A, 0x33335566, 0x85859411, + 0x4545CF8A, 0xF9F910E9, 0x02020604, 0x7F7F81FE, 0x5050F0A0, 0x3C3C4478, 0x9F9FBA25, 0xA8A8E34B, + 0x5151F3A2, 0xA3A3FE5D, 0x4040C080, 0x8F8F8A05, 0x9292AD3F, 0x9D9DBC21, 0x38384870, 0xF5F504F1, + 0xBCBCDF63, 0xB6B6C177, 0xDADA75AF, 0x21216342, 0x10103020, 0xFFFF1AE5, 0xF3F30EFD, 0xD2D26DBF, + 0xCDCD4C81, 0x0C0C1418, 0x13133526, 0xECEC2FC3, 0x5F5FE1BE, 0x9797A235, 0x4444CC88, 0x1717392E, + 0xC4C45793, 0xA7A7F255, 0x7E7E82FC, 0x3D3D477A, 0x6464ACC8, 0x5D5DE7BA, 0x19192B32, 0x737395E6, + 0x6060A0C0, 0x81819819, 0x4F4FD19E, 0xDCDC7FA3, 0x22226644, 0x2A2A7E54, 0x9090AB3B, 0x8888830B, + 0x4646CA8C, 0xEEEE29C7, 0xB8B8D36B, 0x14143C28, 0xDEDE79A7, 0x5E5EE2BC, 0x0B0B1D16, 0xDBDB76AD, + 0xE0E03BDB, 0x32325664, 0x3A3A4E74, 0x0A0A1E14, 0x4949DB92, 0x06060A0C, 0x24246C48, 0x5C5CE4B8, + 0xC2C25D9F, 0xD3D36EBD, 0xACACEF43, 0x6262A6C4, 0x9191A839, 0x9595A431, 0xE4E437D3, 0x79798BF2, + 0xE7E732D5, 0xC8C8438B, 0x3737596E, 0x6D6DB7DA, 0x8D8D8C01, 0xD5D564B1, 0x4E4ED29C, 0xA9A9E049, + 0x6C6CB4D8, 0x5656FAAC, 0xF4F407F3, 0xEAEA25CF, 0x6565AFCA, 0x7A7A8EF4, 0xAEAEE947, 0x08081810, + 0xBABAD56F, 0x787888F0, 0x25256F4A, 0x2E2E725C, 0x1C1C2438, 0xA6A6F157, 0xB4B4C773, 0xC6C65197, + 0xE8E823CB, 0xDDDD7CA1, 0x74749CE8, 0x1F1F213E, 0x4B4BDD96, 0xBDBDDC61, 0x8B8B860D, 0x8A8A850F, + 0x707090E0, 0x3E3E427C, 0xB5B5C471, 0x6666AACC, 0x4848D890, 0x03030506, 0xF6F601F7, 0x0E0E121C, + 0x6161A3C2, 0x35355F6A, 0x5757F9AE, 0xB9B9D069, 0x86869117, 0xC1C15899, 0x1D1D273A, 0x9E9EB927, + 0xE1E138D9, 0xF8F813EB, 0x9898B32B, 0x11113322, 0x6969BBD2, 0xD9D970A9, 0x8E8E8907, 0x9494A733, + 0x9B9BB62D, 0x1E1E223C, 0x87879215, 0xE9E920C9, 0xCECE4987, 0x5555FFAA, 0x28287850, 0xDFDF7AA5, + 0x8C8C8F03, 0xA1A1F859, 0x89898009, 0x0D0D171A, 0xBFBFDA65, 0xE6E631D7, 0x4242C684, 0x6868B8D0, + 0x4141C382, 0x9999B029, 0x2D2D775A, 0x0F0F111E, 0xB0B0CB7B, 0x5454FCA8, 0xBBBBD66D, 0x16163A2C + ); + + $dt3 = array( + 0xF4A75051, 0x4165537E, 0x17A4C31A, 0x275E963A, 0xAB6BCB3B, 0x9D45F11F, 0xFA58ABAC, 0xE303934B, + 0x30FA5520, 0x766DF6AD, 0xCC769188, 0x024C25F5, 0xE5D7FC4F, 0x2ACBD7C5, 0x35448026, 0x62A38FB5, + 0xB15A49DE, 0xBA1B6725, 0xEA0E9845, 0xFEC0E15D, 0x2F7502C3, 0x4CF01281, 0x4697A38D, 0xD3F9C66B, + 0x8F5FE703, 0x929C9515, 0x6D7AEBBF, 0x5259DA95, 0xBE832DD4, 0x7421D358, 0xE0692949, 0xC9C8448E, + 0xC2896A75, 0x8E7978F4, 0x583E6B99, 0xB971DD27, 0xE14FB6BE, 0x88AD17F0, 0x20AC66C9, 0xCE3AB47D, + 0xDF4A1863, 0x1A3182E5, 0x51336097, 0x537F4562, 0x6477E0B1, 0x6BAE84BB, 0x81A01CFE, 0x082B94F9, + 0x48685870, 0x45FD198F, 0xDE6C8794, 0x7BF8B752, 0x73D323AB, 0x4B02E272, 0x1F8F57E3, 0x55AB2A66, + 0xEB2807B2, 0xB5C2032F, 0xC57B9A86, 0x3708A5D3, 0x2887F230, 0xBFA5B223, 0x036ABA02, 0x16825CED, + 0xCF1C2B8A, 0x79B492A7, 0x07F2F0F3, 0x69E2A14E, 0xDAF4CD65, 0x05BED506, 0x34621FD1, 0xA6FE8AC4, + 0x2E539D34, 0xF355A0A2, 0x8AE13205, 0xF6EB75A4, 0x83EC390B, 0x60EFAA40, 0x719F065E, 0x6E1051BD, + 0x218AF93E, 0xDD063D96, 0x3E05AEDD, 0xE6BD464D, 0x548DB591, 0xC45D0571, 0x06D46F04, 0x5015FF60, + 0x98FB2419, 0xBDE997D6, 0x4043CC89, 0xD99E7767, 0xE842BDB0, 0x898B8807, 0x195B38E7, 0xC8EEDB79, + 0x7C0A47A1, 0x420FE97C, 0x841EC9F8, 0x00000000, 0x80868309, 0x2BED4832, 0x1170AC1E, 0x5A724E6C, + 0x0EFFFBFD, 0x8538560F, 0xAED51E3D, 0x2D392736, 0x0FD9640A, 0x5CA62168, 0x5B54D19B, 0x362E3A24, + 0x0A67B10C, 0x57E70F93, 0xEE96D2B4, 0x9B919E1B, 0xC0C54F80, 0xDC20A261, 0x774B695A, 0x121A161C, + 0x93BA0AE2, 0xA02AE5C0, 0x22E0433C, 0x1B171D12, 0x090D0B0E, 0x8BC7ADF2, 0xB6A8B92D, 0x1EA9C814, + 0xF1198557, 0x75074CAF, 0x99DDBBEE, 0x7F60FDA3, 0x01269FF7, 0x72F5BC5C, 0x663BC544, 0xFB7E345B, + 0x4329768B, 0x23C6DCCB, 0xEDFC68B6, 0xE4F163B8, 0x31DCCAD7, 0x63851042, 0x97224013, 0xC6112084, + 0x4A247D85, 0xBB3DF8D2, 0xF93211AE, 0x29A16DC7, 0x9E2F4B1D, 0xB230F3DC, 0x8652EC0D, 0xC1E3D077, + 0xB3166C2B, 0x70B999A9, 0x9448FA11, 0xE9642247, 0xFC8CC4A8, 0xF03F1AA0, 0x7D2CD856, 0x3390EF22, + 0x494EC787, 0x38D1C1D9, 0xCAA2FE8C, 0xD40B3698, 0xF581CFA6, 0x7ADE28A5, 0xB78E26DA, 0xADBFA43F, + 0x3A9DE42C, 0x78920D50, 0x5FCC9B6A, 0x7E466254, 0x8D13C2F6, 0xD8B8E890, 0x39F75E2E, 0xC3AFF582, + 0x5D80BE9F, 0xD0937C69, 0xD52DA96F, 0x2512B3CF, 0xAC993BC8, 0x187DA710, 0x9C636EE8, 0x3BBB7BDB, + 0x267809CD, 0x5918F46E, 0x9AB701EC, 0x4F9AA883, 0x956E65E6, 0xFFE67EAA, 0xBCCF0821, 0x15E8E6EF, + 0xE79BD9BA, 0x6F36CE4A, 0x9F09D4EA, 0xB07CD629, 0xA4B2AF31, 0x3F23312A, 0xA59430C6, 0xA266C035, + 0x4EBC3774, 0x82CAA6FC, 0x90D0B0E0, 0xA7D81533, 0x04984AF1, 0xECDAF741, 0xCD500E7F, 0x91F62F17, + 0x4DD68D76, 0xEFB04D43, 0xAA4D54CC, 0x9604DFE4, 0xD1B5E39E, 0x6A881B4C, 0x2C1FB8C1, 0x65517F46, + 0x5EEA049D, 0x8C355D01, 0x877473FA, 0x0B412EFB, 0x671D5AB3, 0xDBD25292, 0x105633E9, 0xD647136D, + 0xD7618C9A, 0xA10C7A37, 0xF8148E59, 0x133C89EB, 0xA927EECE, 0x61C935B7, 0x1CE5EDE1, 0x47B13C7A, + 0xD2DF599C, 0xF2733F55, 0x14CE7918, 0xC737BF73, 0xF7CDEA53, 0xFDAA5B5F, 0x3D6F14DF, 0x44DB8678, + 0xAFF381CA, 0x68C43EB9, 0x24342C38, 0xA3405FC2, 0x1DC37216, 0xE2250CBC, 0x3C498B28, 0x0D9541FF, + 0xA8017139, 0x0CB3DE08, 0xB4E49CD8, 0x56C19064, 0xCB84617B, 0x32B670D5, 0x6C5C7448, 0xB85742D0 + ); + + for ($i = 0; $i < 256; $i++) { + $t2[] = (($t3[$i] << 8) & 0xFFFFFF00) | (($t3[$i] >> 24) & 0x000000FF); + $t1[] = (($t3[$i] << 16) & 0xFFFF0000) | (($t3[$i] >> 16) & 0x0000FFFF); + $t0[] = (($t3[$i] << 24) & 0xFF000000) | (($t3[$i] >> 8) & 0x00FFFFFF); + + $dt2[] = (($dt3[$i] << 8) & 0xFFFFFF00) | (($dt3[$i] >> 24) & 0x000000FF); + $dt1[] = (($dt3[$i] << 16) & 0xFFFF0000) | (($dt3[$i] >> 16) & 0x0000FFFF); + $dt0[] = (($dt3[$i] << 24) & 0xFF000000) | (($dt3[$i] >> 8) & 0x00FFFFFF); + } + + // sbox for the S-Box substitution + $this->sbox = array( + 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, + 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, + 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, + 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, + 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, + 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, + 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, + 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, + 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, + 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, + 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, + 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, + 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, + 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, + 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, + 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16 + ); + + // sbox for the inverse S-Box substitution + $this->isbox = array( + 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB, + 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB, + 0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E, + 0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25, + 0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92, + 0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84, + 0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06, + 0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B, + 0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73, + 0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E, + 0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B, + 0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4, + 0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F, + 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF, + 0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D + ); + + if (!function_exists('create_function') || !is_callable('create_function')) { + $this->use_inline_crypt = false; + } + } + + /** + * Sets the key. + * + * Keys can be of any length. Rijndael, itself, requires the use of a key that's between 128-bits and 256-bits long and + * whose length is a multiple of 32. If the key is less than 256-bits and the key length isn't set, we round the length + * up to the closest valid key length, padding $key with null bytes. If the key is more than 256-bits, we trim the + * excess bits. + * + * If the key is not explicitly set, it'll be assumed to be all null bytes. + * + * @access public + * @param String $key + */ + function setKey($key) + { + $this->key = $key; + $this->changed = true; + } + + /** + * Sets the initialization vector. (optional) + * + * SetIV is not required when CRYPT_RIJNDAEL_MODE_ECB is being used. If not explictly set, it'll be assumed + * to be all zero's. + * + * @access public + * @param String $iv + */ + function setIV($iv) + { + $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($iv, 0, $this->block_size), $this->block_size, chr(0)); + } + + /** + * Sets the key length + * + * Valid key lengths are 128, 160, 192, 224, and 256. If the length is less than 128, it will be rounded up to + * 128. If the length is greater than 128 and invalid, it will be rounded down to the closest valid amount. + * + * @access public + * @param Integer $length + */ + function setKeyLength($length) + { + $length >>= 5; + if ($length > 8) { + $length = 8; + } else if ($length < 4) { + $length = 4; + } + $this->Nk = $length; + $this->key_size = $length << 2; + + $this->explicit_key_length = true; + $this->changed = true; + } + + /** + * Sets the password. + * + * Depending on what $method is set to, setPassword()'s (optional) parameters are as follows: + * {@link http://en.wikipedia.org/wiki/PBKDF2 pbkdf2}: + * $hash, $salt, $method + * Set $dkLen by calling setKeyLength() + * + * @param String $password + * @param optional String $method + * @access public + */ + function setPassword($password, $method = 'pbkdf2') + { + $key = ''; + + switch ($method) { + default: // 'pbkdf2' + list(, , $hash, $salt, $count) = func_get_args(); + if (!isset($hash)) { + $hash = 'sha1'; + } + // WPA and WPA2 use the SSID as the salt + if (!isset($salt)) { + $salt = 'phpseclib'; + } + // RFC2898#section-4.2 uses 1,000 iterations by default + // WPA and WPA2 use 4,096. + if (!isset($count)) { + $count = 1000; + } + + if (!class_exists('Crypt_Hash')) { + require_once('Crypt/Hash.php'); + } + + $i = 1; + while (strlen($key) < $this->key_size) { // $dkLen == $this->key_size + //$dk.= $this->_pbkdf($password, $salt, $count, $i++); #nolint + $hmac = new Crypt_Hash(); + $hmac->setHash($hash); + $hmac->setKey($password); + $f = $u = $hmac->hash($salt . pack('N', $i++)); + for ($j = 2; $j <= $count; $j++) { + $u = $hmac->hash($u); + $f^= $u; + } + $key.= $f; + } + } + + $this->setKey(substr($key, 0, $this->key_size)); + } + + /** + * Sets the block length + * + * Valid block lengths are 128, 160, 192, 224, and 256. If the length is less than 128, it will be rounded up to + * 128. If the length is greater than 128 and invalid, it will be rounded down to the closest valid amount. + * + * @access public + * @param Integer $length + */ + function setBlockLength($length) + { + $length >>= 5; + if ($length > 8) { + $length = 8; + } else if ($length < 4) { + $length = 4; + } + $this->Nb = $length; + $this->block_size = $length << 2; + $this->changed = true; + } + + /** + * Generate CTR XOR encryption key + * + * Encrypt the output of this and XOR it against the ciphertext / plaintext to get the + * plaintext / ciphertext in CTR mode. + * + * @see Crypt_Rijndael::decrypt() + * @see Crypt_Rijndael::encrypt() + * @access public + * @param Integer $length + * @param String $iv + */ + function _generate_xor($length, &$iv) + { + $xor = ''; + $block_size = $this->block_size; + $num_blocks = floor(($length + ($block_size - 1)) / $block_size); + for ($i = 0; $i < $num_blocks; $i++) { + $xor.= $iv; + for ($j = 4; $j <= $block_size; $j+=4) { + $temp = substr($iv, -$j, 4); + switch ($temp) { + case "\xFF\xFF\xFF\xFF": + $iv = substr_replace($iv, "\x00\x00\x00\x00", -$j, 4); + break; + case "\x7F\xFF\xFF\xFF": + $iv = substr_replace($iv, "\x80\x00\x00\x00", -$j, 4); + break 2; + default: + extract(unpack('Ncount', $temp)); + $iv = substr_replace($iv, pack('N', $count + 1), -$j, 4); + break 2; + } + } + } + + return $xor; + } + + /** + * Encrypts a message. + * + * $plaintext will be padded with additional bytes such that it's length is a multiple of the block size. Other Rjindael + * implementations may or may not pad in the same manner. Other common approaches to padding and the reasons why it's + * necessary are discussed in the following + * URL: + * + * {@link http://www.di-mgt.com.au/cryptopad.html http://www.di-mgt.com.au/cryptopad.html} + * + * An alternative to padding is to, separately, send the length of the file. This is what SSH, in fact, does. + * strlen($plaintext) will still need to be a multiple of 8, however, arbitrary values can be added to make it that + * length. + * + * @see Crypt_Rijndael::decrypt() + * @access public + * @param String $plaintext + */ + function encrypt($plaintext) + { + if ($this->changed) { + $this->_setup(); + } + if ($this->use_inline_crypt) { + $inline = $this->inline_crypt; + return $inline('encrypt', $this, $plaintext); + } + if ($this->paddable) { + $plaintext = $this->_pad($plaintext); + } + + $block_size = $this->block_size; + $buffer = &$this->enbuffer; + $ciphertext = ''; + switch ($this->mode) { + case CRYPT_RIJNDAEL_MODE_ECB: + for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { + $ciphertext.= $this->_encryptBlock(substr($plaintext, $i, $block_size)); + } + break; + case CRYPT_RIJNDAEL_MODE_CBC: + $xor = $this->encryptIV; + for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { + $block = substr($plaintext, $i, $block_size); + $block = $this->_encryptBlock($block ^ $xor); + $xor = $block; + $ciphertext.= $block; + } + if ($this->continuousBuffer) { + $this->encryptIV = $xor; + } + break; + case CRYPT_RIJNDAEL_MODE_CTR: + $xor = $this->encryptIV; + if (strlen($buffer['encrypted'])) { + for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { + $block = substr($plaintext, $i, $block_size); + if (strlen($block) > strlen($buffer['encrypted'])) { + $buffer['encrypted'].= $this->_encryptBlock($this->_generate_xor($block_size, $xor)); + } + $key = $this->_string_shift($buffer['encrypted'], $block_size); + $ciphertext.= $block ^ $key; + } + } else { + for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { + $block = substr($plaintext, $i, $block_size); + $key = $this->_encryptBlock($this->_generate_xor($block_size, $xor)); + $ciphertext.= $block ^ $key; + } + } + if ($this->continuousBuffer) { + $this->encryptIV = $xor; + if ($start = strlen($plaintext) % $block_size) { + $buffer['encrypted'] = substr($key, $start) . $buffer['encrypted']; + } + } + break; + case CRYPT_RIJNDAEL_MODE_CFB: + // cfb loosely routines inspired by openssl's: + // http://cvs.openssl.org/fileview?f=openssl/crypto/modes/cfb128.c&v=1.3.2.2.2.1 + if ($this->continuousBuffer) { + $iv = &$this->encryptIV; + $pos = &$buffer['pos']; + } else { + $iv = $this->encryptIV; + $pos = 0; + } + $len = strlen($plaintext); + $i = 0; + if ($pos) { + $orig_pos = $pos; + $max = $block_size - $pos; + if ($len >= $max) { + $i = $max; + $len-= $max; + $pos = 0; + } else { + $i = $len; + $pos+= $len; + $len = 0; + } + // ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $blocksize + $ciphertext = substr($iv, $orig_pos) ^ $plaintext; + $iv = substr_replace($iv, $ciphertext, $orig_pos, $i); + } + while ($len >= $block_size) { + $iv = $this->_encryptBlock($iv) ^ substr($plaintext, $i, $block_size); + $ciphertext.= $iv; + $len-= $block_size; + $i+= $block_size; + } + if ($len) { + $iv = $this->_encryptBlock($iv); + $block = $iv ^ substr($plaintext, $i); + $iv = substr_replace($iv, $block, 0, $len); + $ciphertext.= $block; + $pos = $len; + } + break; + case CRYPT_RIJNDAEL_MODE_OFB: + $xor = $this->encryptIV; + if (strlen($buffer['xor'])) { + for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { + $block = substr($plaintext, $i, $block_size); + if (strlen($block) > strlen($buffer['xor'])) { + $xor = $this->_encryptBlock($xor); + $buffer['xor'].= $xor; + } + $key = $this->_string_shift($buffer['xor'], $block_size); + $ciphertext.= $block ^ $key; + } + } else { + for ($i = 0; $i < strlen($plaintext); $i+=$block_size) { + $xor = $this->_encryptBlock($xor); + $ciphertext.= substr($plaintext, $i, $block_size) ^ $xor; + } + $key = $xor; + } + if ($this->continuousBuffer) { + $this->encryptIV = $xor; + if ($start = strlen($plaintext) % $block_size) { + $buffer['xor'] = substr($key, $start) . $buffer['xor']; + } + } + } + + return $ciphertext; + } + + /** + * Decrypts a message. + * + * If strlen($ciphertext) is not a multiple of the block size, null bytes will be added to the end of the string until + * it is. + * + * @see Crypt_Rijndael::encrypt() + * @access public + * @param String $ciphertext + */ + function decrypt($ciphertext) + { + if ($this->changed) { + $this->_setup(); + } + if ($this->use_inline_crypt) { + $inline = $this->inline_crypt; + return $inline('decrypt', $this, $ciphertext); + } + if ($this->paddable) { + // we pad with chr(0) since that's what mcrypt_generic does. to quote from http://php.net/function.mcrypt-generic : + // "The data is padded with "\0" to make sure the length of the data is n * blocksize." + $ciphertext = str_pad($ciphertext, strlen($ciphertext) + ($this->block_size - strlen($ciphertext) % $this->block_size) % $this->block_size, chr(0)); + } + + $block_size = $this->block_size; + $buffer = &$this->debuffer; + $plaintext = ''; + switch ($this->mode) { + case CRYPT_RIJNDAEL_MODE_ECB: + for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { + $plaintext.= $this->_decryptBlock(substr($ciphertext, $i, $block_size)); + } + break; + case CRYPT_RIJNDAEL_MODE_CBC: + $xor = $this->decryptIV; + for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { + $block = substr($ciphertext, $i, $block_size); + $plaintext.= $this->_decryptBlock($block) ^ $xor; + $xor = $block; + } + if ($this->continuousBuffer) { + $this->decryptIV = $xor; + } + break; + case CRYPT_RIJNDAEL_MODE_CTR: + $xor = $this->decryptIV; + if (strlen($buffer['ciphertext'])) { + for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { + $block = substr($ciphertext, $i, $block_size); + if (strlen($block) > strlen($buffer['ciphertext'])) { + $buffer['ciphertext'].= $this->_encryptBlock($this->_generate_xor($block_size, $xor)); + } + $key = $this->_string_shift($buffer['ciphertext'], $block_size); + $plaintext.= $block ^ $key; + } + } else { + for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { + $block = substr($ciphertext, $i, $block_size); + $key = $this->_encryptBlock($this->_generate_xor($block_size, $xor)); + $plaintext.= $block ^ $key; + } + } + if ($this->continuousBuffer) { + $this->decryptIV = $xor; + if ($start = strlen($ciphertext) % $block_size) { + $buffer['ciphertext'] = substr($key, $start) . $buffer['ciphertext']; + } + } + break; + case CRYPT_RIJNDAEL_MODE_CFB: + if ($this->continuousBuffer) { + $iv = &$this->decryptIV; + $pos = &$buffer['pos']; + } else { + $iv = $this->decryptIV; + $pos = 0; + } + $len = strlen($ciphertext); + $i = 0; + if ($pos) { + $orig_pos = $pos; + $max = $block_size - $pos; + if ($len >= $max) { + $i = $max; + $len-= $max; + $pos = 0; + } else { + $i = $len; + $pos+= $len; + $len = 0; + } + // ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $blocksize + $plaintext = substr($iv, $orig_pos) ^ $ciphertext; + $iv = substr_replace($iv, substr($ciphertext, 0, $i), $orig_pos, $i); + } + while ($len >= $block_size) { + $iv = $this->_encryptBlock($iv); + $cb = substr($ciphertext, $i, $block_size); + $plaintext.= $iv ^ $cb; + $iv = $cb; + $len-= $block_size; + $i+= $block_size; + } + if ($len) { + $iv = $this->_encryptBlock($iv); + $plaintext.= $iv ^ substr($ciphertext, $i); + $iv = substr_replace($iv, substr($ciphertext, $i), 0, $len); + $pos = $len; + } + break; + case CRYPT_RIJNDAEL_MODE_OFB: + $xor = $this->decryptIV; + if (strlen($buffer['xor'])) { + for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { + $block = substr($ciphertext, $i, $block_size); + if (strlen($block) > strlen($buffer['xor'])) { + $xor = $this->_encryptBlock($xor); + $buffer['xor'].= $xor; + } + $key = $this->_string_shift($buffer['xor'], $block_size); + $plaintext.= $block ^ $key; + } + } else { + for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) { + $xor = $this->_encryptBlock($xor); + $plaintext.= substr($ciphertext, $i, $block_size) ^ $xor; + } + $key = $xor; + } + if ($this->continuousBuffer) { + $this->decryptIV = $xor; + if ($start = strlen($ciphertext) % $block_size) { + $buffer['xor'] = substr($key, $start) . $buffer['xor']; + } + } + } + + return $this->paddable ? $this->_unpad($plaintext) : $plaintext; + } + + /** + * Encrypts a block + * + * @access private + * @param String $in + * @return String + */ + function _encryptBlock($in) + { + $state = array(); + $words = unpack('N*word', $in); + + $w = $this->w; + $t0 = $this->t0; + $t1 = $this->t1; + $t2 = $this->t2; + $t3 = $this->t3; + $Nb = $this->Nb; + $Nr = $this->Nr; + $c = $this->c; + + // addRoundKey + $i = -1; + foreach ($words as $word) { + $state[] = $word ^ $w[0][++$i]; + } + + // fips-197.pdf#page=19, "Figure 5. Pseudo Code for the Cipher", states that this loop has four components - + // subBytes, shiftRows, mixColumns, and addRoundKey. fips-197.pdf#page=30, "Implementation Suggestions Regarding + // Various Platforms" suggests that performs enhanced implementations are described in Rijndael-ammended.pdf. + // Rijndael-ammended.pdf#page=20, "Implementation aspects / 32-bit processor", discusses such an optimization. + // Unfortunately, the description given there is not quite correct. Per aes.spec.v316.pdf#page=19 [1], + // equation (7.4.7) is supposed to use addition instead of subtraction, so we'll do that here, as well. + + // [1] http://fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.v316.pdf + $temp = array(); + for ($round = 1; $round < $Nr; ++$round) { + $i = 0; // $c[0] == 0 + $j = $c[1]; + $k = $c[2]; + $l = $c[3]; + + while ($i < $Nb) { + $temp[$i] = $t0[$state[$i] >> 24 & 0x000000FF] ^ + $t1[$state[$j] >> 16 & 0x000000FF] ^ + $t2[$state[$k] >> 8 & 0x000000FF] ^ + $t3[$state[$l] & 0x000000FF] ^ + $w[$round][$i]; + ++$i; + $j = ($j + 1) % $Nb; + $k = ($k + 1) % $Nb; + $l = ($l + 1) % $Nb; + } + $state = $temp; + } + + // subWord + for ($i = 0; $i < $Nb; ++$i) { + $state[$i] = $this->_subWord($state[$i]); + } + + // shiftRows + addRoundKey + $i = 0; // $c[0] == 0 + $j = $c[1]; + $k = $c[2]; + $l = $c[3]; + while ($i < $Nb) { + $temp[$i] = ($state[$i] & 0xFF000000) ^ + ($state[$j] & 0x00FF0000) ^ + ($state[$k] & 0x0000FF00) ^ + ($state[$l] & 0x000000FF) ^ + $w[$Nr][$i]; + ++$i; + $j = ($j + 1) % $Nb; + $k = ($k + 1) % $Nb; + $l = ($l + 1) % $Nb; + } + + // 100% ugly switch/case code... but ~5% faster ("smart code" below commented out) + switch ($Nb) { + case 8: + return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4], $temp[5], $temp[6], $temp[7]); + case 7: + return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4], $temp[5], $temp[6]); + case 6: + return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4], $temp[5]); + case 5: + return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4]); + default: + return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3]); + } + /* + $state = $temp; + + array_unshift($state, 'N*'); + + return call_user_func_array('pack', $state); + */ + } + + /** + * Decrypts a block + * + * @access private + * @param String $in + * @return String + */ + function _decryptBlock($in) + { + $state = array(); + $words = unpack('N*word', $in); + + $dw = $this->dw; + $dt0 = $this->dt0; + $dt1 = $this->dt1; + $dt2 = $this->dt2; + $dt3 = $this->dt3; + $Nb = $this->Nb; + $Nr = $this->Nr; + $c = $this->c; + + // addRoundKey + $i = -1; + foreach ($words as $word) { + $state[] = $word ^ $dw[$Nr][++$i]; + } + + $temp = array(); + for ($round = $Nr - 1; $round > 0; --$round) { + $i = 0; // $c[0] == 0 + $j = $Nb - $c[1]; + $k = $Nb - $c[2]; + $l = $Nb - $c[3]; + + while ($i < $Nb) { + $temp[$i] = $dt0[$state[$i] >> 24 & 0x000000FF] ^ + $dt1[$state[$j] >> 16 & 0x000000FF] ^ + $dt2[$state[$k] >> 8 & 0x000000FF] ^ + $dt3[$state[$l] & 0x000000FF] ^ + $dw[$round][$i]; + ++$i; + $j = ($j + 1) % $Nb; + $k = ($k + 1) % $Nb; + $l = ($l + 1) % $Nb; + } + $state = $temp; + } + + // invShiftRows + invSubWord + addRoundKey + $i = 0; // $c[0] == 0 + $j = $Nb - $c[1]; + $k = $Nb - $c[2]; + $l = $Nb - $c[3]; + + while ($i < $Nb) { + $temp[$i] = $dw[0][$i] ^ + $this->_invSubWord(($state[$i] & 0xFF000000) | + ($state[$j] & 0x00FF0000) | + ($state[$k] & 0x0000FF00) | + ($state[$l] & 0x000000FF)); + ++$i; + $j = ($j + 1) % $Nb; + $k = ($k + 1) % $Nb; + $l = ($l + 1) % $Nb; + } + + switch ($Nb) { + case 8: + return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4], $temp[5], $temp[6], $temp[7]); + case 7: + return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4], $temp[5], $temp[6]); + case 6: + return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4], $temp[5]); + case 5: + return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4]); + default: + return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3]); + } + /* + $state = $temp; + + array_unshift($state, 'N*'); + + return call_user_func_array('pack', $state); + */ + } + + /** + * Setup Rijndael + * + * Validates all the variables and calculates $Nr - the number of rounds that need to be performed - and $w - the key + * key schedule. + * + * @access private + */ + function _setup() + { + // Each number in $rcon is equal to the previous number multiplied by two in Rijndael's finite field. + // See http://en.wikipedia.org/wiki/Finite_field_arithmetic#Multiplicative_inverse + static $rcon = array(0, + 0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, + 0x20000000, 0x40000000, 0x80000000, 0x1B000000, 0x36000000, + 0x6C000000, 0xD8000000, 0xAB000000, 0x4D000000, 0x9A000000, + 0x2F000000, 0x5E000000, 0xBC000000, 0x63000000, 0xC6000000, + 0x97000000, 0x35000000, 0x6A000000, 0xD4000000, 0xB3000000, + 0x7D000000, 0xFA000000, 0xEF000000, 0xC5000000, 0x91000000 + ); + + if (!$this->explicit_key_length) { + // we do >> 2, here, and not >> 5, as we do above, since strlen($this->key) tells us the number of bytes - not bits + $length = strlen($this->key) >> 2; + if ($length > 8) { + $length = 8; + } else if ($length < 4) { + $length = 4; + } + $this->Nk = $length; + $this->key_size = $length << 2; + } + + $this->key = str_pad(substr($this->key, 0, $this->key_size), $this->key_size, chr(0)); + $this->encryptIV = $this->decryptIV = $this->iv = str_pad(substr($this->iv, 0, $this->block_size), $this->block_size, chr(0)); + + // see Rijndael-ammended.pdf#page=44 + $this->Nr = max($this->Nk, $this->Nb) + 6; + + // shift offsets for Nb = 5, 7 are defined in Rijndael-ammended.pdf#page=44, + // "Table 8: Shift offsets in Shiftrow for the alternative block lengths" + // shift offsets for Nb = 4, 6, 8 are defined in Rijndael-ammended.pdf#page=14, + // "Table 2: Shift offsets for different block lengths" + switch ($this->Nb) { + case 4: + case 5: + case 6: + $this->c = array(0, 1, 2, 3); + break; + case 7: + $this->c = array(0, 1, 2, 4); + break; + case 8: + $this->c = array(0, 1, 3, 4); + } + + $key = $this->key; + + $w = array_values(unpack('N*words', $key)); + + $length = $this->Nb * ($this->Nr + 1); + for ($i = $this->Nk; $i < $length; $i++) { + $temp = $w[$i - 1]; + if ($i % $this->Nk == 0) { + // according to <http://php.net/language.types.integer>, "the size of an integer is platform-dependent". + // on a 32-bit machine, it's 32-bits, and on a 64-bit machine, it's 64-bits. on a 32-bit machine, + // 0xFFFFFFFF << 8 == 0xFFFFFF00, but on a 64-bit machine, it equals 0xFFFFFFFF00. as such, doing 'and' + // with 0xFFFFFFFF (or 0xFFFFFF00) on a 32-bit machine is unnecessary, but on a 64-bit machine, it is. + $temp = (($temp << 8) & 0xFFFFFF00) | (($temp >> 24) & 0x000000FF); // rotWord + $temp = $this->_subWord($temp) ^ $rcon[$i / $this->Nk]; + } else if ($this->Nk > 6 && $i % $this->Nk == 4) { + $temp = $this->_subWord($temp); + } + $w[$i] = $w[$i - $this->Nk] ^ $temp; + } + + // convert the key schedule from a vector of $Nb * ($Nr + 1) length to a matrix with $Nr + 1 rows and $Nb columns + // and generate the inverse key schedule. more specifically, + // according to <http://csrc.nist.gov/archive/aes/rijndael/Rijndael-ammended.pdf#page=23> (section 5.3.3), + // "The key expansion for the Inverse Cipher is defined as follows: + // 1. Apply the Key Expansion. + // 2. Apply InvMixColumn to all Round Keys except the first and the last one." + // also, see fips-197.pdf#page=27, "5.3.5 Equivalent Inverse Cipher" + $temp = $this->w = $this->dw = array(); + for ($i = $row = $col = 0; $i < $length; $i++, $col++) { + if ($col == $this->Nb) { + if ($row == 0) { + $this->dw[0] = $this->w[0]; + } else { + // subWord + invMixColumn + invSubWord = invMixColumn + $j = 0; + while ($j < $this->Nb) { + $dw = $this->_subWord($this->w[$row][$j]); + $temp[$j] = $this->dt0[$dw >> 24 & 0x000000FF] ^ + $this->dt1[$dw >> 16 & 0x000000FF] ^ + $this->dt2[$dw >> 8 & 0x000000FF] ^ + $this->dt3[$dw & 0x000000FF]; + $j++; + } + $this->dw[$row] = $temp; + } + + $col = 0; + $row++; + } + $this->w[$row][$col] = $w[$i]; + } + + $this->dw[$row] = $this->w[$row]; + + // In case of $this->use_inline_crypt === true we have to use 1-dim key arrays (both ascending) + if ($this->use_inline_crypt) { + $this->dw = array_reverse($this->dw); + $w = array_pop($this->w); + $dw = array_pop($this->dw); + foreach ($this->w as $r => $wr) { + foreach ($wr as $c => $wc) { + $w[] = $wc; + $dw[] = $this->dw[$r][$c]; + } + } + $this->w = $w; + $this->dw = $dw; + + $this->inline_crypt_setup(); + } + + $this->changed = false; + } + + /** + * Performs S-Box substitutions + * + * @access private + */ + function _subWord($word) + { + $sbox = $this->sbox; + + return $sbox[$word & 0x000000FF] | + ($sbox[$word >> 8 & 0x000000FF] << 8) | + ($sbox[$word >> 16 & 0x000000FF] << 16) | + ($sbox[$word >> 24 & 0x000000FF] << 24); + } + + /** + * Performs inverse S-Box substitutions + * + * @access private + */ + function _invSubWord($word) + { + $isbox = $this->isbox; + + return $isbox[$word & 0x000000FF] | + ($isbox[$word >> 8 & 0x000000FF] << 8) | + ($isbox[$word >> 16 & 0x000000FF] << 16) | + ($isbox[$word >> 24 & 0x000000FF] << 24); + } + + /** + * Pad "packets". + * + * Rijndael works by encrypting between sixteen and thirty-two bytes at a time, provided that number is also a multiple + * of four. If you ever need to encrypt or decrypt something that isn't of the proper length, it becomes necessary to + * pad the input so that it is of the proper length. + * + * Padding is enabled by default. Sometimes, however, it is undesirable to pad strings. Such is the case in SSH, + * where "packets" are padded with random bytes before being encrypted. Unpad these packets and you risk stripping + * away characters that shouldn't be stripped away. (SSH knows how many bytes are added because the length is + * transmitted separately) + * + * @see Crypt_Rijndael::disablePadding() + * @access public + */ + function enablePadding() + { + $this->padding = true; + } + + /** + * Do not pad packets. + * + * @see Crypt_Rijndael::enablePadding() + * @access public + */ + function disablePadding() + { + $this->padding = false; + } + + /** + * Pads a string + * + * Pads a string using the RSA PKCS padding standards so that its length is a multiple of the blocksize. + * $block_size - (strlen($text) % $block_size) bytes are added, each of which is equal to + * chr($block_size - (strlen($text) % $block_size) + * + * If padding is disabled and $text is not a multiple of the blocksize, the string will be padded regardless + * and padding will, hence forth, be enabled. + * + * @see Crypt_Rijndael::_unpad() + * @access private + */ + function _pad($text) + { + $length = strlen($text); + + if (!$this->padding) { + if ($length % $this->block_size == 0) { + return $text; + } else { + user_error("The plaintext's length ($length) is not a multiple of the block size ({$this->block_size})"); + $this->padding = true; + } + } + + $pad = $this->block_size - ($length % $this->block_size); + + return str_pad($text, $length + $pad, chr($pad)); + } + + /** + * Unpads a string. + * + * If padding is enabled and the reported padding length is invalid the encryption key will be assumed to be wrong + * and false will be returned. + * + * @see Crypt_Rijndael::_pad() + * @access private + */ + function _unpad($text) + { + if (!$this->padding) { + return $text; + } + + $length = ord($text[strlen($text) - 1]); + + if (!$length || $length > $this->block_size) { + return false; + } + + return substr($text, 0, -$length); + } + + /** + * Treat consecutive "packets" as if they are a continuous buffer. + * + * Say you have a 32-byte plaintext $plaintext. Using the default behavior, the two following code snippets + * will yield different outputs: + * + * <code> + * echo $rijndael->encrypt(substr($plaintext, 0, 16)); + * echo $rijndael->encrypt(substr($plaintext, 16, 16)); + * </code> + * <code> + * echo $rijndael->encrypt($plaintext); + * </code> + * + * The solution is to enable the continuous buffer. Although this will resolve the above discrepancy, it creates + * another, as demonstrated with the following: + * + * <code> + * $rijndael->encrypt(substr($plaintext, 0, 16)); + * echo $rijndael->decrypt($des->encrypt(substr($plaintext, 16, 16))); + * </code> + * <code> + * echo $rijndael->decrypt($des->encrypt(substr($plaintext, 16, 16))); + * </code> + * + * With the continuous buffer disabled, these would yield the same output. With it enabled, they yield different + * outputs. The reason is due to the fact that the initialization vector's change after every encryption / + * decryption round when the continuous buffer is enabled. When it's disabled, they remain constant. + * + * Put another way, when the continuous buffer is enabled, the state of the Crypt_Rijndael() object changes after each + * encryption / decryption round, whereas otherwise, it'd remain constant. For this reason, it's recommended that + * continuous buffers not be used. They do offer better security and are, in fact, sometimes required (SSH uses them), + * however, they are also less intuitive and more likely to cause you problems. + * + * @see Crypt_Rijndael::disableContinuousBuffer() + * @access public + */ + function enableContinuousBuffer() + { + $this->continuousBuffer = true; + } + + /** + * Treat consecutive packets as if they are a discontinuous buffer. + * + * The default behavior. + * + * @see Crypt_Rijndael::enableContinuousBuffer() + * @access public + */ + function disableContinuousBuffer() + { + $this->continuousBuffer = false; + $this->encryptIV = $this->iv; + $this->decryptIV = $this->iv; + $this->enbuffer = array('encrypted' => '', 'xor' => '', 'pos' => 0); + $this->debuffer = array('ciphertext' => '', 'xor' => '', 'pos' => 0); + } + + /** + * String Shift + * + * Inspired by array_shift + * + * @param String $string + * @param optional Integer $index + * @return String + * @access private + */ + function _string_shift(&$string, $index = 1) + { + $substr = substr($string, 0, $index); + $string = substr($string, $index); + return $substr; + } + + /** + * Creates performance-optimized function for de/encrypt(), storing it in $this->inline_crypt + * + * @see Crypt_Rijndael::encrypt() + * @see Crypt_Rijndael::decrypt() + * @access private + */ + function inline_crypt_setup() + { + // Note: inline_crypt_setup() will be called only if $this->changed === true + // So here we are'nt under the same heavy timing-stress as we are in _de/encryptBlock() or de/encrypt(). + // However...the here generated function- $code, stored as php callback in $this->inline_crypt, must work as fast as even possible. + + $lambda_functions =& Crypt_Rijndael::get_lambda_functions(); + $block_size = $this->block_size; + $mode = $this->mode; + + // The first 5 generated $lambda_functions will use the key-words hardcoded for better performance. + // For memory reason we limit those ultra-optimized function code to 5. + // After that, we use pure (extracted) integer vars for the key-words which is faster than accessing them via array. + if (count($lambda_functions) < 5) { + $w = $this->w; + $dw = $this->dw; + $init_encryptBlock = ''; + $init_decryptBlock = ''; + } else { + for ($i = 0, $cw = count($this->w); $i < $cw; ++$i) { + $w[] = '$w_'.$i; + $dw[] = '$dw_'.$i; + } + $init_encryptBlock = 'extract($self->w, EXTR_PREFIX_ALL, "w");'; + $init_decryptBlock = 'extract($self->dw, EXTR_PREFIX_ALL, "dw");'; + } + + $code_hash = md5("$mode, $block_size, " . implode(',', $w)); + + if (!isset($lambda_functions[$code_hash])) { + $Nr = $this->Nr; + $Nb = $this->Nb; + $c = $this->c; + + // Generating encrypt code: + $init_encryptBlock.= ' + $t0 = $self->t0; + $t1 = $self->t1; + $t2 = $self->t2; + $t3 = $self->t3; + $sbox = $self->sbox;'; + + $s = 'e'; + $e = 's'; + $wc = $Nb - 1; + + // Preround: addRoundKey + $_encryptBlock = '$in = unpack("N*", $in);'."\n"; + for ($i = 0; $i < $Nb; ++$i) { + $_encryptBlock .= '$s'.$i.' = $in['.($i + 1).'] ^ '.$w[++$wc].";\n"; + } + + // Mainrounds: shiftRows + subWord + mixColumns + addRoundKey + for ($round = 1; $round < $Nr; ++$round) { + list($s, $e) = array($e, $s); + for ($i = 0; $i < $Nb; ++$i) { + $_encryptBlock.= + '$'.$e.$i.' = + $t0[($'.$s.$i .' >> 24) & 0xff] ^ + $t1[($'.$s.(($i + $c[1]) % $Nb).' >> 16) & 0xff] ^ + $t2[($'.$s.(($i + $c[2]) % $Nb).' >> 8) & 0xff] ^ + $t3[ $'.$s.(($i + $c[3]) % $Nb).' & 0xff] ^ + '.$w[++$wc].";\n"; + } + } + + // Finalround: subWord + shiftRows + addRoundKey + for ($i = 0; $i < $Nb; ++$i) { + $_encryptBlock.= + '$'.$e.$i.' = + $sbox[ $'.$e.$i.' & 0xff] | + ($sbox[($'.$e.$i.' >> 8) & 0xff] << 8) | + ($sbox[($'.$e.$i.' >> 16) & 0xff] << 16) | + ($sbox[($'.$e.$i.' >> 24) & 0xff] << 24);'."\n"; + } + $_encryptBlock .= '$in = pack("N*"'."\n"; + for ($i = 0; $i < $Nb; ++$i) { + $_encryptBlock.= ', + ($'.$e.$i .' & 0xFF000000) ^ + ($'.$e.(($i + $c[1]) % $Nb).' & 0x00FF0000) ^ + ($'.$e.(($i + $c[2]) % $Nb).' & 0x0000FF00) ^ + ($'.$e.(($i + $c[3]) % $Nb).' & 0x000000FF) ^ + '.$w[$i]."\n"; + } + $_encryptBlock .= ');'; + + // Generating decrypt code: + $init_decryptBlock.= ' + $dt0 = $self->dt0; + $dt1 = $self->dt1; + $dt2 = $self->dt2; + $dt3 = $self->dt3; + $isbox = $self->isbox;'; + + $s = 'e'; + $e = 's'; + $wc = $Nb - 1; + + // Preround: addRoundKey + $_decryptBlock = '$in = unpack("N*", $in);'."\n"; + for ($i = 0; $i < $Nb; ++$i) { + $_decryptBlock .= '$s'.$i.' = $in['.($i + 1).'] ^ '.$dw[++$wc].';'."\n"; + } + + // Mainrounds: shiftRows + subWord + mixColumns + addRoundKey + for ($round = 1; $round < $Nr; ++$round) { + list($s, $e) = array($e, $s); + for ($i = 0; $i < $Nb; ++$i) { + $_decryptBlock.= + '$'.$e.$i.' = + $dt0[($'.$s.$i .' >> 24) & 0xff] ^ + $dt1[($'.$s.(($Nb + $i - $c[1]) % $Nb).' >> 16) & 0xff] ^ + $dt2[($'.$s.(($Nb + $i - $c[2]) % $Nb).' >> 8) & 0xff] ^ + $dt3[ $'.$s.(($Nb + $i - $c[3]) % $Nb).' & 0xff] ^ + '.$dw[++$wc].";\n"; + } + } + + // Finalround: subWord + shiftRows + addRoundKey + for ($i = 0; $i < $Nb; ++$i) { + $_decryptBlock.= + '$'.$e.$i.' = + $isbox[ $'.$e.$i.' & 0xff] | + ($isbox[($'.$e.$i.' >> 8) & 0xff] << 8) | + ($isbox[($'.$e.$i.' >> 16) & 0xff] << 16) | + ($isbox[($'.$e.$i.' >> 24) & 0xff] << 24);'."\n"; + } + $_decryptBlock .= '$in = pack("N*"'."\n"; + for ($i = 0; $i < $Nb; ++$i) { + $_decryptBlock.= ', + ($'.$e.$i. ' & 0xFF000000) ^ + ($'.$e.(($Nb + $i - $c[1]) % $Nb).' & 0x00FF0000) ^ + ($'.$e.(($Nb + $i - $c[2]) % $Nb).' & 0x0000FF00) ^ + ($'.$e.(($Nb + $i - $c[3]) % $Nb).' & 0x000000FF) ^ + '.$dw[$i]."\n"; + } + $_decryptBlock .= ');'; + + // Generating mode of operation code: + switch ($mode) { + case CRYPT_RIJNDAEL_MODE_ECB: + $encrypt = $init_encryptBlock . ' + $ciphertext = ""; + $text = $self->_pad($text); + $plaintext_len = strlen($text); + + for ($i = 0; $i < $plaintext_len; $i+= '.$block_size.') { + $in = substr($text, $i, '.$block_size.'); + '.$_encryptBlock.' + $ciphertext.= $in; + } + + return $ciphertext; + '; + + $decrypt = $init_decryptBlock . ' + $plaintext = ""; + $text = str_pad($text, strlen($text) + ('.$block_size.' - strlen($text) % '.$block_size.') % '.$block_size.', chr(0)); + $ciphertext_len = strlen($text); + + for ($i = 0; $i < $ciphertext_len; $i+= '.$block_size.') { + $in = substr($text, $i, '.$block_size.'); + '.$_decryptBlock.' + $plaintext.= $in; + } + + return $self->_unpad($plaintext); + '; + break; + case CRYPT_RIJNDAEL_MODE_CBC: + $encrypt = $init_encryptBlock . ' + $ciphertext = ""; + $text = $self->_pad($text); + $plaintext_len = strlen($text); + + $in = $self->encryptIV; + + for ($i = 0; $i < $plaintext_len; $i+= '.$block_size.') { + $in = substr($text, $i, '.$block_size.') ^ $in; + '.$_encryptBlock.' + $ciphertext.= $in; + } + + if ($self->continuousBuffer) { + $self->encryptIV = $in; + } + + return $ciphertext; + '; + + $decrypt = $init_decryptBlock . ' + $plaintext = ""; + $text = str_pad($text, strlen($text) + ('.$block_size.' - strlen($text) % '.$block_size.') % '.$block_size.', chr(0)); + $ciphertext_len = strlen($text); + + $iv = $self->decryptIV; + + for ($i = 0; $i < $ciphertext_len; $i+= '.$block_size.') { + $in = $block = substr($text, $i, '.$block_size.'); + '.$_decryptBlock.' + $plaintext.= $in ^ $iv; + $iv = $block; + } + + if ($self->continuousBuffer) { + $self->decryptIV = $iv; + } + + return $self->_unpad($plaintext); + '; + break; + case CRYPT_RIJNDAEL_MODE_CTR: + $encrypt = $init_encryptBlock . ' + $ciphertext = ""; + $plaintext_len = strlen($text); + $xor = $self->encryptIV; + $buffer = &$self->enbuffer; + + if (strlen($buffer["encrypted"])) { + for ($i = 0; $i < $plaintext_len; $i+= '.$block_size.') { + $block = substr($text, $i, '.$block_size.'); + if (strlen($block) > strlen($buffer["encrypted"])) { + $in = $self->_generate_xor('.$block_size.', $xor); + '.$_encryptBlock.' + $buffer["encrypted"].= $in; + } + $key = $self->_string_shift($buffer["encrypted"], '.$block_size.'); + $ciphertext.= $block ^ $key; + } + } else { + for ($i = 0; $i < $plaintext_len; $i+= '.$block_size.') { + $block = substr($text, $i, '.$block_size.'); + $in = $self->_generate_xor('.$block_size.', $xor); + '.$_encryptBlock.' + $key = $in; + $ciphertext.= $block ^ $key; + } + } + if ($self->continuousBuffer) { + $self->encryptIV = $xor; + if ($start = $plaintext_len % '.$block_size.') { + $buffer["encrypted"] = substr($key, $start) . $buffer["encrypted"]; + } + } + + return $ciphertext; + '; + + $decrypt = $init_encryptBlock . ' + $plaintext = ""; + $ciphertext_len = strlen($text); + $xor = $self->decryptIV; + $buffer = &$self->debuffer; + + if (strlen($buffer["ciphertext"])) { + for ($i = 0; $i < $ciphertext_len; $i+= '.$block_size.') { + $block = substr($text, $i, '.$block_size.'); + if (strlen($block) > strlen($buffer["ciphertext"])) { + $in = $self->_generate_xor('.$block_size.', $xor); + '.$_encryptBlock.' + $buffer["ciphertext"].= $in; + } + $key = $self->_string_shift($buffer["ciphertext"], '.$block_size.'); + $plaintext.= $block ^ $key; + } + } else { + for ($i = 0; $i < $ciphertext_len; $i+= '.$block_size.') { + $block = substr($text, $i, '.$block_size.'); + $in = $self->_generate_xor('.$block_size.', $xor); + '.$_encryptBlock.' + $key = $in; + $plaintext.= $block ^ $key; + } + } + if ($self->continuousBuffer) { + $self->decryptIV = $xor; + if ($start = $ciphertext_len % '.$block_size.') { + $buffer["ciphertext"] = substr($key, $start) . $buffer["ciphertext"]; + } + } + + return $plaintext; + '; + break; + case CRYPT_RIJNDAEL_MODE_CFB: + $encrypt = $init_encryptBlock . ' + $ciphertext = ""; + $buffer = &$self->enbuffer; + + if ($self->continuousBuffer) { + $iv = &$self->encryptIV; + $pos = &$buffer["pos"]; + } else { + $iv = $self->encryptIV; + $pos = 0; + } + $len = strlen($text); + $i = 0; + if ($pos) { + $orig_pos = $pos; + $max = '.$block_size.' - $pos; + if ($len >= $max) { + $i = $max; + $len-= $max; + $pos = 0; + } else { + $i = $len; + $pos+= $len; + $len = 0; + } + $ciphertext = substr($iv, $orig_pos) ^ $text; + $iv = substr_replace($iv, $ciphertext, $orig_pos, $i); + } + while ($len >= '.$block_size.') { + $in = $iv; + '.$_encryptBlock.'; + $iv = $in ^ substr($text, $i, '.$block_size.'); + $ciphertext.= $iv; + $len-= '.$block_size.'; + $i+= '.$block_size.'; + } + if ($len) { + $in = $iv; + '.$_encryptBlock.' + $iv = $in; + $block = $iv ^ substr($text, $i); + $iv = substr_replace($iv, $block, 0, $len); + $ciphertext.= $block; + $pos = $len; + } + return $ciphertext; + '; + + $decrypt = $init_encryptBlock . ' + $plaintext = ""; + $buffer = &$self->debuffer; + + if ($self->continuousBuffer) { + $iv = &$self->decryptIV; + $pos = &$buffer["pos"]; + } else { + $iv = $self->decryptIV; + $pos = 0; + } + $len = strlen($text); + $i = 0; + if ($pos) { + $orig_pos = $pos; + $max = '.$block_size.' - $pos; + if ($len >= $max) { + $i = $max; + $len-= $max; + $pos = 0; + } else { + $i = $len; + $pos+= $len; + $len = 0; + } + $plaintext = substr($iv, $orig_pos) ^ $text; + $iv = substr_replace($iv, substr($text, 0, $i), $orig_pos, $i); + } + while ($len >= '.$block_size.') { + $in = $iv; + '.$_encryptBlock.' + $iv = $in; + $cb = substr($text, $i, '.$block_size.'); + $plaintext.= $iv ^ $cb; + $iv = $cb; + $len-= '.$block_size.'; + $i+= '.$block_size.'; + } + if ($len) { + $in = $iv; + '.$_encryptBlock.' + $iv = $in; + $plaintext.= $iv ^ substr($text, $i); + $iv = substr_replace($iv, substr($text, $i), 0, $len); + $pos = $len; + } + + return $plaintext; + '; + break; + case CRYPT_RIJNDAEL_MODE_OFB: + $encrypt = $init_encryptBlock . ' + $ciphertext = ""; + $plaintext_len = strlen($text); + $xor = $self->encryptIV; + $buffer = &$self->enbuffer; + + if (strlen($buffer["xor"])) { + for ($i = 0; $i < $plaintext_len; $i+= '.$block_size.') { + $block = substr($text, $i, '.$block_size.'); + if (strlen($block) > strlen($buffer["xor"])) { + $in = $xor; + '.$_encryptBlock.' + $xor = $in; + $buffer["xor"].= $xor; + } + $key = $self->_string_shift($buffer["xor"], '.$block_size.'); + $ciphertext.= $block ^ $key; + } + } else { + for ($i = 0; $i < $plaintext_len; $i+= '.$block_size.') { + $in = $xor; + '.$_encryptBlock.' + $xor = $in; + $ciphertext.= substr($text, $i, '.$block_size.') ^ $xor; + } + $key = $xor; + } + if ($self->continuousBuffer) { + $self->encryptIV = $xor; + if ($start = $plaintext_len % '.$block_size.') { + $buffer["xor"] = substr($key, $start) . $buffer["xor"]; + } + } + return $ciphertext; + '; + + $decrypt = $init_encryptBlock . ' + $plaintext = ""; + $ciphertext_len = strlen($text); + $xor = $self->decryptIV; + $buffer = &$self->debuffer; + + if (strlen($buffer["xor"])) { + for ($i = 0; $i < $ciphertext_len; $i+= '.$block_size.') { + $block = substr($text, $i, '.$block_size.'); + if (strlen($block) > strlen($buffer["xor"])) { + $in = $xor; + '.$_encryptBlock.' + $xor = $in; + $buffer["xor"].= $xor; + } + $key = $self->_string_shift($buffer["xor"], '.$block_size.'); + $plaintext.= $block ^ $key; + } + } else { + for ($i = 0; $i < $ciphertext_len; $i+= '.$block_size.') { + $in = $xor; + '.$_encryptBlock.' + $xor = $in; + $plaintext.= substr($text, $i, '.$block_size.') ^ $xor; + } + $key = $xor; + } + if ($self->continuousBuffer) { + $self->decryptIV = $xor; + if ($start = $ciphertext_len % '.$block_size.') { + $buffer["xor"] = substr($key, $start) . $buffer["xor"]; + } + } + return $plaintext; + '; + break; + } + $lambda_functions[$code_hash] = create_function('$action, &$self, $text', 'if ($action == "encrypt") { '.$encrypt.' } else { '.$decrypt.' }'); + } + $this->inline_crypt = $lambda_functions[$code_hash]; + } + + /** + * Holds the lambda_functions table (classwide) + * + * @see Crypt_Rijndael::inline_crypt_setup() + * @return Array + * @access private + */ + function &get_lambda_functions() + { + static $functions = array(); + return $functions; + } +} + +// vim: ts=4:sw=4:et: +// vim6: fdl=1: diff --git a/include/pear/Math/BigInteger.php b/include/pear/Math/BigInteger.php new file mode 100644 index 0000000000000000000000000000000000000000..37acb1fe300e23864b627197622d0ddf91e20f15 --- /dev/null +++ b/include/pear/Math/BigInteger.php @@ -0,0 +1,3650 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * Pure-PHP arbitrary precision integer arithmetic library. + * + * Supports base-2, base-10, base-16, and base-256 numbers. Uses the GMP or BCMath extensions, if available, + * and an internal implementation, otherwise. + * + * PHP versions 4 and 5 + * + * {@internal (all DocBlock comments regarding implementation - such as the one that follows - refer to the + * {@link MATH_BIGINTEGER_MODE_INTERNAL MATH_BIGINTEGER_MODE_INTERNAL} mode) + * + * Math_BigInteger uses base-2**26 to perform operations such as multiplication and division and + * base-2**52 (ie. two base 2**26 digits) to perform addition and subtraction. Because the largest possible + * value when multiplying two base-2**26 numbers together is a base-2**52 number, double precision floating + * point numbers - numbers that should be supported on most hardware and whose significand is 53 bits - are + * used. As a consequence, bitwise operators such as >> and << cannot be used, nor can the modulo operator %, + * which only supports integers. Although this fact will slow this library down, the fact that such a high + * base is being used should more than compensate. + * + * When PHP version 6 is officially released, we'll be able to use 64-bit integers. This should, once again, + * allow bitwise operators, and will increase the maximum possible base to 2**31 (or 2**62 for addition / + * subtraction). + * + * Numbers are stored in {@link http://en.wikipedia.org/wiki/Endianness little endian} format. ie. + * (new Math_BigInteger(pow(2, 26)))->value = array(0, 1) + * + * Useful resources are as follows: + * + * - {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf Handbook of Applied Cryptography (HAC)} + * - {@link http://math.libtomcrypt.com/files/tommath.pdf Multi-Precision Math (MPM)} + * - Java's BigInteger classes. See /j2se/src/share/classes/java/math in jdk-1_5_0-src-jrl.zip + * + * Here's an example of how to use this library: + * <code> + * <?php + * include('Math/BigInteger.php'); + * + * $a = new Math_BigInteger(2); + * $b = new Math_BigInteger(3); + * + * $c = $a->add($b); + * + * echo $c->toString(); // outputs 5 + * ?> + * </code> + * + * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @category Math + * @package Math_BigInteger + * @author Jim Wigginton <terrafrost@php.net> + * @copyright MMVI Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://pear.php.net/package/Math_BigInteger + */ + +/**#@+ + * Reduction constants + * + * @access private + * @see Math_BigInteger::_reduce() + */ +/** + * @see Math_BigInteger::_montgomery() + * @see Math_BigInteger::_prepMontgomery() + */ +define('MATH_BIGINTEGER_MONTGOMERY', 0); +/** + * @see Math_BigInteger::_barrett() + */ +define('MATH_BIGINTEGER_BARRETT', 1); +/** + * @see Math_BigInteger::_mod2() + */ +define('MATH_BIGINTEGER_POWEROF2', 2); +/** + * @see Math_BigInteger::_remainder() #nolint + */ +define('MATH_BIGINTEGER_CLASSIC', 3); +/** + * @see Math_BigInteger::__clone() + */ +define('MATH_BIGINTEGER_NONE', 4); +/**#@-*/ + +/**#@+ + * Array constants + * + * Rather than create a thousands and thousands of new Math_BigInteger objects in repeated function calls to add() and + * multiply() or whatever, we'll just work directly on arrays, taking them in as parameters and returning them. + * + * @access private + */ +/** + * $result[MATH_BIGINTEGER_VALUE] contains the value. + */ +define('MATH_BIGINTEGER_VALUE', 0); +/** + * $result[MATH_BIGINTEGER_SIGN] contains the sign. + */ +define('MATH_BIGINTEGER_SIGN', 1); +/**#@-*/ + +/**#@+ + * @access private + * @see Math_BigInteger::_montgomery() + * @see Math_BigInteger::_barrett() + */ +/** + * Cache constants + * + * $cache[MATH_BIGINTEGER_VARIABLE] tells us whether or not the cached data is still valid. + */ +define('MATH_BIGINTEGER_VARIABLE', 0); +/** + * $cache[MATH_BIGINTEGER_DATA] contains the cached data. + */ +define('MATH_BIGINTEGER_DATA', 1); +/**#@-*/ + +/**#@+ + * Mode constants. + * + * @access private + * @see Math_BigInteger::Math_BigInteger() + */ +/** + * To use the pure-PHP implementation + */ +define('MATH_BIGINTEGER_MODE_INTERNAL', 1); +/** + * To use the BCMath library + * + * (if enabled; otherwise, the internal implementation will be used) + */ +define('MATH_BIGINTEGER_MODE_BCMATH', 2); +/** + * To use the GMP library + * + * (if present; otherwise, either the BCMath or the internal implementation will be used) + */ +define('MATH_BIGINTEGER_MODE_GMP', 3); +/**#@-*/ + +/** + * Karatsuba Cutoff + * + * At what point do we switch between Karatsuba multiplication and schoolbook long multiplication? + * + * @access private + */ +define('MATH_BIGINTEGER_KARATSUBA_CUTOFF', 25); + +/** + * Pure-PHP arbitrary precision integer arithmetic library. Supports base-2, base-10, base-16, and base-256 + * numbers. + * + * @author Jim Wigginton <terrafrost@php.net> + * @version 1.0.0RC4 + * @access public + * @package Math_BigInteger + */ +class Math_BigInteger { + /** + * Holds the BigInteger's value. + * + * @var Array + * @access private + */ + var $value; + + /** + * Holds the BigInteger's magnitude. + * + * @var Boolean + * @access private + */ + var $is_negative = false; + + /** + * Random number generator function + * + * @see setRandomGenerator() + * @access private + */ + var $generator = 'mt_rand'; + + /** + * Precision + * + * @see setPrecision() + * @access private + */ + var $precision = -1; + + /** + * Precision Bitmask + * + * @see setPrecision() + * @access private + */ + var $bitmask = false; + + /** + * Mode independent value used for serialization. + * + * If the bcmath or gmp extensions are installed $this->value will be a non-serializable resource, hence the need for + * a variable that'll be serializable regardless of whether or not extensions are being used. Unlike $this->value, + * however, $this->hex is only calculated when $this->__sleep() is called. + * + * @see __sleep() + * @see __wakeup() + * @var String + * @access private + */ + var $hex; + + /** + * Converts base-2, base-10, base-16, and binary strings (base-256) to BigIntegers. + * + * If the second parameter - $base - is negative, then it will be assumed that the number's are encoded using + * two's compliment. The sole exception to this is -10, which is treated the same as 10 is. + * + * Here's an example: + * <code> + * <?php + * include('Math/BigInteger.php'); + * + * $a = new Math_BigInteger('0x32', 16); // 50 in base-16 + * + * echo $a->toString(); // outputs 50 + * ?> + * </code> + * + * @param optional $x base-10 number or base-$base number if $base set. + * @param optional integer $base + * @return Math_BigInteger + * @access public + */ + function Math_BigInteger($x = 0, $base = 10) + { + if ( !defined('MATH_BIGINTEGER_MODE') ) { + switch (true) { + case extension_loaded('gmp'): + define('MATH_BIGINTEGER_MODE', MATH_BIGINTEGER_MODE_GMP); + break; + case extension_loaded('bcmath'): + define('MATH_BIGINTEGER_MODE', MATH_BIGINTEGER_MODE_BCMATH); + break; + default: + define('MATH_BIGINTEGER_MODE', MATH_BIGINTEGER_MODE_INTERNAL); + } + } + + if (function_exists('openssl_public_encrypt') && !defined('MATH_BIGINTEGER_OPENSSL_DISABLE') && !defined('MATH_BIGINTEGER_OPENSSL_ENABLED')) { + define('MATH_BIGINTEGER_OPENSSL_ENABLED', true); + } + + if (!defined('PHP_INT_SIZE')) { + define('PHP_INT_SIZE', 4); + } + + if (!defined('MATH_BIGINTEGER_BASE') && MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_INTERNAL) { + switch (PHP_INT_SIZE) { + case 8: // use 64-bit integers if int size is 8 bytes + define('MATH_BIGINTEGER_BASE', 31); + define('MATH_BIGINTEGER_BASE_FULL', 0x80000000); + define('MATH_BIGINTEGER_MAX_DIGIT', 0x7FFFFFFF); + define('MATH_BIGINTEGER_MSB', 0x40000000); + // 10**9 is the closest we can get to 2**31 without passing it + define('MATH_BIGINTEGER_MAX10', 1000000000); + define('MATH_BIGINTEGER_MAX10_LEN', 9); + // the largest digit that may be used in addition / subtraction + define('MATH_BIGINTEGER_MAX_DIGIT2', pow(2, 62)); + break; + //case 4: // use 64-bit floats if int size is 4 bytes + default: + define('MATH_BIGINTEGER_BASE', 26); + define('MATH_BIGINTEGER_BASE_FULL', 0x4000000); + define('MATH_BIGINTEGER_MAX_DIGIT', 0x3FFFFFF); + define('MATH_BIGINTEGER_MSB', 0x2000000); + // 10**7 is the closest to 2**26 without passing it + define('MATH_BIGINTEGER_MAX10', 10000000); + define('MATH_BIGINTEGER_MAX10_LEN', 7); + // the largest digit that may be used in addition / subtraction + // we do pow(2, 52) instead of using 4503599627370496 directly because some + // PHP installations will truncate 4503599627370496. + define('MATH_BIGINTEGER_MAX_DIGIT2', pow(2, 52)); + } + } + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + if (is_resource($x) && get_resource_type($x) == 'GMP integer') { + $this->value = $x; + return; + } + $this->value = gmp_init(0); + break; + case MATH_BIGINTEGER_MODE_BCMATH: + $this->value = '0'; + break; + default: + $this->value = array(); + } + + // '0' counts as empty() but when the base is 256 '0' is equal to ord('0') or 48 + // '0' is the only value like this per http://php.net/empty + if (empty($x) && (abs($base) != 256 || $x !== '0')) { + return; + } + + switch ($base) { + case -256: + if (ord($x[0]) & 0x80) { + $x = ~$x; + $this->is_negative = true; + } + case 256: + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $sign = $this->is_negative ? '-' : ''; + $this->value = gmp_init($sign . '0x' . bin2hex($x)); + break; + case MATH_BIGINTEGER_MODE_BCMATH: + // round $len to the nearest 4 (thanks, DavidMJ!) + $len = (strlen($x) + 3) & 0xFFFFFFFC; + + $x = str_pad($x, $len, chr(0), STR_PAD_LEFT); + + for ($i = 0; $i < $len; $i+= 4) { + $this->value = bcmul($this->value, '4294967296', 0); // 4294967296 == 2**32 + $this->value = bcadd($this->value, 0x1000000 * ord($x[$i]) + ((ord($x[$i + 1]) << 16) | (ord($x[$i + 2]) << 8) | ord($x[$i + 3])), 0); + } + + if ($this->is_negative) { + $this->value = '-' . $this->value; + } + + break; + // converts a base-2**8 (big endian / msb) number to base-2**26 (little endian / lsb) + default: + while (strlen($x)) { + $this->value[] = $this->_bytes2int($this->_base256_rshift($x, MATH_BIGINTEGER_BASE)); + } + } + + if ($this->is_negative) { + if (MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_INTERNAL) { + $this->is_negative = false; + } + $temp = $this->add(new Math_BigInteger('-1')); + $this->value = $temp->value; + } + break; + case 16: + case -16: + if ($base > 0 && $x[0] == '-') { + $this->is_negative = true; + $x = substr($x, 1); + } + + $x = preg_replace('#^(?:0x)?([A-Fa-f0-9]*).*#', '$1', $x); + + $is_negative = false; + if ($base < 0 && hexdec($x[0]) >= 8) { + $this->is_negative = $is_negative = true; + $x = bin2hex(~pack('H*', $x)); + } + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = $this->is_negative ? '-0x' . $x : '0x' . $x; + $this->value = gmp_init($temp); + $this->is_negative = false; + break; + case MATH_BIGINTEGER_MODE_BCMATH: + $x = ( strlen($x) & 1 ) ? '0' . $x : $x; + $temp = new Math_BigInteger(pack('H*', $x), 256); + $this->value = $this->is_negative ? '-' . $temp->value : $temp->value; + $this->is_negative = false; + break; + default: + $x = ( strlen($x) & 1 ) ? '0' . $x : $x; + $temp = new Math_BigInteger(pack('H*', $x), 256); + $this->value = $temp->value; + } + + if ($is_negative) { + $temp = $this->add(new Math_BigInteger('-1')); + $this->value = $temp->value; + } + break; + case 10: + case -10: + // (?<!^)(?:-).*: find any -'s that aren't at the beginning and then any characters that follow that + // (?<=^|-)0*: find any 0's that are preceded by the start of the string or by a - (ie. octals) + // [^-0-9].*: find any non-numeric characters and then any characters that follow that + $x = preg_replace('#(?<!^)(?:-).*|(?<=^|-)0*|[^-0-9].*#', '', $x); + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $this->value = gmp_init($x); + break; + case MATH_BIGINTEGER_MODE_BCMATH: + // explicitly casting $x to a string is necessary, here, since doing $x[0] on -1 yields different + // results then doing it on '-1' does (modInverse does $x[0]) + $this->value = $x === '-' ? '0' : (string) $x; + break; + default: + $temp = new Math_BigInteger(); + + $multiplier = new Math_BigInteger(); + $multiplier->value = array(MATH_BIGINTEGER_MAX10); + + if ($x[0] == '-') { + $this->is_negative = true; + $x = substr($x, 1); + } + + $x = str_pad($x, strlen($x) + ((MATH_BIGINTEGER_MAX10_LEN - 1) * strlen($x)) % MATH_BIGINTEGER_MAX10_LEN, 0, STR_PAD_LEFT); + + while (strlen($x)) { + $temp = $temp->multiply($multiplier); + $temp = $temp->add(new Math_BigInteger($this->_int2bytes(substr($x, 0, MATH_BIGINTEGER_MAX10_LEN)), 256)); + $x = substr($x, MATH_BIGINTEGER_MAX10_LEN); + } + + $this->value = $temp->value; + } + break; + case 2: // base-2 support originally implemented by Lluis Pamies - thanks! + case -2: + if ($base > 0 && $x[0] == '-') { + $this->is_negative = true; + $x = substr($x, 1); + } + + $x = preg_replace('#^([01]*).*#', '$1', $x); + $x = str_pad($x, strlen($x) + (3 * strlen($x)) % 4, 0, STR_PAD_LEFT); + + $str = '0x'; + while (strlen($x)) { + $part = substr($x, 0, 4); + $str.= dechex(bindec($part)); + $x = substr($x, 4); + } + + if ($this->is_negative) { + $str = '-' . $str; + } + + $temp = new Math_BigInteger($str, 8 * $base); // ie. either -16 or +16 + $this->value = $temp->value; + $this->is_negative = $temp->is_negative; + + break; + default: + // base not supported, so we'll let $this == 0 + } + } + + /** + * Converts a BigInteger to a byte string (eg. base-256). + * + * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're + * saved as two's compliment. + * + * Here's an example: + * <code> + * <?php + * include('Math/BigInteger.php'); + * + * $a = new Math_BigInteger('65'); + * + * echo $a->toBytes(); // outputs chr(65) + * ?> + * </code> + * + * @param Boolean $twos_compliment + * @return String + * @access public + * @internal Converts a base-2**26 number to base-2**8 + */ + function toBytes($twos_compliment = false) + { + if ($twos_compliment) { + $comparison = $this->compare(new Math_BigInteger()); + if ($comparison == 0) { + return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; + } + + $temp = $comparison < 0 ? $this->add(new Math_BigInteger(1)) : $this->copy(); + $bytes = $temp->toBytes(); + + if (empty($bytes)) { // eg. if the number we're trying to convert is -1 + $bytes = chr(0); + } + + if (ord($bytes[0]) & 0x80) { + $bytes = chr(0) . $bytes; + } + + return $comparison < 0 ? ~$bytes : $bytes; + } + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + if (gmp_cmp($this->value, gmp_init(0)) == 0) { + return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; + } + + $temp = gmp_strval(gmp_abs($this->value), 16); + $temp = ( strlen($temp) & 1 ) ? '0' . $temp : $temp; + $temp = pack('H*', $temp); + + return $this->precision > 0 ? + substr(str_pad($temp, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) : + ltrim($temp, chr(0)); + case MATH_BIGINTEGER_MODE_BCMATH: + if ($this->value === '0') { + return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; + } + + $value = ''; + $current = $this->value; + + if ($current[0] == '-') { + $current = substr($current, 1); + } + + while (bccomp($current, '0', 0) > 0) { + $temp = bcmod($current, '16777216'); + $value = chr($temp >> 16) . chr($temp >> 8) . chr($temp) . $value; + $current = bcdiv($current, '16777216', 0); + } + + return $this->precision > 0 ? + substr(str_pad($value, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) : + ltrim($value, chr(0)); + } + + if (!count($this->value)) { + return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : ''; + } + $result = $this->_int2bytes($this->value[count($this->value) - 1]); + + $temp = $this->copy(); + + for ($i = count($temp->value) - 2; $i >= 0; --$i) { + $temp->_base256_lshift($result, MATH_BIGINTEGER_BASE); + $result = $result | str_pad($temp->_int2bytes($temp->value[$i]), strlen($result), chr(0), STR_PAD_LEFT); + } + + return $this->precision > 0 ? + str_pad(substr($result, -(($this->precision + 7) >> 3)), ($this->precision + 7) >> 3, chr(0), STR_PAD_LEFT) : + $result; + } + + /** + * Converts a BigInteger to a hex string (eg. base-16)). + * + * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're + * saved as two's compliment. + * + * Here's an example: + * <code> + * <?php + * include('Math/BigInteger.php'); + * + * $a = new Math_BigInteger('65'); + * + * echo $a->toHex(); // outputs '41' + * ?> + * </code> + * + * @param Boolean $twos_compliment + * @return String + * @access public + * @internal Converts a base-2**26 number to base-2**8 + */ + function toHex($twos_compliment = false) + { + return bin2hex($this->toBytes($twos_compliment)); + } + + /** + * Converts a BigInteger to a bit string (eg. base-2). + * + * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're + * saved as two's compliment. + * + * Here's an example: + * <code> + * <?php + * include('Math/BigInteger.php'); + * + * $a = new Math_BigInteger('65'); + * + * echo $a->toBits(); // outputs '1000001' + * ?> + * </code> + * + * @param Boolean $twos_compliment + * @return String + * @access public + * @internal Converts a base-2**26 number to base-2**2 + */ + function toBits($twos_compliment = false) + { + $hex = $this->toHex($twos_compliment); + $bits = ''; + for ($i = strlen($hex) - 8, $start = strlen($hex) & 7; $i >= $start; $i-=8) { + $bits = str_pad(decbin(hexdec(substr($hex, $i, 8))), 32, '0', STR_PAD_LEFT) . $bits; + } + if ($start) { // hexdec('') == 0 + $bits = str_pad(decbin(hexdec(substr($hex, 0, $start))), 8, '0', STR_PAD_LEFT) . $bits; + } + $result = $this->precision > 0 ? substr($bits, -$this->precision) : ltrim($bits, '0'); + + if ($twos_compliment && $this->compare(new Math_BigInteger()) > 0 && $this->precision <= 0) { + return '0' . $result; + } + + return $result; + } + + /** + * Converts a BigInteger to a base-10 number. + * + * Here's an example: + * <code> + * <?php + * include('Math/BigInteger.php'); + * + * $a = new Math_BigInteger('50'); + * + * echo $a->toString(); // outputs 50 + * ?> + * </code> + * + * @return String + * @access public + * @internal Converts a base-2**26 number to base-10**7 (which is pretty much base-10) + */ + function toString() + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + return gmp_strval($this->value); + case MATH_BIGINTEGER_MODE_BCMATH: + if ($this->value === '0') { + return '0'; + } + + return ltrim($this->value, '0'); + } + + if (!count($this->value)) { + return '0'; + } + + $temp = $this->copy(); + $temp->is_negative = false; + + $divisor = new Math_BigInteger(); + $divisor->value = array(MATH_BIGINTEGER_MAX10); + $result = ''; + while (count($temp->value)) { + list($temp, $mod) = $temp->divide($divisor); + $result = str_pad(isset($mod->value[0]) ? $mod->value[0] : '', MATH_BIGINTEGER_MAX10_LEN, '0', STR_PAD_LEFT) . $result; + } + $result = ltrim($result, '0'); + if (empty($result)) { + $result = '0'; + } + + if ($this->is_negative) { + $result = '-' . $result; + } + + return $result; + } + + /** + * Copy an object + * + * PHP5 passes objects by reference while PHP4 passes by value. As such, we need a function to guarantee + * that all objects are passed by value, when appropriate. More information can be found here: + * + * {@link http://php.net/language.oop5.basic#51624} + * + * @access public + * @see __clone() + * @return Math_BigInteger + */ + function copy() + { + $temp = new Math_BigInteger(); + $temp->value = $this->value; + $temp->is_negative = $this->is_negative; + $temp->generator = $this->generator; + $temp->precision = $this->precision; + $temp->bitmask = $this->bitmask; + return $temp; + } + + /** + * __toString() magic method + * + * Will be called, automatically, if you're supporting just PHP5. If you're supporting PHP4, you'll need to call + * toString(). + * + * @access public + * @internal Implemented per a suggestion by Techie-Michael - thanks! + */ + function __toString() + { + return $this->toString(); + } + + /** + * __clone() magic method + * + * Although you can call Math_BigInteger::__toString() directly in PHP5, you cannot call Math_BigInteger::__clone() + * directly in PHP5. You can in PHP4 since it's not a magic method, but in PHP5, you have to call it by using the PHP5 + * only syntax of $y = clone $x. As such, if you're trying to write an application that works on both PHP4 and PHP5, + * call Math_BigInteger::copy(), instead. + * + * @access public + * @see copy() + * @return Math_BigInteger + */ + function __clone() + { + return $this->copy(); + } + + /** + * __sleep() magic method + * + * Will be called, automatically, when serialize() is called on a Math_BigInteger object. + * + * @see __wakeup() + * @access public + */ + function __sleep() + { + $this->hex = $this->toHex(true); + $vars = array('hex'); + if ($this->generator != 'mt_rand') { + $vars[] = 'generator'; + } + if ($this->precision > 0) { + $vars[] = 'precision'; + } + return $vars; + + } + + /** + * __wakeup() magic method + * + * Will be called, automatically, when unserialize() is called on a Math_BigInteger object. + * + * @see __sleep() + * @access public + */ + function __wakeup() + { + $temp = new Math_BigInteger($this->hex, -16); + $this->value = $temp->value; + $this->is_negative = $temp->is_negative; + $this->setRandomGenerator($this->generator); + if ($this->precision > 0) { + // recalculate $this->bitmask + $this->setPrecision($this->precision); + } + } + + /** + * Adds two BigIntegers. + * + * Here's an example: + * <code> + * <?php + * include('Math/BigInteger.php'); + * + * $a = new Math_BigInteger('10'); + * $b = new Math_BigInteger('20'); + * + * $c = $a->add($b); + * + * echo $c->toString(); // outputs 30 + * ?> + * </code> + * + * @param Math_BigInteger $y + * @return Math_BigInteger + * @access public + * @internal Performs base-2**52 addition + */ + function add($y) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new Math_BigInteger(); + $temp->value = gmp_add($this->value, $y->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $temp = new Math_BigInteger(); + $temp->value = bcadd($this->value, $y->value, 0); + + return $this->_normalize($temp); + } + + $temp = $this->_add($this->value, $this->is_negative, $y->value, $y->is_negative); + + $result = new Math_BigInteger(); + $result->value = $temp[MATH_BIGINTEGER_VALUE]; + $result->is_negative = $temp[MATH_BIGINTEGER_SIGN]; + + return $this->_normalize($result); + } + + /** + * Performs addition. + * + * @param Array $x_value + * @param Boolean $x_negative + * @param Array $y_value + * @param Boolean $y_negative + * @return Array + * @access private + */ + function _add($x_value, $x_negative, $y_value, $y_negative) + { + $x_size = count($x_value); + $y_size = count($y_value); + + if ($x_size == 0) { + return array( + MATH_BIGINTEGER_VALUE => $y_value, + MATH_BIGINTEGER_SIGN => $y_negative + ); + } else if ($y_size == 0) { + return array( + MATH_BIGINTEGER_VALUE => $x_value, + MATH_BIGINTEGER_SIGN => $x_negative + ); + } + + // subtract, if appropriate + if ( $x_negative != $y_negative ) { + if ( $x_value == $y_value ) { + return array( + MATH_BIGINTEGER_VALUE => array(), + MATH_BIGINTEGER_SIGN => false + ); + } + + $temp = $this->_subtract($x_value, false, $y_value, false); + $temp[MATH_BIGINTEGER_SIGN] = $this->_compare($x_value, false, $y_value, false) > 0 ? + $x_negative : $y_negative; + + return $temp; + } + + if ($x_size < $y_size) { + $size = $x_size; + $value = $y_value; + } else { + $size = $y_size; + $value = $x_value; + } + + $value[] = 0; // just in case the carry adds an extra digit + + $carry = 0; + for ($i = 0, $j = 1; $j < $size; $i+=2, $j+=2) { + $sum = $x_value[$j] * MATH_BIGINTEGER_BASE_FULL + $x_value[$i] + $y_value[$j] * MATH_BIGINTEGER_BASE_FULL + $y_value[$i] + $carry; + $carry = $sum >= MATH_BIGINTEGER_MAX_DIGIT2; // eg. floor($sum / 2**52); only possible values (in any base) are 0 and 1 + $sum = $carry ? $sum - MATH_BIGINTEGER_MAX_DIGIT2 : $sum; + + $temp = (int) ($sum / MATH_BIGINTEGER_BASE_FULL); + + $value[$i] = (int) ($sum - MATH_BIGINTEGER_BASE_FULL * $temp); // eg. a faster alternative to fmod($sum, 0x4000000) + $value[$j] = $temp; + } + + if ($j == $size) { // ie. if $y_size is odd + $sum = $x_value[$i] + $y_value[$i] + $carry; + $carry = $sum >= MATH_BIGINTEGER_BASE_FULL; + $value[$i] = $carry ? $sum - MATH_BIGINTEGER_BASE_FULL : $sum; + ++$i; // ie. let $i = $j since we've just done $value[$i] + } + + if ($carry) { + for (; $value[$i] == MATH_BIGINTEGER_MAX_DIGIT; ++$i) { + $value[$i] = 0; + } + ++$value[$i]; + } + + return array( + MATH_BIGINTEGER_VALUE => $this->_trim($value), + MATH_BIGINTEGER_SIGN => $x_negative + ); + } + + /** + * Subtracts two BigIntegers. + * + * Here's an example: + * <code> + * <?php + * include('Math/BigInteger.php'); + * + * $a = new Math_BigInteger('10'); + * $b = new Math_BigInteger('20'); + * + * $c = $a->subtract($b); + * + * echo $c->toString(); // outputs -10 + * ?> + * </code> + * + * @param Math_BigInteger $y + * @return Math_BigInteger + * @access public + * @internal Performs base-2**52 subtraction + */ + function subtract($y) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new Math_BigInteger(); + $temp->value = gmp_sub($this->value, $y->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $temp = new Math_BigInteger(); + $temp->value = bcsub($this->value, $y->value, 0); + + return $this->_normalize($temp); + } + + $temp = $this->_subtract($this->value, $this->is_negative, $y->value, $y->is_negative); + + $result = new Math_BigInteger(); + $result->value = $temp[MATH_BIGINTEGER_VALUE]; + $result->is_negative = $temp[MATH_BIGINTEGER_SIGN]; + + return $this->_normalize($result); + } + + /** + * Performs subtraction. + * + * @param Array $x_value + * @param Boolean $x_negative + * @param Array $y_value + * @param Boolean $y_negative + * @return Array + * @access private + */ + function _subtract($x_value, $x_negative, $y_value, $y_negative) + { + $x_size = count($x_value); + $y_size = count($y_value); + + if ($x_size == 0) { + return array( + MATH_BIGINTEGER_VALUE => $y_value, + MATH_BIGINTEGER_SIGN => !$y_negative + ); + } else if ($y_size == 0) { + return array( + MATH_BIGINTEGER_VALUE => $x_value, + MATH_BIGINTEGER_SIGN => $x_negative + ); + } + + // add, if appropriate (ie. -$x - +$y or +$x - -$y) + if ( $x_negative != $y_negative ) { + $temp = $this->_add($x_value, false, $y_value, false); + $temp[MATH_BIGINTEGER_SIGN] = $x_negative; + + return $temp; + } + + $diff = $this->_compare($x_value, $x_negative, $y_value, $y_negative); + + if ( !$diff ) { + return array( + MATH_BIGINTEGER_VALUE => array(), + MATH_BIGINTEGER_SIGN => false + ); + } + + // switch $x and $y around, if appropriate. + if ( (!$x_negative && $diff < 0) || ($x_negative && $diff > 0) ) { + $temp = $x_value; + $x_value = $y_value; + $y_value = $temp; + + $x_negative = !$x_negative; + + $x_size = count($x_value); + $y_size = count($y_value); + } + + // at this point, $x_value should be at least as big as - if not bigger than - $y_value + + $carry = 0; + for ($i = 0, $j = 1; $j < $y_size; $i+=2, $j+=2) { + $sum = $x_value[$j] * MATH_BIGINTEGER_BASE_FULL + $x_value[$i] - $y_value[$j] * MATH_BIGINTEGER_BASE_FULL - $y_value[$i] - $carry; + $carry = $sum < 0; // eg. floor($sum / 2**52); only possible values (in any base) are 0 and 1 + $sum = $carry ? $sum + MATH_BIGINTEGER_MAX_DIGIT2 : $sum; + + $temp = (int) ($sum / MATH_BIGINTEGER_BASE_FULL); + + $x_value[$i] = (int) ($sum - MATH_BIGINTEGER_BASE_FULL * $temp); + $x_value[$j] = $temp; + } + + if ($j == $y_size) { // ie. if $y_size is odd + $sum = $x_value[$i] - $y_value[$i] - $carry; + $carry = $sum < 0; + $x_value[$i] = $carry ? $sum + MATH_BIGINTEGER_BASE_FULL : $sum; + ++$i; + } + + if ($carry) { + for (; !$x_value[$i]; ++$i) { + $x_value[$i] = MATH_BIGINTEGER_MAX_DIGIT; + } + --$x_value[$i]; + } + + return array( + MATH_BIGINTEGER_VALUE => $this->_trim($x_value), + MATH_BIGINTEGER_SIGN => $x_negative + ); + } + + /** + * Multiplies two BigIntegers + * + * Here's an example: + * <code> + * <?php + * include('Math/BigInteger.php'); + * + * $a = new Math_BigInteger('10'); + * $b = new Math_BigInteger('20'); + * + * $c = $a->multiply($b); + * + * echo $c->toString(); // outputs 200 + * ?> + * </code> + * + * @param Math_BigInteger $x + * @return Math_BigInteger + * @access public + */ + function multiply($x) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new Math_BigInteger(); + $temp->value = gmp_mul($this->value, $x->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $temp = new Math_BigInteger(); + $temp->value = bcmul($this->value, $x->value, 0); + + return $this->_normalize($temp); + } + + $temp = $this->_multiply($this->value, $this->is_negative, $x->value, $x->is_negative); + + $product = new Math_BigInteger(); + $product->value = $temp[MATH_BIGINTEGER_VALUE]; + $product->is_negative = $temp[MATH_BIGINTEGER_SIGN]; + + return $this->_normalize($product); + } + + /** + * Performs multiplication. + * + * @param Array $x_value + * @param Boolean $x_negative + * @param Array $y_value + * @param Boolean $y_negative + * @return Array + * @access private + */ + function _multiply($x_value, $x_negative, $y_value, $y_negative) + { + //if ( $x_value == $y_value ) { + // return array( + // MATH_BIGINTEGER_VALUE => $this->_square($x_value), + // MATH_BIGINTEGER_SIGN => $x_sign != $y_value + // ); + //} + + $x_length = count($x_value); + $y_length = count($y_value); + + if ( !$x_length || !$y_length ) { // a 0 is being multiplied + return array( + MATH_BIGINTEGER_VALUE => array(), + MATH_BIGINTEGER_SIGN => false + ); + } + + return array( + MATH_BIGINTEGER_VALUE => min($x_length, $y_length) < 2 * MATH_BIGINTEGER_KARATSUBA_CUTOFF ? + $this->_trim($this->_regularMultiply($x_value, $y_value)) : + $this->_trim($this->_karatsuba($x_value, $y_value)), + MATH_BIGINTEGER_SIGN => $x_negative != $y_negative + ); + } + + /** + * Performs long multiplication on two BigIntegers + * + * Modeled after 'multiply' in MutableBigInteger.java. + * + * @param Array $x_value + * @param Array $y_value + * @return Array + * @access private + */ + function _regularMultiply($x_value, $y_value) + { + $x_length = count($x_value); + $y_length = count($y_value); + + if ( !$x_length || !$y_length ) { // a 0 is being multiplied + return array(); + } + + if ( $x_length < $y_length ) { + $temp = $x_value; + $x_value = $y_value; + $y_value = $temp; + + $x_length = count($x_value); + $y_length = count($y_value); + } + + $product_value = $this->_array_repeat(0, $x_length + $y_length); + + // the following for loop could be removed if the for loop following it + // (the one with nested for loops) initially set $i to 0, but + // doing so would also make the result in one set of unnecessary adds, + // since on the outermost loops first pass, $product->value[$k] is going + // to always be 0 + + $carry = 0; + + for ($j = 0; $j < $x_length; ++$j) { // ie. $i = 0 + $temp = $x_value[$j] * $y_value[0] + $carry; // $product_value[$k] == 0 + $carry = (int) ($temp / MATH_BIGINTEGER_BASE_FULL); + $product_value[$j] = (int) ($temp - MATH_BIGINTEGER_BASE_FULL * $carry); + } + + $product_value[$j] = $carry; + + // the above for loop is what the previous comment was talking about. the + // following for loop is the "one with nested for loops" + for ($i = 1; $i < $y_length; ++$i) { + $carry = 0; + + for ($j = 0, $k = $i; $j < $x_length; ++$j, ++$k) { + $temp = $product_value[$k] + $x_value[$j] * $y_value[$i] + $carry; + $carry = (int) ($temp / MATH_BIGINTEGER_BASE_FULL); + $product_value[$k] = (int) ($temp - MATH_BIGINTEGER_BASE_FULL * $carry); + } + + $product_value[$k] = $carry; + } + + return $product_value; + } + + /** + * Performs Karatsuba multiplication on two BigIntegers + * + * See {@link http://en.wikipedia.org/wiki/Karatsuba_algorithm Karatsuba algorithm} and + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=120 MPM 5.2.3}. + * + * @param Array $x_value + * @param Array $y_value + * @return Array + * @access private + */ + function _karatsuba($x_value, $y_value) + { + $m = min(count($x_value) >> 1, count($y_value) >> 1); + + if ($m < MATH_BIGINTEGER_KARATSUBA_CUTOFF) { + return $this->_regularMultiply($x_value, $y_value); + } + + $x1 = array_slice($x_value, $m); + $x0 = array_slice($x_value, 0, $m); + $y1 = array_slice($y_value, $m); + $y0 = array_slice($y_value, 0, $m); + + $z2 = $this->_karatsuba($x1, $y1); + $z0 = $this->_karatsuba($x0, $y0); + + $z1 = $this->_add($x1, false, $x0, false); + $temp = $this->_add($y1, false, $y0, false); + $z1 = $this->_karatsuba($z1[MATH_BIGINTEGER_VALUE], $temp[MATH_BIGINTEGER_VALUE]); + $temp = $this->_add($z2, false, $z0, false); + $z1 = $this->_subtract($z1, false, $temp[MATH_BIGINTEGER_VALUE], false); + + $z2 = array_merge(array_fill(0, 2 * $m, 0), $z2); + $z1[MATH_BIGINTEGER_VALUE] = array_merge(array_fill(0, $m, 0), $z1[MATH_BIGINTEGER_VALUE]); + + $xy = $this->_add($z2, false, $z1[MATH_BIGINTEGER_VALUE], $z1[MATH_BIGINTEGER_SIGN]); + $xy = $this->_add($xy[MATH_BIGINTEGER_VALUE], $xy[MATH_BIGINTEGER_SIGN], $z0, false); + + return $xy[MATH_BIGINTEGER_VALUE]; + } + + /** + * Performs squaring + * + * @param Array $x + * @return Array + * @access private + */ + function _square($x = false) + { + return count($x) < 2 * MATH_BIGINTEGER_KARATSUBA_CUTOFF ? + $this->_trim($this->_baseSquare($x)) : + $this->_trim($this->_karatsubaSquare($x)); + } + + /** + * Performs traditional squaring on two BigIntegers + * + * Squaring can be done faster than multiplying a number by itself can be. See + * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=7 HAC 14.2.4} / + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=141 MPM 5.3} for more information. + * + * @param Array $value + * @return Array + * @access private + */ + function _baseSquare($value) + { + if ( empty($value) ) { + return array(); + } + $square_value = $this->_array_repeat(0, 2 * count($value)); + + for ($i = 0, $max_index = count($value) - 1; $i <= $max_index; ++$i) { + $i2 = $i << 1; + + $temp = $square_value[$i2] + $value[$i] * $value[$i]; + $carry = (int) ($temp / MATH_BIGINTEGER_BASE_FULL); + $square_value[$i2] = (int) ($temp - MATH_BIGINTEGER_BASE_FULL * $carry); + + // note how we start from $i+1 instead of 0 as we do in multiplication. + for ($j = $i + 1, $k = $i2 + 1; $j <= $max_index; ++$j, ++$k) { + $temp = $square_value[$k] + 2 * $value[$j] * $value[$i] + $carry; + $carry = (int) ($temp / MATH_BIGINTEGER_BASE_FULL); + $square_value[$k] = (int) ($temp - MATH_BIGINTEGER_BASE_FULL * $carry); + } + + // the following line can yield values larger 2**15. at this point, PHP should switch + // over to floats. + $square_value[$i + $max_index + 1] = $carry; + } + + return $square_value; + } + + /** + * Performs Karatsuba "squaring" on two BigIntegers + * + * See {@link http://en.wikipedia.org/wiki/Karatsuba_algorithm Karatsuba algorithm} and + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=151 MPM 5.3.4}. + * + * @param Array $value + * @return Array + * @access private + */ + function _karatsubaSquare($value) + { + $m = count($value) >> 1; + + if ($m < MATH_BIGINTEGER_KARATSUBA_CUTOFF) { + return $this->_baseSquare($value); + } + + $x1 = array_slice($value, $m); + $x0 = array_slice($value, 0, $m); + + $z2 = $this->_karatsubaSquare($x1); + $z0 = $this->_karatsubaSquare($x0); + + $z1 = $this->_add($x1, false, $x0, false); + $z1 = $this->_karatsubaSquare($z1[MATH_BIGINTEGER_VALUE]); + $temp = $this->_add($z2, false, $z0, false); + $z1 = $this->_subtract($z1, false, $temp[MATH_BIGINTEGER_VALUE], false); + + $z2 = array_merge(array_fill(0, 2 * $m, 0), $z2); + $z1[MATH_BIGINTEGER_VALUE] = array_merge(array_fill(0, $m, 0), $z1[MATH_BIGINTEGER_VALUE]); + + $xx = $this->_add($z2, false, $z1[MATH_BIGINTEGER_VALUE], $z1[MATH_BIGINTEGER_SIGN]); + $xx = $this->_add($xx[MATH_BIGINTEGER_VALUE], $xx[MATH_BIGINTEGER_SIGN], $z0, false); + + return $xx[MATH_BIGINTEGER_VALUE]; + } + + /** + * Divides two BigIntegers. + * + * Returns an array whose first element contains the quotient and whose second element contains the + * "common residue". If the remainder would be positive, the "common residue" and the remainder are the + * same. If the remainder would be negative, the "common residue" is equal to the sum of the remainder + * and the divisor (basically, the "common residue" is the first positive modulo). + * + * Here's an example: + * <code> + * <?php + * include('Math/BigInteger.php'); + * + * $a = new Math_BigInteger('10'); + * $b = new Math_BigInteger('20'); + * + * list($quotient, $remainder) = $a->divide($b); + * + * echo $quotient->toString(); // outputs 0 + * echo "\r\n"; + * echo $remainder->toString(); // outputs 10 + * ?> + * </code> + * + * @param Math_BigInteger $y + * @return Array + * @access public + * @internal This function is based off of {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=9 HAC 14.20}. + */ + function divide($y) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $quotient = new Math_BigInteger(); + $remainder = new Math_BigInteger(); + + list($quotient->value, $remainder->value) = gmp_div_qr($this->value, $y->value); + + if (gmp_sign($remainder->value) < 0) { + $remainder->value = gmp_add($remainder->value, gmp_abs($y->value)); + } + + return array($this->_normalize($quotient), $this->_normalize($remainder)); + case MATH_BIGINTEGER_MODE_BCMATH: + $quotient = new Math_BigInteger(); + $remainder = new Math_BigInteger(); + + $quotient->value = bcdiv($this->value, $y->value, 0); + $remainder->value = bcmod($this->value, $y->value); + + if ($remainder->value[0] == '-') { + $remainder->value = bcadd($remainder->value, $y->value[0] == '-' ? substr($y->value, 1) : $y->value, 0); + } + + return array($this->_normalize($quotient), $this->_normalize($remainder)); + } + + if (count($y->value) == 1) { + list($q, $r) = $this->_divide_digit($this->value, $y->value[0]); + $quotient = new Math_BigInteger(); + $remainder = new Math_BigInteger(); + $quotient->value = $q; + $remainder->value = array($r); + $quotient->is_negative = $this->is_negative != $y->is_negative; + return array($this->_normalize($quotient), $this->_normalize($remainder)); + } + + static $zero; + if ( !isset($zero) ) { + $zero = new Math_BigInteger(); + } + + $x = $this->copy(); + $y = $y->copy(); + + $x_sign = $x->is_negative; + $y_sign = $y->is_negative; + + $x->is_negative = $y->is_negative = false; + + $diff = $x->compare($y); + + if ( !$diff ) { + $temp = new Math_BigInteger(); + $temp->value = array(1); + $temp->is_negative = $x_sign != $y_sign; + return array($this->_normalize($temp), $this->_normalize(new Math_BigInteger())); + } + + if ( $diff < 0 ) { + // if $x is negative, "add" $y. + if ( $x_sign ) { + $x = $y->subtract($x); + } + return array($this->_normalize(new Math_BigInteger()), $this->_normalize($x)); + } + + // normalize $x and $y as described in HAC 14.23 / 14.24 + $msb = $y->value[count($y->value) - 1]; + for ($shift = 0; !($msb & MATH_BIGINTEGER_MSB); ++$shift) { + $msb <<= 1; + } + $x->_lshift($shift); + $y->_lshift($shift); + $y_value = &$y->value; + + $x_max = count($x->value) - 1; + $y_max = count($y->value) - 1; + + $quotient = new Math_BigInteger(); + $quotient_value = &$quotient->value; + $quotient_value = $this->_array_repeat(0, $x_max - $y_max + 1); + + static $temp, $lhs, $rhs; + if (!isset($temp)) { + $temp = new Math_BigInteger(); + $lhs = new Math_BigInteger(); + $rhs = new Math_BigInteger(); + } + $temp_value = &$temp->value; + $rhs_value = &$rhs->value; + + // $temp = $y << ($x_max - $y_max-1) in base 2**26 + $temp_value = array_merge($this->_array_repeat(0, $x_max - $y_max), $y_value); + + while ( $x->compare($temp) >= 0 ) { + // calculate the "common residue" + ++$quotient_value[$x_max - $y_max]; + $x = $x->subtract($temp); + $x_max = count($x->value) - 1; + } + + for ($i = $x_max; $i >= $y_max + 1; --$i) { + $x_value = &$x->value; + $x_window = array( + isset($x_value[$i]) ? $x_value[$i] : 0, + isset($x_value[$i - 1]) ? $x_value[$i - 1] : 0, + isset($x_value[$i - 2]) ? $x_value[$i - 2] : 0 + ); + $y_window = array( + $y_value[$y_max], + ( $y_max > 0 ) ? $y_value[$y_max - 1] : 0 + ); + + $q_index = $i - $y_max - 1; + if ($x_window[0] == $y_window[0]) { + $quotient_value[$q_index] = MATH_BIGINTEGER_MAX_DIGIT; + } else { + $quotient_value[$q_index] = (int) ( + ($x_window[0] * MATH_BIGINTEGER_BASE_FULL + $x_window[1]) + / + $y_window[0] + ); + } + + $temp_value = array($y_window[1], $y_window[0]); + + $lhs->value = array($quotient_value[$q_index]); + $lhs = $lhs->multiply($temp); + + $rhs_value = array($x_window[2], $x_window[1], $x_window[0]); + + while ( $lhs->compare($rhs) > 0 ) { + --$quotient_value[$q_index]; + + $lhs->value = array($quotient_value[$q_index]); + $lhs = $lhs->multiply($temp); + } + + $adjust = $this->_array_repeat(0, $q_index); + $temp_value = array($quotient_value[$q_index]); + $temp = $temp->multiply($y); + $temp_value = &$temp->value; + $temp_value = array_merge($adjust, $temp_value); + + $x = $x->subtract($temp); + + if ($x->compare($zero) < 0) { + $temp_value = array_merge($adjust, $y_value); + $x = $x->add($temp); + + --$quotient_value[$q_index]; + } + + $x_max = count($x_value) - 1; + } + + // unnormalize the remainder + $x->_rshift($shift); + + $quotient->is_negative = $x_sign != $y_sign; + + // calculate the "common residue", if appropriate + if ( $x_sign ) { + $y->_rshift($shift); + $x = $y->subtract($x); + } + + return array($this->_normalize($quotient), $this->_normalize($x)); + } + + /** + * Divides a BigInteger by a regular integer + * + * abc / x = a00 / x + b0 / x + c / x + * + * @param Array $dividend + * @param Array $divisor + * @return Array + * @access private + */ + function _divide_digit($dividend, $divisor) + { + $carry = 0; + $result = array(); + + for ($i = count($dividend) - 1; $i >= 0; --$i) { + $temp = MATH_BIGINTEGER_BASE_FULL * $carry + $dividend[$i]; + $result[$i] = (int) ($temp / $divisor); + $carry = (int) ($temp - $divisor * $result[$i]); + } + + return array($result, $carry); + } + + /** + * Performs modular exponentiation. + * + * Here's an example: + * <code> + * <?php + * include('Math/BigInteger.php'); + * + * $a = new Math_BigInteger('10'); + * $b = new Math_BigInteger('20'); + * $c = new Math_BigInteger('30'); + * + * $c = $a->modPow($b, $c); + * + * echo $c->toString(); // outputs 10 + * ?> + * </code> + * + * @param Math_BigInteger $e + * @param Math_BigInteger $n + * @return Math_BigInteger + * @access public + * @internal The most naive approach to modular exponentiation has very unreasonable requirements, and + * and although the approach involving repeated squaring does vastly better, it, too, is impractical + * for our purposes. The reason being that division - by far the most complicated and time-consuming + * of the basic operations (eg. +,-,*,/) - occurs multiple times within it. + * + * Modular reductions resolve this issue. Although an individual modular reduction takes more time + * then an individual division, when performed in succession (with the same modulo), they're a lot faster. + * + * The two most commonly used modular reductions are Barrett and Montgomery reduction. Montgomery reduction, + * although faster, only works when the gcd of the modulo and of the base being used is 1. In RSA, when the + * base is a power of two, the modulo - a product of two primes - is always going to have a gcd of 1 (because + * the product of two odd numbers is odd), but what about when RSA isn't used? + * + * In contrast, Barrett reduction has no such constraint. As such, some bigint implementations perform a + * Barrett reduction after every operation in the modpow function. Others perform Barrett reductions when the + * modulo is even and Montgomery reductions when the modulo is odd. BigInteger.java's modPow method, however, + * uses a trick involving the Chinese Remainder Theorem to factor the even modulo into two numbers - one odd and + * the other, a power of two - and recombine them, later. This is the method that this modPow function uses. + * {@link http://islab.oregonstate.edu/papers/j34monex.pdf Montgomery Reduction with Even Modulus} elaborates. + */ + function modPow($e, $n) + { + $n = $this->bitmask !== false && $this->bitmask->compare($n) < 0 ? $this->bitmask : $n->abs(); + + if ($e->compare(new Math_BigInteger()) < 0) { + $e = $e->abs(); + + $temp = $this->modInverse($n); + if ($temp === false) { + return false; + } + + return $this->_normalize($temp->modPow($e, $n)); + } + + if ( MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_GMP ) { + $temp = new Math_BigInteger(); + $temp->value = gmp_powm($this->value, $e->value, $n->value); + + return $this->_normalize($temp); + } + + if ($this->compare(new Math_BigInteger()) < 0 || $this->compare($n) > 0) { + list(, $temp) = $this->divide($n); + return $temp->modPow($e, $n); + } + + if (defined('MATH_BIGINTEGER_OPENSSL_ENABLED')) { + $components = array( + 'modulus' => $n->toBytes(true), + 'publicExponent' => $e->toBytes(true) + ); + + $components = array( + 'modulus' => pack('Ca*a*', 2, $this->_encodeASN1Length(strlen($components['modulus'])), $components['modulus']), + 'publicExponent' => pack('Ca*a*', 2, $this->_encodeASN1Length(strlen($components['publicExponent'])), $components['publicExponent']) + ); + + $RSAPublicKey = pack('Ca*a*a*', + 48, $this->_encodeASN1Length(strlen($components['modulus']) + strlen($components['publicExponent'])), + $components['modulus'], $components['publicExponent'] + ); + + $rsaOID = pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA + $RSAPublicKey = chr(0) . $RSAPublicKey; + $RSAPublicKey = chr(3) . $this->_encodeASN1Length(strlen($RSAPublicKey)) . $RSAPublicKey; + + $encapsulated = pack('Ca*a*', + 48, $this->_encodeASN1Length(strlen($rsaOID . $RSAPublicKey)), $rsaOID . $RSAPublicKey + ); + + $RSAPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" . + chunk_split(base64_encode($encapsulated)) . + '-----END PUBLIC KEY-----'; + + $plaintext = str_pad($this->toBytes(), strlen($n->toBytes(true)) - 1, "\0", STR_PAD_LEFT); + + if (openssl_public_encrypt($plaintext, $result, $RSAPublicKey, OPENSSL_NO_PADDING)) { + return new Math_BigInteger($result, 256); + } + } + + if ( MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_BCMATH ) { + $temp = new Math_BigInteger(); + $temp->value = bcpowmod($this->value, $e->value, $n->value, 0); + + return $this->_normalize($temp); + } + + if ( empty($e->value) ) { + $temp = new Math_BigInteger(); + $temp->value = array(1); + return $this->_normalize($temp); + } + + if ( $e->value == array(1) ) { + list(, $temp) = $this->divide($n); + return $this->_normalize($temp); + } + + if ( $e->value == array(2) ) { + $temp = new Math_BigInteger(); + $temp->value = $this->_square($this->value); + list(, $temp) = $temp->divide($n); + return $this->_normalize($temp); + } + + return $this->_normalize($this->_slidingWindow($e, $n, MATH_BIGINTEGER_BARRETT)); + + // is the modulo odd? + if ( $n->value[0] & 1 ) { + return $this->_normalize($this->_slidingWindow($e, $n, MATH_BIGINTEGER_MONTGOMERY)); + } + // if it's not, it's even + + // find the lowest set bit (eg. the max pow of 2 that divides $n) + for ($i = 0; $i < count($n->value); ++$i) { + if ( $n->value[$i] ) { + $temp = decbin($n->value[$i]); + $j = strlen($temp) - strrpos($temp, '1') - 1; + $j+= 26 * $i; + break; + } + } + // at this point, 2^$j * $n/(2^$j) == $n + + $mod1 = $n->copy(); + $mod1->_rshift($j); + $mod2 = new Math_BigInteger(); + $mod2->value = array(1); + $mod2->_lshift($j); + + $part1 = ( $mod1->value != array(1) ) ? $this->_slidingWindow($e, $mod1, MATH_BIGINTEGER_MONTGOMERY) : new Math_BigInteger(); + $part2 = $this->_slidingWindow($e, $mod2, MATH_BIGINTEGER_POWEROF2); + + $y1 = $mod2->modInverse($mod1); + $y2 = $mod1->modInverse($mod2); + + $result = $part1->multiply($mod2); + $result = $result->multiply($y1); + + $temp = $part2->multiply($mod1); + $temp = $temp->multiply($y2); + + $result = $result->add($temp); + list(, $result) = $result->divide($n); + + return $this->_normalize($result); + } + + /** + * Performs modular exponentiation. + * + * Alias for Math_BigInteger::modPow() + * + * @param Math_BigInteger $e + * @param Math_BigInteger $n + * @return Math_BigInteger + * @access public + */ + function powMod($e, $n) + { + return $this->modPow($e, $n); + } + + /** + * Sliding Window k-ary Modular Exponentiation + * + * Based on {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=27 HAC 14.85} / + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=210 MPM 7.7}. In a departure from those algorithims, + * however, this function performs a modular reduction after every multiplication and squaring operation. + * As such, this function has the same preconditions that the reductions being used do. + * + * @param Math_BigInteger $e + * @param Math_BigInteger $n + * @param Integer $mode + * @return Math_BigInteger + * @access private + */ + function _slidingWindow($e, $n, $mode) + { + static $window_ranges = array(7, 25, 81, 241, 673, 1793); // from BigInteger.java's oddModPow function + //static $window_ranges = array(0, 7, 36, 140, 450, 1303, 3529); // from MPM 7.3.1 + + $e_value = $e->value; + $e_length = count($e_value) - 1; + $e_bits = decbin($e_value[$e_length]); + for ($i = $e_length - 1; $i >= 0; --$i) { + $e_bits.= str_pad(decbin($e_value[$i]), MATH_BIGINTEGER_BASE, '0', STR_PAD_LEFT); + } + + $e_length = strlen($e_bits); + + // calculate the appropriate window size. + // $window_size == 3 if $window_ranges is between 25 and 81, for example. + for ($i = 0, $window_size = 1; $e_length > $window_ranges[$i] && $i < count($window_ranges); ++$window_size, ++$i); + + $n_value = $n->value; + + // precompute $this^0 through $this^$window_size + $powers = array(); + $powers[1] = $this->_prepareReduce($this->value, $n_value, $mode); + $powers[2] = $this->_squareReduce($powers[1], $n_value, $mode); + + // we do every other number since substr($e_bits, $i, $j+1) (see below) is supposed to end + // in a 1. ie. it's supposed to be odd. + $temp = 1 << ($window_size - 1); + for ($i = 1; $i < $temp; ++$i) { + $i2 = $i << 1; + $powers[$i2 + 1] = $this->_multiplyReduce($powers[$i2 - 1], $powers[2], $n_value, $mode); + } + + $result = array(1); + $result = $this->_prepareReduce($result, $n_value, $mode); + + for ($i = 0; $i < $e_length; ) { + if ( !$e_bits[$i] ) { + $result = $this->_squareReduce($result, $n_value, $mode); + ++$i; + } else { + for ($j = $window_size - 1; $j > 0; --$j) { + if ( !empty($e_bits[$i + $j]) ) { + break; + } + } + + for ($k = 0; $k <= $j; ++$k) {// eg. the length of substr($e_bits, $i, $j+1) + $result = $this->_squareReduce($result, $n_value, $mode); + } + + $result = $this->_multiplyReduce($result, $powers[bindec(substr($e_bits, $i, $j + 1))], $n_value, $mode); + + $i+=$j + 1; + } + } + + $temp = new Math_BigInteger(); + $temp->value = $this->_reduce($result, $n_value, $mode); + + return $temp; + } + + /** + * Modular reduction + * + * For most $modes this will return the remainder. + * + * @see _slidingWindow() + * @access private + * @param Array $x + * @param Array $n + * @param Integer $mode + * @return Array + */ + function _reduce($x, $n, $mode) + { + switch ($mode) { + case MATH_BIGINTEGER_MONTGOMERY: + return $this->_montgomery($x, $n); + case MATH_BIGINTEGER_BARRETT: + return $this->_barrett($x, $n); + case MATH_BIGINTEGER_POWEROF2: + $lhs = new Math_BigInteger(); + $lhs->value = $x; + $rhs = new Math_BigInteger(); + $rhs->value = $n; + return $x->_mod2($n); + case MATH_BIGINTEGER_CLASSIC: + $lhs = new Math_BigInteger(); + $lhs->value = $x; + $rhs = new Math_BigInteger(); + $rhs->value = $n; + list(, $temp) = $lhs->divide($rhs); + return $temp->value; + case MATH_BIGINTEGER_NONE: + return $x; + default: + // an invalid $mode was provided + } + } + + /** + * Modular reduction preperation + * + * @see _slidingWindow() + * @access private + * @param Array $x + * @param Array $n + * @param Integer $mode + * @return Array + */ + function _prepareReduce($x, $n, $mode) + { + if ($mode == MATH_BIGINTEGER_MONTGOMERY) { + return $this->_prepMontgomery($x, $n); + } + return $this->_reduce($x, $n, $mode); + } + + /** + * Modular multiply + * + * @see _slidingWindow() + * @access private + * @param Array $x + * @param Array $y + * @param Array $n + * @param Integer $mode + * @return Array + */ + function _multiplyReduce($x, $y, $n, $mode) + { + if ($mode == MATH_BIGINTEGER_MONTGOMERY) { + return $this->_montgomeryMultiply($x, $y, $n); + } + $temp = $this->_multiply($x, false, $y, false); + return $this->_reduce($temp[MATH_BIGINTEGER_VALUE], $n, $mode); + } + + /** + * Modular square + * + * @see _slidingWindow() + * @access private + * @param Array $x + * @param Array $n + * @param Integer $mode + * @return Array + */ + function _squareReduce($x, $n, $mode) + { + if ($mode == MATH_BIGINTEGER_MONTGOMERY) { + return $this->_montgomeryMultiply($x, $x, $n); + } + return $this->_reduce($this->_square($x), $n, $mode); + } + + /** + * Modulos for Powers of Two + * + * Calculates $x%$n, where $n = 2**$e, for some $e. Since this is basically the same as doing $x & ($n-1), + * we'll just use this function as a wrapper for doing that. + * + * @see _slidingWindow() + * @access private + * @param Math_BigInteger + * @return Math_BigInteger + */ + function _mod2($n) + { + $temp = new Math_BigInteger(); + $temp->value = array(1); + return $this->bitwise_and($n->subtract($temp)); + } + + /** + * Barrett Modular Reduction + * + * See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=14 HAC 14.3.3} / + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=165 MPM 6.2.5} for more information. Modified slightly, + * so as not to require negative numbers (initially, this script didn't support negative numbers). + * + * Employs "folding", as described at + * {@link http://www.cosic.esat.kuleuven.be/publications/thesis-149.pdf#page=66 thesis-149.pdf#page=66}. To quote from + * it, "the idea [behind folding] is to find a value x' such that x (mod m) = x' (mod m), with x' being smaller than x." + * + * Unfortunately, the "Barrett Reduction with Folding" algorithm described in thesis-149.pdf is not, as written, all that + * usable on account of (1) its not using reasonable radix points as discussed in + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=162 MPM 6.2.2} and (2) the fact that, even with reasonable + * radix points, it only works when there are an even number of digits in the denominator. The reason for (2) is that + * (x >> 1) + (x >> 1) != x / 2 + x / 2. If x is even, they're the same, but if x is odd, they're not. See the in-line + * comments for details. + * + * @see _slidingWindow() + * @access private + * @param Array $n + * @param Array $m + * @return Array + */ + function _barrett($n, $m) + { + static $cache = array( + MATH_BIGINTEGER_VARIABLE => array(), + MATH_BIGINTEGER_DATA => array() + ); + + $m_length = count($m); + + // if ($this->_compare($n, $this->_square($m)) >= 0) { + if (count($n) > 2 * $m_length) { + $lhs = new Math_BigInteger(); + $rhs = new Math_BigInteger(); + $lhs->value = $n; + $rhs->value = $m; + list(, $temp) = $lhs->divide($rhs); + return $temp->value; + } + + // if (m.length >> 1) + 2 <= m.length then m is too small and n can't be reduced + if ($m_length < 5) { + return $this->_regularBarrett($n, $m); + } + + // n = 2 * m.length + + if ( ($key = array_search($m, $cache[MATH_BIGINTEGER_VARIABLE])) === false ) { + $key = count($cache[MATH_BIGINTEGER_VARIABLE]); + $cache[MATH_BIGINTEGER_VARIABLE][] = $m; + + $lhs = new Math_BigInteger(); + $lhs_value = &$lhs->value; + $lhs_value = $this->_array_repeat(0, $m_length + ($m_length >> 1)); + $lhs_value[] = 1; + $rhs = new Math_BigInteger(); + $rhs->value = $m; + + list($u, $m1) = $lhs->divide($rhs); + $u = $u->value; + $m1 = $m1->value; + + $cache[MATH_BIGINTEGER_DATA][] = array( + 'u' => $u, // m.length >> 1 (technically (m.length >> 1) + 1) + 'm1'=> $m1 // m.length + ); + } else { + extract($cache[MATH_BIGINTEGER_DATA][$key]); + } + + $cutoff = $m_length + ($m_length >> 1); + $lsd = array_slice($n, 0, $cutoff); // m.length + (m.length >> 1) + $msd = array_slice($n, $cutoff); // m.length >> 1 + $lsd = $this->_trim($lsd); + $temp = $this->_multiply($msd, false, $m1, false); + $n = $this->_add($lsd, false, $temp[MATH_BIGINTEGER_VALUE], false); // m.length + (m.length >> 1) + 1 + + if ($m_length & 1) { + return $this->_regularBarrett($n[MATH_BIGINTEGER_VALUE], $m); + } + + // (m.length + (m.length >> 1) + 1) - (m.length - 1) == (m.length >> 1) + 2 + $temp = array_slice($n[MATH_BIGINTEGER_VALUE], $m_length - 1); + // if even: ((m.length >> 1) + 2) + (m.length >> 1) == m.length + 2 + // if odd: ((m.length >> 1) + 2) + (m.length >> 1) == (m.length - 1) + 2 == m.length + 1 + $temp = $this->_multiply($temp, false, $u, false); + // if even: (m.length + 2) - ((m.length >> 1) + 1) = m.length - (m.length >> 1) + 1 + // if odd: (m.length + 1) - ((m.length >> 1) + 1) = m.length - (m.length >> 1) + $temp = array_slice($temp[MATH_BIGINTEGER_VALUE], ($m_length >> 1) + 1); + // if even: (m.length - (m.length >> 1) + 1) + m.length = 2 * m.length - (m.length >> 1) + 1 + // if odd: (m.length - (m.length >> 1)) + m.length = 2 * m.length - (m.length >> 1) + $temp = $this->_multiply($temp, false, $m, false); + + // at this point, if m had an odd number of digits, we'd be subtracting a 2 * m.length - (m.length >> 1) digit + // number from a m.length + (m.length >> 1) + 1 digit number. ie. there'd be an extra digit and the while loop + // following this comment would loop a lot (hence our calling _regularBarrett() in that situation). + + $result = $this->_subtract($n[MATH_BIGINTEGER_VALUE], false, $temp[MATH_BIGINTEGER_VALUE], false); + + while ($this->_compare($result[MATH_BIGINTEGER_VALUE], $result[MATH_BIGINTEGER_SIGN], $m, false) >= 0) { + $result = $this->_subtract($result[MATH_BIGINTEGER_VALUE], $result[MATH_BIGINTEGER_SIGN], $m, false); + } + + return $result[MATH_BIGINTEGER_VALUE]; + } + + /** + * (Regular) Barrett Modular Reduction + * + * For numbers with more than four digits Math_BigInteger::_barrett() is faster. The difference between that and this + * is that this function does not fold the denominator into a smaller form. + * + * @see _slidingWindow() + * @access private + * @param Array $x + * @param Array $n + * @return Array + */ + function _regularBarrett($x, $n) + { + static $cache = array( + MATH_BIGINTEGER_VARIABLE => array(), + MATH_BIGINTEGER_DATA => array() + ); + + $n_length = count($n); + + if (count($x) > 2 * $n_length) { + $lhs = new Math_BigInteger(); + $rhs = new Math_BigInteger(); + $lhs->value = $x; + $rhs->value = $n; + list(, $temp) = $lhs->divide($rhs); + return $temp->value; + } + + if ( ($key = array_search($n, $cache[MATH_BIGINTEGER_VARIABLE])) === false ) { + $key = count($cache[MATH_BIGINTEGER_VARIABLE]); + $cache[MATH_BIGINTEGER_VARIABLE][] = $n; + $lhs = new Math_BigInteger(); + $lhs_value = &$lhs->value; + $lhs_value = $this->_array_repeat(0, 2 * $n_length); + $lhs_value[] = 1; + $rhs = new Math_BigInteger(); + $rhs->value = $n; + list($temp, ) = $lhs->divide($rhs); // m.length + $cache[MATH_BIGINTEGER_DATA][] = $temp->value; + } + + // 2 * m.length - (m.length - 1) = m.length + 1 + $temp = array_slice($x, $n_length - 1); + // (m.length + 1) + m.length = 2 * m.length + 1 + $temp = $this->_multiply($temp, false, $cache[MATH_BIGINTEGER_DATA][$key], false); + // (2 * m.length + 1) - (m.length - 1) = m.length + 2 + $temp = array_slice($temp[MATH_BIGINTEGER_VALUE], $n_length + 1); + + // m.length + 1 + $result = array_slice($x, 0, $n_length + 1); + // m.length + 1 + $temp = $this->_multiplyLower($temp, false, $n, false, $n_length + 1); + // $temp == array_slice($temp->_multiply($temp, false, $n, false)->value, 0, $n_length + 1) + + if ($this->_compare($result, false, $temp[MATH_BIGINTEGER_VALUE], $temp[MATH_BIGINTEGER_SIGN]) < 0) { + $corrector_value = $this->_array_repeat(0, $n_length + 1); + $corrector_value[] = 1; + $result = $this->_add($result, false, $corrector_value, false); + $result = $result[MATH_BIGINTEGER_VALUE]; + } + + // at this point, we're subtracting a number with m.length + 1 digits from another number with m.length + 1 digits + $result = $this->_subtract($result, false, $temp[MATH_BIGINTEGER_VALUE], $temp[MATH_BIGINTEGER_SIGN]); + while ($this->_compare($result[MATH_BIGINTEGER_VALUE], $result[MATH_BIGINTEGER_SIGN], $n, false) > 0) { + $result = $this->_subtract($result[MATH_BIGINTEGER_VALUE], $result[MATH_BIGINTEGER_SIGN], $n, false); + } + + return $result[MATH_BIGINTEGER_VALUE]; + } + + /** + * Performs long multiplication up to $stop digits + * + * If you're going to be doing array_slice($product->value, 0, $stop), some cycles can be saved. + * + * @see _regularBarrett() + * @param Array $x_value + * @param Boolean $x_negative + * @param Array $y_value + * @param Boolean $y_negative + * @param Integer $stop + * @return Array + * @access private + */ + function _multiplyLower($x_value, $x_negative, $y_value, $y_negative, $stop) + { + $x_length = count($x_value); + $y_length = count($y_value); + + if ( !$x_length || !$y_length ) { // a 0 is being multiplied + return array( + MATH_BIGINTEGER_VALUE => array(), + MATH_BIGINTEGER_SIGN => false + ); + } + + if ( $x_length < $y_length ) { + $temp = $x_value; + $x_value = $y_value; + $y_value = $temp; + + $x_length = count($x_value); + $y_length = count($y_value); + } + + $product_value = $this->_array_repeat(0, $x_length + $y_length); + + // the following for loop could be removed if the for loop following it + // (the one with nested for loops) initially set $i to 0, but + // doing so would also make the result in one set of unnecessary adds, + // since on the outermost loops first pass, $product->value[$k] is going + // to always be 0 + + $carry = 0; + + for ($j = 0; $j < $x_length; ++$j) { // ie. $i = 0, $k = $i + $temp = $x_value[$j] * $y_value[0] + $carry; // $product_value[$k] == 0 + $carry = (int) ($temp / MATH_BIGINTEGER_BASE_FULL); + $product_value[$j] = (int) ($temp - MATH_BIGINTEGER_BASE_FULL * $carry); + } + + if ($j < $stop) { + $product_value[$j] = $carry; + } + + // the above for loop is what the previous comment was talking about. the + // following for loop is the "one with nested for loops" + + for ($i = 1; $i < $y_length; ++$i) { + $carry = 0; + + for ($j = 0, $k = $i; $j < $x_length && $k < $stop; ++$j, ++$k) { + $temp = $product_value[$k] + $x_value[$j] * $y_value[$i] + $carry; + $carry = (int) ($temp / MATH_BIGINTEGER_BASE_FULL); + $product_value[$k] = (int) ($temp - MATH_BIGINTEGER_BASE_FULL * $carry); + } + + if ($k < $stop) { + $product_value[$k] = $carry; + } + } + + return array( + MATH_BIGINTEGER_VALUE => $this->_trim($product_value), + MATH_BIGINTEGER_SIGN => $x_negative != $y_negative + ); + } + + /** + * Montgomery Modular Reduction + * + * ($x->_prepMontgomery($n))->_montgomery($n) yields $x % $n. + * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=170 MPM 6.3} provides insights on how this can be + * improved upon (basically, by using the comba method). gcd($n, 2) must be equal to one for this function + * to work correctly. + * + * @see _prepMontgomery() + * @see _slidingWindow() + * @access private + * @param Array $x + * @param Array $n + * @return Array + */ + function _montgomery($x, $n) + { + static $cache = array( + MATH_BIGINTEGER_VARIABLE => array(), + MATH_BIGINTEGER_DATA => array() + ); + + if ( ($key = array_search($n, $cache[MATH_BIGINTEGER_VARIABLE])) === false ) { + $key = count($cache[MATH_BIGINTEGER_VARIABLE]); + $cache[MATH_BIGINTEGER_VARIABLE][] = $x; + $cache[MATH_BIGINTEGER_DATA][] = $this->_modInverse67108864($n); + } + + $k = count($n); + + $result = array(MATH_BIGINTEGER_VALUE => $x); + + for ($i = 0; $i < $k; ++$i) { + $temp = $result[MATH_BIGINTEGER_VALUE][$i] * $cache[MATH_BIGINTEGER_DATA][$key]; + $temp = (int) ($temp - MATH_BIGINTEGER_BASE_FULL * ((int) ($temp / MATH_BIGINTEGER_BASE_FULL))); + $temp = $this->_regularMultiply(array($temp), $n); + $temp = array_merge($this->_array_repeat(0, $i), $temp); + $result = $this->_add($result[MATH_BIGINTEGER_VALUE], false, $temp, false); + } + + $result[MATH_BIGINTEGER_VALUE] = array_slice($result[MATH_BIGINTEGER_VALUE], $k); + + if ($this->_compare($result, false, $n, false) >= 0) { + $result = $this->_subtract($result[MATH_BIGINTEGER_VALUE], false, $n, false); + } + + return $result[MATH_BIGINTEGER_VALUE]; + } + + /** + * Montgomery Multiply + * + * Interleaves the montgomery reduction and long multiplication algorithms together as described in + * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=13 HAC 14.36} + * + * @see _prepMontgomery() + * @see _montgomery() + * @access private + * @param Array $x + * @param Array $y + * @param Array $m + * @return Array + */ + function _montgomeryMultiply($x, $y, $m) + { + $temp = $this->_multiply($x, false, $y, false); + return $this->_montgomery($temp[MATH_BIGINTEGER_VALUE], $m); + + static $cache = array( + MATH_BIGINTEGER_VARIABLE => array(), + MATH_BIGINTEGER_DATA => array() + ); + + if ( ($key = array_search($m, $cache[MATH_BIGINTEGER_VARIABLE])) === false ) { + $key = count($cache[MATH_BIGINTEGER_VARIABLE]); + $cache[MATH_BIGINTEGER_VARIABLE][] = $m; + $cache[MATH_BIGINTEGER_DATA][] = $this->_modInverse67108864($m); + } + + $n = max(count($x), count($y), count($m)); + $x = array_pad($x, $n, 0); + $y = array_pad($y, $n, 0); + $m = array_pad($m, $n, 0); + $a = array(MATH_BIGINTEGER_VALUE => $this->_array_repeat(0, $n + 1)); + for ($i = 0; $i < $n; ++$i) { + $temp = $a[MATH_BIGINTEGER_VALUE][0] + $x[$i] * $y[0]; + $temp = (int) ($temp - MATH_BIGINTEGER_BASE_FULL * ((int) ($temp / MATH_BIGINTEGER_BASE_FULL))); + $temp = $temp * $cache[MATH_BIGINTEGER_DATA][$key]; + $temp = (int) ($temp - MATH_BIGINTEGER_BASE_FULL * ((int) ($temp / MATH_BIGINTEGER_BASE_FULL))); + $temp = $this->_add($this->_regularMultiply(array($x[$i]), $y), false, $this->_regularMultiply(array($temp), $m), false); + $a = $this->_add($a[MATH_BIGINTEGER_VALUE], false, $temp[MATH_BIGINTEGER_VALUE], false); + $a[MATH_BIGINTEGER_VALUE] = array_slice($a[MATH_BIGINTEGER_VALUE], 1); + } + if ($this->_compare($a[MATH_BIGINTEGER_VALUE], false, $m, false) >= 0) { + $a = $this->_subtract($a[MATH_BIGINTEGER_VALUE], false, $m, false); + } + return $a[MATH_BIGINTEGER_VALUE]; + } + + /** + * Prepare a number for use in Montgomery Modular Reductions + * + * @see _montgomery() + * @see _slidingWindow() + * @access private + * @param Array $x + * @param Array $n + * @return Array + */ + function _prepMontgomery($x, $n) + { + $lhs = new Math_BigInteger(); + $lhs->value = array_merge($this->_array_repeat(0, count($n)), $x); + $rhs = new Math_BigInteger(); + $rhs->value = $n; + + list(, $temp) = $lhs->divide($rhs); + return $temp->value; + } + + /** + * Modular Inverse of a number mod 2**26 (eg. 67108864) + * + * Based off of the bnpInvDigit function implemented and justified in the following URL: + * + * {@link http://www-cs-students.stanford.edu/~tjw/jsbn/jsbn.js} + * + * The following URL provides more info: + * + * {@link http://groups.google.com/group/sci.crypt/msg/7a137205c1be7d85} + * + * As for why we do all the bitmasking... strange things can happen when converting from floats to ints. For + * instance, on some computers, var_dump((int) -4294967297) yields int(-1) and on others, it yields + * int(-2147483648). To avoid problems stemming from this, we use bitmasks to guarantee that ints aren't + * auto-converted to floats. The outermost bitmask is present because without it, there's no guarantee that + * the "residue" returned would be the so-called "common residue". We use fmod, in the last step, because the + * maximum possible $x is 26 bits and the maximum $result is 16 bits. Thus, we have to be able to handle up to + * 40 bits, which only 64-bit floating points will support. + * + * Thanks to Pedro Gimeno Fortea for input! + * + * @see _montgomery() + * @access private + * @param Array $x + * @return Integer + */ + function _modInverse67108864($x) // 2**26 == 67,108,864 + { + $x = -$x[0]; + $result = $x & 0x3; // x**-1 mod 2**2 + $result = ($result * (2 - $x * $result)) & 0xF; // x**-1 mod 2**4 + $result = ($result * (2 - ($x & 0xFF) * $result)) & 0xFF; // x**-1 mod 2**8 + $result = ($result * ((2 - ($x & 0xFFFF) * $result) & 0xFFFF)) & 0xFFFF; // x**-1 mod 2**16 + $result = fmod($result * (2 - fmod($x * $result, MATH_BIGINTEGER_BASE_FULL)), MATH_BIGINTEGER_BASE_FULL); // x**-1 mod 2**26 + return $result & MATH_BIGINTEGER_MAX_DIGIT; + } + + /** + * Calculates modular inverses. + * + * Say you have (30 mod 17 * x mod 17) mod 17 == 1. x can be found using modular inverses. + * + * Here's an example: + * <code> + * <?php + * include('Math/BigInteger.php'); + * + * $a = new Math_BigInteger(30); + * $b = new Math_BigInteger(17); + * + * $c = $a->modInverse($b); + * echo $c->toString(); // outputs 4 + * + * echo "\r\n"; + * + * $d = $a->multiply($c); + * list(, $d) = $d->divide($b); + * echo $d; // outputs 1 (as per the definition of modular inverse) + * ?> + * </code> + * + * @param Math_BigInteger $n + * @return mixed false, if no modular inverse exists, Math_BigInteger, otherwise. + * @access public + * @internal See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=21 HAC 14.64} for more information. + */ + function modInverse($n) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new Math_BigInteger(); + $temp->value = gmp_invert($this->value, $n->value); + + return ( $temp->value === false ) ? false : $this->_normalize($temp); + } + + static $zero, $one; + if (!isset($zero)) { + $zero = new Math_BigInteger(); + $one = new Math_BigInteger(1); + } + + // $x mod -$n == $x mod $n. + $n = $n->abs(); + + if ($this->compare($zero) < 0) { + $temp = $this->abs(); + $temp = $temp->modInverse($n); + return $this->_normalize($n->subtract($temp)); + } + + extract($this->extendedGCD($n)); + + if (!$gcd->equals($one)) { + return false; + } + + $x = $x->compare($zero) < 0 ? $x->add($n) : $x; + + return $this->compare($zero) < 0 ? $this->_normalize($n->subtract($x)) : $this->_normalize($x); + } + + /** + * Calculates the greatest common divisor and Bezout's identity. + * + * Say you have 693 and 609. The GCD is 21. Bezout's identity states that there exist integers x and y such that + * 693*x + 609*y == 21. In point of fact, there are actually an infinite number of x and y combinations and which + * combination is returned is dependant upon which mode is in use. See + * {@link http://en.wikipedia.org/wiki/B%C3%A9zout%27s_identity Bezout's identity - Wikipedia} for more information. + * + * Here's an example: + * <code> + * <?php + * include('Math/BigInteger.php'); + * + * $a = new Math_BigInteger(693); + * $b = new Math_BigInteger(609); + * + * extract($a->extendedGCD($b)); + * + * echo $gcd->toString() . "\r\n"; // outputs 21 + * echo $a->toString() * $x->toString() + $b->toString() * $y->toString(); // outputs 21 + * ?> + * </code> + * + * @param Math_BigInteger $n + * @return Math_BigInteger + * @access public + * @internal Calculates the GCD using the binary xGCD algorithim described in + * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=19 HAC 14.61}. As the text above 14.61 notes, + * the more traditional algorithim requires "relatively costly multiple-precision divisions". + */ + function extendedGCD($n) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + extract(gmp_gcdext($this->value, $n->value)); + + return array( + 'gcd' => $this->_normalize(new Math_BigInteger($g)), + 'x' => $this->_normalize(new Math_BigInteger($s)), + 'y' => $this->_normalize(new Math_BigInteger($t)) + ); + case MATH_BIGINTEGER_MODE_BCMATH: + // it might be faster to use the binary xGCD algorithim here, as well, but (1) that algorithim works + // best when the base is a power of 2 and (2) i don't think it'd make much difference, anyway. as is, + // the basic extended euclidean algorithim is what we're using. + + $u = $this->value; + $v = $n->value; + + $a = '1'; + $b = '0'; + $c = '0'; + $d = '1'; + + while (bccomp($v, '0', 0) != 0) { + $q = bcdiv($u, $v, 0); + + $temp = $u; + $u = $v; + $v = bcsub($temp, bcmul($v, $q, 0), 0); + + $temp = $a; + $a = $c; + $c = bcsub($temp, bcmul($a, $q, 0), 0); + + $temp = $b; + $b = $d; + $d = bcsub($temp, bcmul($b, $q, 0), 0); + } + + return array( + 'gcd' => $this->_normalize(new Math_BigInteger($u)), + 'x' => $this->_normalize(new Math_BigInteger($a)), + 'y' => $this->_normalize(new Math_BigInteger($b)) + ); + } + + $y = $n->copy(); + $x = $this->copy(); + $g = new Math_BigInteger(); + $g->value = array(1); + + while ( !(($x->value[0] & 1)|| ($y->value[0] & 1)) ) { + $x->_rshift(1); + $y->_rshift(1); + $g->_lshift(1); + } + + $u = $x->copy(); + $v = $y->copy(); + + $a = new Math_BigInteger(); + $b = new Math_BigInteger(); + $c = new Math_BigInteger(); + $d = new Math_BigInteger(); + + $a->value = $d->value = $g->value = array(1); + $b->value = $c->value = array(); + + while ( !empty($u->value) ) { + while ( !($u->value[0] & 1) ) { + $u->_rshift(1); + if ( (!empty($a->value) && ($a->value[0] & 1)) || (!empty($b->value) && ($b->value[0] & 1)) ) { + $a = $a->add($y); + $b = $b->subtract($x); + } + $a->_rshift(1); + $b->_rshift(1); + } + + while ( !($v->value[0] & 1) ) { + $v->_rshift(1); + if ( (!empty($d->value) && ($d->value[0] & 1)) || (!empty($c->value) && ($c->value[0] & 1)) ) { + $c = $c->add($y); + $d = $d->subtract($x); + } + $c->_rshift(1); + $d->_rshift(1); + } + + if ($u->compare($v) >= 0) { + $u = $u->subtract($v); + $a = $a->subtract($c); + $b = $b->subtract($d); + } else { + $v = $v->subtract($u); + $c = $c->subtract($a); + $d = $d->subtract($b); + } + } + + return array( + 'gcd' => $this->_normalize($g->multiply($v)), + 'x' => $this->_normalize($c), + 'y' => $this->_normalize($d) + ); + } + + /** + * Calculates the greatest common divisor + * + * Say you have 693 and 609. The GCD is 21. + * + * Here's an example: + * <code> + * <?php + * include('Math/BigInteger.php'); + * + * $a = new Math_BigInteger(693); + * $b = new Math_BigInteger(609); + * + * $gcd = a->extendedGCD($b); + * + * echo $gcd->toString() . "\r\n"; // outputs 21 + * ?> + * </code> + * + * @param Math_BigInteger $n + * @return Math_BigInteger + * @access public + */ + function gcd($n) + { + extract($this->extendedGCD($n)); + return $gcd; + } + + /** + * Absolute value. + * + * @return Math_BigInteger + * @access public + */ + function abs() + { + $temp = new Math_BigInteger(); + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp->value = gmp_abs($this->value); + break; + case MATH_BIGINTEGER_MODE_BCMATH: + $temp->value = (bccomp($this->value, '0', 0) < 0) ? substr($this->value, 1) : $this->value; + break; + default: + $temp->value = $this->value; + } + + return $temp; + } + + /** + * Compares two numbers. + * + * Although one might think !$x->compare($y) means $x != $y, it, in fact, means the opposite. The reason for this is + * demonstrated thusly: + * + * $x > $y: $x->compare($y) > 0 + * $x < $y: $x->compare($y) < 0 + * $x == $y: $x->compare($y) == 0 + * + * Note how the same comparison operator is used. If you want to test for equality, use $x->equals($y). + * + * @param Math_BigInteger $y + * @return Integer < 0 if $this is less than $y; > 0 if $this is greater than $y, and 0 if they are equal. + * @access public + * @see equals() + * @internal Could return $this->subtract($x), but that's not as fast as what we do do. + */ + function compare($y) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + return gmp_cmp($this->value, $y->value); + case MATH_BIGINTEGER_MODE_BCMATH: + return bccomp($this->value, $y->value, 0); + } + + return $this->_compare($this->value, $this->is_negative, $y->value, $y->is_negative); + } + + /** + * Compares two numbers. + * + * @param Array $x_value + * @param Boolean $x_negative + * @param Array $y_value + * @param Boolean $y_negative + * @return Integer + * @see compare() + * @access private + */ + function _compare($x_value, $x_negative, $y_value, $y_negative) + { + if ( $x_negative != $y_negative ) { + return ( !$x_negative && $y_negative ) ? 1 : -1; + } + + $result = $x_negative ? -1 : 1; + + if ( count($x_value) != count($y_value) ) { + return ( count($x_value) > count($y_value) ) ? $result : -$result; + } + $size = max(count($x_value), count($y_value)); + + $x_value = array_pad($x_value, $size, 0); + $y_value = array_pad($y_value, $size, 0); + + for ($i = count($x_value) - 1; $i >= 0; --$i) { + if ($x_value[$i] != $y_value[$i]) { + return ( $x_value[$i] > $y_value[$i] ) ? $result : -$result; + } + } + + return 0; + } + + /** + * Tests the equality of two numbers. + * + * If you need to see if one number is greater than or less than another number, use Math_BigInteger::compare() + * + * @param Math_BigInteger $x + * @return Boolean + * @access public + * @see compare() + */ + function equals($x) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + return gmp_cmp($this->value, $x->value) == 0; + default: + return $this->value === $x->value && $this->is_negative == $x->is_negative; + } + } + + /** + * Set Precision + * + * Some bitwise operations give different results depending on the precision being used. Examples include left + * shift, not, and rotates. + * + * @param Integer $bits + * @access public + */ + function setPrecision($bits) + { + $this->precision = $bits; + if ( MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_BCMATH ) { + $this->bitmask = new Math_BigInteger(chr((1 << ($bits & 0x7)) - 1) . str_repeat(chr(0xFF), $bits >> 3), 256); + } else { + $this->bitmask = new Math_BigInteger(bcpow('2', $bits, 0)); + } + + $temp = $this->_normalize($this); + $this->value = $temp->value; + } + + /** + * Logical And + * + * @param Math_BigInteger $x + * @access public + * @internal Implemented per a request by Lluis Pamies i Juarez <lluis _a_ pamies.cat> + * @return Math_BigInteger + */ + function bitwise_and($x) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new Math_BigInteger(); + $temp->value = gmp_and($this->value, $x->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $left = $this->toBytes(); + $right = $x->toBytes(); + + $length = max(strlen($left), strlen($right)); + + $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); + $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); + + return $this->_normalize(new Math_BigInteger($left & $right, 256)); + } + + $result = $this->copy(); + + $length = min(count($x->value), count($this->value)); + + $result->value = array_slice($result->value, 0, $length); + + for ($i = 0; $i < $length; ++$i) { + $result->value[$i]&= $x->value[$i]; + } + + return $this->_normalize($result); + } + + /** + * Logical Or + * + * @param Math_BigInteger $x + * @access public + * @internal Implemented per a request by Lluis Pamies i Juarez <lluis _a_ pamies.cat> + * @return Math_BigInteger + */ + function bitwise_or($x) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new Math_BigInteger(); + $temp->value = gmp_or($this->value, $x->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $left = $this->toBytes(); + $right = $x->toBytes(); + + $length = max(strlen($left), strlen($right)); + + $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); + $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); + + return $this->_normalize(new Math_BigInteger($left | $right, 256)); + } + + $length = max(count($this->value), count($x->value)); + $result = $this->copy(); + $result->value = array_pad($result->value, $length, 0); + $x->value = array_pad($x->value, $length, 0); + + for ($i = 0; $i < $length; ++$i) { + $result->value[$i]|= $x->value[$i]; + } + + return $this->_normalize($result); + } + + /** + * Logical Exclusive-Or + * + * @param Math_BigInteger $x + * @access public + * @internal Implemented per a request by Lluis Pamies i Juarez <lluis _a_ pamies.cat> + * @return Math_BigInteger + */ + function bitwise_xor($x) + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + $temp = new Math_BigInteger(); + $temp->value = gmp_xor($this->value, $x->value); + + return $this->_normalize($temp); + case MATH_BIGINTEGER_MODE_BCMATH: + $left = $this->toBytes(); + $right = $x->toBytes(); + + $length = max(strlen($left), strlen($right)); + + $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); + $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); + + return $this->_normalize(new Math_BigInteger($left ^ $right, 256)); + } + + $length = max(count($this->value), count($x->value)); + $result = $this->copy(); + $result->value = array_pad($result->value, $length, 0); + $x->value = array_pad($x->value, $length, 0); + + for ($i = 0; $i < $length; ++$i) { + $result->value[$i]^= $x->value[$i]; + } + + return $this->_normalize($result); + } + + /** + * Logical Not + * + * @access public + * @internal Implemented per a request by Lluis Pamies i Juarez <lluis _a_ pamies.cat> + * @return Math_BigInteger + */ + function bitwise_not() + { + // calculuate "not" without regard to $this->precision + // (will always result in a smaller number. ie. ~1 isn't 1111 1110 - it's 0) + $temp = $this->toBytes(); + $pre_msb = decbin(ord($temp[0])); + $temp = ~$temp; + $msb = decbin(ord($temp[0])); + if (strlen($msb) == 8) { + $msb = substr($msb, strpos($msb, '0')); + } + $temp[0] = chr(bindec($msb)); + + // see if we need to add extra leading 1's + $current_bits = strlen($pre_msb) + 8 * strlen($temp) - 8; + $new_bits = $this->precision - $current_bits; + if ($new_bits <= 0) { + return $this->_normalize(new Math_BigInteger($temp, 256)); + } + + // generate as many leading 1's as we need to. + $leading_ones = chr((1 << ($new_bits & 0x7)) - 1) . str_repeat(chr(0xFF), $new_bits >> 3); + $this->_base256_lshift($leading_ones, $current_bits); + + $temp = str_pad($temp, ceil($this->bits / 8), chr(0), STR_PAD_LEFT); + + return $this->_normalize(new Math_BigInteger($leading_ones | $temp, 256)); + } + + /** + * Logical Right Shift + * + * Shifts BigInteger's by $shift bits, effectively dividing by 2**$shift. + * + * @param Integer $shift + * @return Math_BigInteger + * @access public + * @internal The only version that yields any speed increases is the internal version. + */ + function bitwise_rightShift($shift) + { + $temp = new Math_BigInteger(); + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + static $two; + + if (!isset($two)) { + $two = gmp_init('2'); + } + + $temp->value = gmp_div_q($this->value, gmp_pow($two, $shift)); + + break; + case MATH_BIGINTEGER_MODE_BCMATH: + $temp->value = bcdiv($this->value, bcpow('2', $shift, 0), 0); + + break; + default: // could just replace _lshift with this, but then all _lshift() calls would need to be rewritten + // and I don't want to do that... + $temp->value = $this->value; + $temp->_rshift($shift); + } + + return $this->_normalize($temp); + } + + /** + * Logical Left Shift + * + * Shifts BigInteger's by $shift bits, effectively multiplying by 2**$shift. + * + * @param Integer $shift + * @return Math_BigInteger + * @access public + * @internal The only version that yields any speed increases is the internal version. + */ + function bitwise_leftShift($shift) + { + $temp = new Math_BigInteger(); + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + static $two; + + if (!isset($two)) { + $two = gmp_init('2'); + } + + $temp->value = gmp_mul($this->value, gmp_pow($two, $shift)); + + break; + case MATH_BIGINTEGER_MODE_BCMATH: + $temp->value = bcmul($this->value, bcpow('2', $shift, 0), 0); + + break; + default: // could just replace _rshift with this, but then all _lshift() calls would need to be rewritten + // and I don't want to do that... + $temp->value = $this->value; + $temp->_lshift($shift); + } + + return $this->_normalize($temp); + } + + /** + * Logical Left Rotate + * + * Instead of the top x bits being dropped they're appended to the shifted bit string. + * + * @param Integer $shift + * @return Math_BigInteger + * @access public + */ + function bitwise_leftRotate($shift) + { + $bits = $this->toBytes(); + + if ($this->precision > 0) { + $precision = $this->precision; + if ( MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_BCMATH ) { + $mask = $this->bitmask->subtract(new Math_BigInteger(1)); + $mask = $mask->toBytes(); + } else { + $mask = $this->bitmask->toBytes(); + } + } else { + $temp = ord($bits[0]); + for ($i = 0; $temp >> $i; ++$i); + $precision = 8 * strlen($bits) - 8 + $i; + $mask = chr((1 << ($precision & 0x7)) - 1) . str_repeat(chr(0xFF), $precision >> 3); + } + + if ($shift < 0) { + $shift+= $precision; + } + $shift%= $precision; + + if (!$shift) { + return $this->copy(); + } + + $left = $this->bitwise_leftShift($shift); + $left = $left->bitwise_and(new Math_BigInteger($mask, 256)); + $right = $this->bitwise_rightShift($precision - $shift); + $result = MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_BCMATH ? $left->bitwise_or($right) : $left->add($right); + return $this->_normalize($result); + } + + /** + * Logical Right Rotate + * + * Instead of the bottom x bits being dropped they're prepended to the shifted bit string. + * + * @param Integer $shift + * @return Math_BigInteger + * @access public + */ + function bitwise_rightRotate($shift) + { + return $this->bitwise_leftRotate(-$shift); + } + + /** + * Set random number generator function + * + * This function is deprecated. + * + * @param String $generator + * @access public + */ + function setRandomGenerator($generator) + { + } + + /** + * Generate a random number + * + * @param optional Integer $min + * @param optional Integer $max + * @return Math_BigInteger + * @access public + */ + function random($min = false, $max = false) + { + if ($min === false) { + $min = new Math_BigInteger(0); + } + + if ($max === false) { + $max = new Math_BigInteger(0x7FFFFFFF); + } + + $compare = $max->compare($min); + + if (!$compare) { + return $this->_normalize($min); + } else if ($compare < 0) { + // if $min is bigger then $max, swap $min and $max + $temp = $max; + $max = $min; + $min = $temp; + } + + $max = $max->subtract($min); + $max = ltrim($max->toBytes(), chr(0)); + $size = strlen($max) - 1; + + $crypt_random = function_exists('crypt_random_string') || (!class_exists('Crypt_Random') && function_exists('crypt_random_string')); + if ($crypt_random) { + $random = crypt_random_string($size); + } else { + $random = ''; + + if ($size & 1) { + $random.= chr(mt_rand(0, 255)); + } + + $blocks = $size >> 1; + for ($i = 0; $i < $blocks; ++$i) { + // mt_rand(-2147483648, 0x7FFFFFFF) always produces -2147483648 on some systems + $random.= pack('n', mt_rand(0, 0xFFFF)); + } + } + + $fragment = new Math_BigInteger($random, 256); + $leading = $fragment->compare(new Math_BigInteger(substr($max, 1), 256)) > 0 ? + ord($max[0]) - 1 : ord($max[0]); + + if (!$crypt_random) { + $msb = chr(mt_rand(0, $leading)); + } else { + $cutoff = floor(0xFF / $leading) * $leading; + while (true) { + $msb = ord(crypt_random_string(1)); + if ($msb <= $cutoff) { + $msb%= $leading; + break; + } + } + $msb = chr($msb); + } + + $random = new Math_BigInteger($msb . $random, 256); + + return $this->_normalize($random->add($min)); + } + + /** + * Generate a random prime number. + * + * If there's not a prime within the given range, false will be returned. If more than $timeout seconds have elapsed, + * give up and return false. + * + * @param optional Integer $min + * @param optional Integer $max + * @param optional Integer $timeout + * @return Math_BigInteger + * @access public + * @internal See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap4.pdf#page=15 HAC 4.44}. + */ + function randomPrime($min = false, $max = false, $timeout = false) + { + if ($min === false) { + $min = new Math_BigInteger(0); + } + + if ($max === false) { + $max = new Math_BigInteger(0x7FFFFFFF); + } + + $compare = $max->compare($min); + + if (!$compare) { + return $min->isPrime() ? $min : false; + } else if ($compare < 0) { + // if $min is bigger then $max, swap $min and $max + $temp = $max; + $max = $min; + $min = $temp; + } + + static $one, $two; + if (!isset($one)) { + $one = new Math_BigInteger(1); + $two = new Math_BigInteger(2); + } + + $start = time(); + + $x = $this->random($min, $max); + + // gmp_nextprime() requires PHP 5 >= 5.2.0 per <http://php.net/gmp-nextprime>. + if ( MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_GMP && function_exists('gmp_nextprime') ) { + $p->value = gmp_nextprime($x->value); + + if ($p->compare($max) <= 0) { + return $p; + } + + if (!$min->equals($x)) { + $x = $x->subtract($one); + } + + return $x->randomPrime($min, $x); + } + + if ($x->equals($two)) { + return $x; + } + + $x->_make_odd(); + if ($x->compare($max) > 0) { + // if $x > $max then $max is even and if $min == $max then no prime number exists between the specified range + if ($min->equals($max)) { + return false; + } + $x = $min->copy(); + $x->_make_odd(); + } + + $initial_x = $x->copy(); + + while (true) { + if ($timeout !== false && time() - $start > $timeout) { + return false; + } + + if ($x->isPrime()) { + return $x; + } + + $x = $x->add($two); + + if ($x->compare($max) > 0) { + $x = $min->copy(); + if ($x->equals($two)) { + return $x; + } + $x->_make_odd(); + } + + if ($x->equals($initial_x)) { + return false; + } + } + } + + /** + * Make the current number odd + * + * If the current number is odd it'll be unchanged. If it's even, one will be added to it. + * + * @see randomPrime() + * @access private + */ + function _make_odd() + { + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + gmp_setbit($this->value, 0); + break; + case MATH_BIGINTEGER_MODE_BCMATH: + if ($this->value[strlen($this->value) - 1] % 2 == 0) { + $this->value = bcadd($this->value, '1'); + } + break; + default: + $this->value[0] |= 1; + } + } + + /** + * Checks a numer to see if it's prime + * + * Assuming the $t parameter is not set, this function has an error rate of 2**-80. The main motivation for the + * $t parameter is distributability. Math_BigInteger::randomPrime() can be distributed accross multiple pageloads + * on a website instead of just one. + * + * @param optional Integer $t + * @return Boolean + * @access public + * @internal Uses the + * {@link http://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test Miller-Rabin primality test}. See + * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap4.pdf#page=8 HAC 4.24}. + */ + function isPrime($t = false) + { + $length = strlen($this->toBytes()); + + if (!$t) { + // see HAC 4.49 "Note (controlling the error probability)" + if ($length >= 163) { $t = 2; } // floor(1300 / 8) + else if ($length >= 106) { $t = 3; } // floor( 850 / 8) + else if ($length >= 81 ) { $t = 4; } // floor( 650 / 8) + else if ($length >= 68 ) { $t = 5; } // floor( 550 / 8) + else if ($length >= 56 ) { $t = 6; } // floor( 450 / 8) + else if ($length >= 50 ) { $t = 7; } // floor( 400 / 8) + else if ($length >= 43 ) { $t = 8; } // floor( 350 / 8) + else if ($length >= 37 ) { $t = 9; } // floor( 300 / 8) + else if ($length >= 31 ) { $t = 12; } // floor( 250 / 8) + else if ($length >= 25 ) { $t = 15; } // floor( 200 / 8) + else if ($length >= 18 ) { $t = 18; } // floor( 150 / 8) + else { $t = 27; } + } + + // ie. gmp_testbit($this, 0) + // ie. isEven() or !isOdd() + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + return gmp_prob_prime($this->value, $t) != 0; + case MATH_BIGINTEGER_MODE_BCMATH: + if ($this->value === '2') { + return true; + } + if ($this->value[strlen($this->value) - 1] % 2 == 0) { + return false; + } + break; + default: + if ($this->value == array(2)) { + return true; + } + if (~$this->value[0] & 1) { + return false; + } + } + + static $primes, $zero, $one, $two; + + if (!isset($primes)) { + $primes = array( + 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, + 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, + 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, + 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, + 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, + 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, + 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, + 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, + 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, + 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, + 953, 967, 971, 977, 983, 991, 997 + ); + + if ( MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_INTERNAL ) { + for ($i = 0; $i < count($primes); ++$i) { + $primes[$i] = new Math_BigInteger($primes[$i]); + } + } + + $zero = new Math_BigInteger(); + $one = new Math_BigInteger(1); + $two = new Math_BigInteger(2); + } + + if ($this->equals($one)) { + return false; + } + + // see HAC 4.4.1 "Random search for probable primes" + if ( MATH_BIGINTEGER_MODE != MATH_BIGINTEGER_MODE_INTERNAL ) { + foreach ($primes as $prime) { + list(, $r) = $this->divide($prime); + if ($r->equals($zero)) { + return $this->equals($prime); + } + } + } else { + $value = $this->value; + foreach ($primes as $prime) { + list(, $r) = $this->_divide_digit($value, $prime); + if (!$r) { + return count($value) == 1 && $value[0] == $prime; + } + } + } + + $n = $this->copy(); + $n_1 = $n->subtract($one); + $n_2 = $n->subtract($two); + + $r = $n_1->copy(); + $r_value = $r->value; + // ie. $s = gmp_scan1($n, 0) and $r = gmp_div_q($n, gmp_pow(gmp_init('2'), $s)); + if ( MATH_BIGINTEGER_MODE == MATH_BIGINTEGER_MODE_BCMATH ) { + $s = 0; + // if $n was 1, $r would be 0 and this would be an infinite loop, hence our $this->equals($one) check earlier + while ($r->value[strlen($r->value) - 1] % 2 == 0) { + $r->value = bcdiv($r->value, '2', 0); + ++$s; + } + } else { + for ($i = 0, $r_length = count($r_value); $i < $r_length; ++$i) { + $temp = ~$r_value[$i] & 0xFFFFFF; + for ($j = 1; ($temp >> $j) & 1; ++$j); + if ($j != 25) { + break; + } + } + $s = 26 * $i + $j - 1; + $r->_rshift($s); + } + + for ($i = 0; $i < $t; ++$i) { + $a = $this->random($two, $n_2); + $y = $a->modPow($r, $n); + + if (!$y->equals($one) && !$y->equals($n_1)) { + for ($j = 1; $j < $s && !$y->equals($n_1); ++$j) { + $y = $y->modPow($two, $n); + if ($y->equals($one)) { + return false; + } + } + + if (!$y->equals($n_1)) { + return false; + } + } + } + return true; + } + + /** + * Logical Left Shift + * + * Shifts BigInteger's by $shift bits. + * + * @param Integer $shift + * @access private + */ + function _lshift($shift) + { + if ( $shift == 0 ) { + return; + } + + $num_digits = (int) ($shift / MATH_BIGINTEGER_BASE); + $shift %= MATH_BIGINTEGER_BASE; + $shift = 1 << $shift; + + $carry = 0; + + for ($i = 0; $i < count($this->value); ++$i) { + $temp = $this->value[$i] * $shift + $carry; + $carry = (int) ($temp / MATH_BIGINTEGER_BASE_FULL); + $this->value[$i] = (int) ($temp - $carry * MATH_BIGINTEGER_BASE_FULL); + } + + if ( $carry ) { + $this->value[] = $carry; + } + + while ($num_digits--) { + array_unshift($this->value, 0); + } + } + + /** + * Logical Right Shift + * + * Shifts BigInteger's by $shift bits. + * + * @param Integer $shift + * @access private + */ + function _rshift($shift) + { + if ($shift == 0) { + return; + } + + $num_digits = (int) ($shift / MATH_BIGINTEGER_BASE); + $shift %= MATH_BIGINTEGER_BASE; + $carry_shift = MATH_BIGINTEGER_BASE - $shift; + $carry_mask = (1 << $shift) - 1; + + if ( $num_digits ) { + $this->value = array_slice($this->value, $num_digits); + } + + $carry = 0; + + for ($i = count($this->value) - 1; $i >= 0; --$i) { + $temp = $this->value[$i] >> $shift | $carry; + $carry = ($this->value[$i] & $carry_mask) << $carry_shift; + $this->value[$i] = $temp; + } + + $this->value = $this->_trim($this->value); + } + + /** + * Normalize + * + * Removes leading zeros and truncates (if necessary) to maintain the appropriate precision + * + * @param Math_BigInteger + * @return Math_BigInteger + * @see _trim() + * @access private + */ + function _normalize($result) + { + $result->precision = $this->precision; + $result->bitmask = $this->bitmask; + + switch ( MATH_BIGINTEGER_MODE ) { + case MATH_BIGINTEGER_MODE_GMP: + if (!empty($result->bitmask->value)) { + $result->value = gmp_and($result->value, $result->bitmask->value); + } + + return $result; + case MATH_BIGINTEGER_MODE_BCMATH: + if (!empty($result->bitmask->value)) { + $result->value = bcmod($result->value, $result->bitmask->value); + } + + return $result; + } + + $value = &$result->value; + + if ( !count($value) ) { + return $result; + } + + $value = $this->_trim($value); + + if (!empty($result->bitmask->value)) { + $length = min(count($value), count($this->bitmask->value)); + $value = array_slice($value, 0, $length); + + for ($i = 0; $i < $length; ++$i) { + $value[$i] = $value[$i] & $this->bitmask->value[$i]; + } + } + + return $result; + } + + /** + * Trim + * + * Removes leading zeros + * + * @param Array $value + * @return Math_BigInteger + * @access private + */ + function _trim($value) + { + for ($i = count($value) - 1; $i >= 0; --$i) { + if ( $value[$i] ) { + break; + } + unset($value[$i]); + } + + return $value; + } + + /** + * Array Repeat + * + * @param $input Array + * @param $multiplier mixed + * @return Array + * @access private + */ + function _array_repeat($input, $multiplier) + { + return ($multiplier) ? array_fill(0, $multiplier, $input) : array(); + } + + /** + * Logical Left Shift + * + * Shifts binary strings $shift bits, essentially multiplying by 2**$shift. + * + * @param $x String + * @param $shift Integer + * @return String + * @access private + */ + function _base256_lshift(&$x, $shift) + { + if ($shift == 0) { + return; + } + + $num_bytes = $shift >> 3; // eg. floor($shift/8) + $shift &= 7; // eg. $shift % 8 + + $carry = 0; + for ($i = strlen($x) - 1; $i >= 0; --$i) { + $temp = ord($x[$i]) << $shift | $carry; + $x[$i] = chr($temp); + $carry = $temp >> 8; + } + $carry = ($carry != 0) ? chr($carry) : ''; + $x = $carry . $x . str_repeat(chr(0), $num_bytes); + } + + /** + * Logical Right Shift + * + * Shifts binary strings $shift bits, essentially dividing by 2**$shift and returning the remainder. + * + * @param $x String + * @param $shift Integer + * @return String + * @access private + */ + function _base256_rshift(&$x, $shift) + { + if ($shift == 0) { + $x = ltrim($x, chr(0)); + return ''; + } + + $num_bytes = $shift >> 3; // eg. floor($shift/8) + $shift &= 7; // eg. $shift % 8 + + $remainder = ''; + if ($num_bytes) { + $start = $num_bytes > strlen($x) ? -strlen($x) : -$num_bytes; + $remainder = substr($x, $start); + $x = substr($x, 0, -$num_bytes); + } + + $carry = 0; + $carry_shift = 8 - $shift; + for ($i = 0; $i < strlen($x); ++$i) { + $temp = (ord($x[$i]) >> $shift) | $carry; + $carry = (ord($x[$i]) << $carry_shift) & 0xFF; + $x[$i] = chr($temp); + } + $x = ltrim($x, chr(0)); + + $remainder = chr($carry >> $carry_shift) . $remainder; + + return ltrim($remainder, chr(0)); + } + + // one quirk about how the following functions are implemented is that PHP defines N to be an unsigned long + // at 32-bits, while java's longs are 64-bits. + + /** + * Converts 32-bit integers to bytes. + * + * @param Integer $x + * @return String + * @access private + */ + function _int2bytes($x) + { + return ltrim(pack('N', $x), chr(0)); + } + + /** + * Converts bytes to 32-bit integers + * + * @param String $x + * @return Integer + * @access private + */ + function _bytes2int($x) + { + $temp = unpack('Nint', str_pad($x, 4, chr(0), STR_PAD_LEFT)); + return $temp['int']; + } + + /** + * DER-encode an integer + * + * The ability to DER-encode integers is needed to create RSA public keys for use with OpenSSL + * + * @see modPow() + * @access private + * @param Integer $length + * @return String + */ + function _encodeASN1Length($length) + { + if ($length <= 0x7F) { + return chr($length); + } + + $temp = ltrim(pack('N', $length), chr(0)); + return pack('Ca*', 0x80 | strlen($temp), $temp); + } +} diff --git a/include/staff/apikeys.inc.php b/include/staff/apikeys.inc.php index 90b4e31eef10af37b71ba2ebef6edffc9dc63d7b..9f4375ca7f2f5a3979f9cc7bfae1e196e3892b46 100644 --- a/include/staff/apikeys.inc.php +++ b/include/staff/apikeys.inc.php @@ -39,7 +39,7 @@ else ?> -<div style="width:700;padding-top:5px; float:left;"> +<div style="width:700px;padding-top:5px; float:left;"> <h2>API Keys</h2> </div> <div style="float:right;text-align:right;padding-top:5px;padding-right:5px;"> diff --git a/include/staff/cannedresponses.inc.php b/include/staff/cannedresponses.inc.php index 1a7316aa961b27f4a081c3f1d146ba5077ace76f..ef58ffda4dbe51a7c206dcbf0c0c6392430fd3f0 100644 --- a/include/staff/cannedresponses.inc.php +++ b/include/staff/cannedresponses.inc.php @@ -46,7 +46,7 @@ else $showing='No premade responses found!'; ?> -<div style="width:700;padding-top:5px; float:left;"> +<div style="width:700px;padding-top:5px; float:left;"> <h2>Canned Responses</h2> </div> <div style="float:right;text-align:right;padding-top:5px;padding-right:5px;"> diff --git a/include/staff/categories.inc.php b/include/staff/categories.inc.php index 293388d19485a03f63e71a167e466ac6015ce0a2..ca9d67b5005b325efd9c4004199e6a37e232d9d0 100644 --- a/include/staff/categories.inc.php +++ b/include/staff/categories.inc.php @@ -40,7 +40,7 @@ else $showing='No FAQ categories found!'; ?> -<div style="width:700;padding-top:5px; float:left;"> +<div style="width:700px;padding-top:5px; float:left;"> <h2>FAQ Categories</h2> </div> <div style="float:right;text-align:right;padding-top:5px;padding-right:5px;"> diff --git a/include/staff/category.inc.php b/include/staff/category.inc.php index c682219b0331e089a22a6b38054dd83997a38962..f5a153dbc7f312cb68ee9a87cd3e1e4604bc9081 100644 --- a/include/staff/category.inc.php +++ b/include/staff/category.inc.php @@ -3,7 +3,7 @@ if(!defined('OSTSCPINC') || !$thisstaff || !$thisstaff->canManageFAQ()) die('Acc $info=array(); $qstr=''; if($category && $_REQUEST['a']!='add'){ - $title='Update Category :'.$category->getName(); + $title='Update Category: '.$category->getName(); $action='update'; $submit_text='Save Changes'; $info=$category->getHashtable(); diff --git a/include/staff/department.inc.php b/include/staff/department.inc.php index 00a65ebc54cfc1345d55ea365e00f5ff9aaa12ca..b43806b0b99bd65ec8be77b68b2db7e803ab410b 100644 --- a/include/staff/department.inc.php +++ b/include/staff/department.inc.php @@ -88,7 +88,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <select name="tpl_id"> <option value="0">— System default —</option> <?php - $sql='SELECT tpl_id,name FROM '.EMAIL_TEMPLATE_TABLE.' tpl WHERE isactive=1 ORDER by name'; + $sql='SELECT tpl_id,name FROM '.EMAIL_TEMPLATE_GRP_TABLE.' tpl WHERE isactive=1 ORDER by name'; if(($res=db_query($sql)) && db_num_rows($res)){ while(list($id,$name)=db_fetch_row($res)){ $selected=($info['tpl_id'] && $id==$info['tpl_id'])?'selected="selected"':''; @@ -144,7 +144,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <span class="error"> <?php echo $errors['manager_id']; ?></span> </td> </tr> - <?php + <?php } ?> <tr> @@ -158,7 +158,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); </tr> <tr> <th colspan="2"> - <em><strong>Auto Response Settings</strong>: Overwrite global auto-response settings for tickets routed to the Dept.</em> + <em><strong>Auto Response Settings</strong>: Override global auto-response settings for tickets routed to the Dept.</em> </th> </tr> <tr> @@ -167,7 +167,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); </td> <td> <input type="checkbox" name="ticket_auto_response" value="0" <?php echo !$info['ticket_auto_response']?'checked="checked"':''; ?> > - + <strong>Disable</strong> new ticket auto-response for this Dept. </td> </tr> @@ -192,7 +192,9 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); $sql='SELECT email_id,email,name FROM '.EMAIL_TABLE.' email ORDER by name'; if(($res=db_query($sql)) && db_num_rows($res)){ while(list($id,$email,$name)=db_fetch_row($res)){ - $selected=($info['email_id'] && $id==$info['email_id'])?'selected="selected"':''; + $selected = (isset($info['autoresp_email_id']) + && $id == $info['autoresp_email_id']) + ? 'selected="selected"' : ''; if($name) $email=Format::htmlchars("$name <$email>"); echo sprintf('<option value="%d" %s>%s</option>',$id,$selected,$email); @@ -217,7 +219,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); .' ORDER BY group_name'; if(($res=db_query($sql)) && db_num_rows($res)){ while(list($id, $name, $members) = db_fetch_row($res)) { - if($members>0) + if($members>0) $members=sprintf('<a href="staff.php?a=filter&gid=%d">%d</a>', $id, $members); $ck=($info['groups'] && in_array($id,$info['groups']))?'checked="checked"':''; diff --git a/include/staff/departments.inc.php b/include/staff/departments.inc.php index 64b668463e38ca51b39ba9146b8242493687c175..f78f4b3f7ccccf4a0278669c172f4bc569fd767f 100644 --- a/include/staff/departments.inc.php +++ b/include/staff/departments.inc.php @@ -40,7 +40,7 @@ else $showing='No departments found!'; ?> -<div style="width:700;padding-top:5px; float:left;"> +<div style="width:700px;padding-top:5px; float:left;"> <h2>Departments</h2> </div> <div style="float:right;text-align:right;padding-top:5px;padding-right:5px;"> diff --git a/include/staff/directory.inc.php b/include/staff/directory.inc.php index 1d44df3ef2de432e915156f91670a899a8cc4be0..d1c07f439dfc91a1612bb408e03eb88e9a48b928 100644 --- a/include/staff/directory.inc.php +++ b/include/staff/directory.inc.php @@ -61,7 +61,7 @@ $query="$select $from $where GROUP BY staff.staff_id ORDER BY $order_by LIMIT ". //echo $query; ?> <h2>Staff Members</h2> -<div style="width:700; float:left;"> +<div style="width:700px; float:left;"> <form action="directory.php" method="GET" name="filter"> <input type="text" name="q" value="<?php echo Format::htmlchars($_REQUEST['q']); ?>" > <select name="did" id="did"> diff --git a/include/staff/email.inc.php b/include/staff/email.inc.php index 5e2935e06a02157c4cc7f62e45a38eb4af87326d..58ecb35a8cd6b1cad83377fdfbf68a6f8541fbc9 100644 --- a/include/staff/email.inc.php +++ b/include/staff/email.inc.php @@ -40,7 +40,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <tr> <th colspan="2"> <h4><?php echo $title; ?></h4> - <em><strong>Email Information</strong>: Login details are optional BUT required when IMAP/POP or SMTP are enabled.</em> + <em><strong>Email Information & Settings</strong></em> </th> </tr> </thead> @@ -65,7 +65,62 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); </tr> <tr> <td width="180"> - Login Username + New Ticket Priority + </td> + <td> + <select name="priority_id"> + <option value="">— Select Priority —</option> + <?php + $sql='SELECT priority_id,priority_desc FROM '.PRIORITY_TABLE.' pri ORDER by priority_urgency DESC'; + if(($res=db_query($sql)) && db_num_rows($res)){ + while(list($id,$name)=db_fetch_row($res)){ + $selected=($info['priority_id'] && $id==$info['priority_id'])?'selected="selected"':''; + echo sprintf('<option value="%d" %s>%s</option>',$id,$selected,$name); + } + } + ?> + </select> + <span class="error"><?php echo $errors['priority_id']; ?></span> + </td> + </tr> + <tr> + <td width="180"> + New Ticket Dept. + </td> + <td> + <select name="dept_id"> + <option value="">— Select Department —</option> + <?php + $sql='SELECT dept_id,dept_name FROM '.DEPT_TABLE.' dept ORDER by dept_name'; + if(($res=db_query($sql)) && db_num_rows($res)){ + while(list($id,$name)=db_fetch_row($res)){ + $selected=($info['dept_id'] && $id==$info['dept_id'])?'selected="selected"':''; + echo sprintf('<option value="%d" %s>%s</option>',$id,$selected,$name); + } + } + ?> + </select> + <span class="error"><?php echo $errors['dept_id']; ?></span> + </td> + </tr> + <tr> + <td width="180"> + Auto-response + </td> + <td> + <input type="checkbox" name="noautoresp" value="1" <?php echo $info['noautoresp']?'checked="checked"':''; ?> > + <strong>Disable</strong> new ticket auto-response for this + email. Override global and dept. settings. + </td> + </tr> + <tr> + <th colspan="2"> + <em><strong>Login Information:</strong>: Optional BUT required when IMAP/POP or SMTP (with auth.) are enabled.</em> + </th> + </tr> + <tr> + <td width="180"> + Username </td> <td> <input type="text" size="35" name="userid" value="<?php echo $info['userid']; ?>"> @@ -74,7 +129,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); </tr> <tr> <td width="180"> - Login Password + Password </td> <td> <input type="password" size="35" name="passwd" value="<?php echo $info['passwd']; ?>"> @@ -137,55 +192,6 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <font class="error"> <?php echo $errors['mail_fetchmax']; ?></font> </td> </tr> - <tr> - <td width="180"> - New Ticket Priority: - </td> - <td> - <select name="priority_id"> - <option value="">— Select Priority —</option> - <?php - $sql='SELECT priority_id,priority_desc FROM '.PRIORITY_TABLE.' pri ORDER by priority_urgency DESC'; - if(($res=db_query($sql)) && db_num_rows($res)){ - while(list($id,$name)=db_fetch_row($res)){ - $selected=($info['priority_id'] && $id==$info['priority_id'])?'selected="selected"':''; - echo sprintf('<option value="%d" %s>%s</option>',$id,$selected,$name); - } - } - ?> - </select> - <span class="error"><?php echo $errors['priority_id']; ?></span> - </td> - </tr> - <tr> - <td width="180"> - New Ticket Dept. - </td> - <td> - <select name="dept_id"> - <option value="">— Select Department —</option> - <?php - $sql='SELECT dept_id,dept_name FROM '.DEPT_TABLE.' dept ORDER by dept_name'; - if(($res=db_query($sql)) && db_num_rows($res)){ - while(list($id,$name)=db_fetch_row($res)){ - $selected=($info['dept_id'] && $id==$info['dept_id'])?'selected="selected"':''; - echo sprintf('<option value="%d" %s>%s</option>',$id,$selected,$name); - } - } - ?> - </select> - <span class="error"><?php echo $errors['dept_id']; ?></span> - </td> - </tr> - <tr> - <td width="180"> - Auto-response - </td> - <td> - <input type="checkbox" name="noautoresp" value="1" <?php echo $info['noautoresp']?'checked="checked"':''; ?> > - <strong>Disable</strong> new ticket auto-response for this email. Overwrite global and dept. settings. - </td> - </tr> <tr><td valign="top">Fetched Emails</td> <td> <input type="radio" name="postfetch" value="archive" <?php echo ($info['postfetch']=='archive')? 'checked="checked"': ''; ?> > diff --git a/include/staff/emails.inc.php b/include/staff/emails.inc.php index e5f035d1c254656d112879bd4753aaba7ba0908e..57bc48ff838c92a87fa62cc20eb59d163e21a821 100644 --- a/include/staff/emails.inc.php +++ b/include/staff/emails.inc.php @@ -42,7 +42,7 @@ else $showing='No emails found!'; ?> -<div style="width:700;padding-top:5px; float:left;"> +<div style="width:700px;padding-top:5px; float:left;"> <h2>Email Addresses</h2> </div> <div style="float:right;text-align:right;padding-top:5px;padding-right:5px;"> diff --git a/include/staff/faq-category.inc.php b/include/staff/faq-category.inc.php index e7013192e995738c2f9be4f249c3292acac9ab53..f12ed8ae8f3d4c90b0245235525ce4160b6cd836 100644 --- a/include/staff/faq-category.inc.php +++ b/include/staff/faq-category.inc.php @@ -2,7 +2,7 @@ if(!defined('OSTSTAFFINC') || !$category || !$thisstaff) die('Access Denied'); ?> -<div style="width:700;padding-top:10px; float:left;"> +<div style="width:700px;padding-top:10px; float:left;"> <h2>Frequently Asked Questions</h2> </div> <div style="float:right;text-align:right;padding-top:5px;padding-right:5px;"> </div> diff --git a/include/staff/faq-view.inc.php b/include/staff/faq-view.inc.php index ee7d743e4044d93ea18971e055b842985f6f656b..0882197ecac5e223803a0284d08a6c9d5ab0bd55 100644 --- a/include/staff/faq-view.inc.php +++ b/include/staff/faq-view.inc.php @@ -10,7 +10,7 @@ $category=$faq->getCategory(); » <a href="kb.php?cid=<?php echo $category->getId(); ?>"><?php echo $category->getName(); ?></a> <span class="faded">(<?php echo $category->isPublic()?'Public':'Internal'; ?>)</span> </div> -<div style="width:700;padding-top:2px; float:left;"> +<div style="width:700px;padding-top:2px; float:left;"> <strong style="font-size:16px;"><?php echo $faq->getQuestion() ?></strong> <span class="faded"><?php echo $faq->isPublished()?'(Published)':''; ?></span> </div> <div style="float:right;text-align:right;padding-top:5px;padding-right:5px;"> diff --git a/include/staff/filter.inc.php b/include/staff/filter.inc.php index ef1081422c941d1662dbbc3cfe83541a4858a45b..d82c1ca85d2e2fd0998be5154a9de778dc996eeb 100644 --- a/include/staff/filter.inc.php +++ b/include/staff/filter.inc.php @@ -121,7 +121,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); for($i=1; $i<=$n; $i++){ ?> <tr id="r<?php echo $i; ?>"> <td colspan="2"> - <div style="width:700; float:left;"> + <div style="width:700px; float:left;"> <select name="rule_w<?php echo $i; ?>"> <option value="">— Select One ‐</option> <?php @@ -157,7 +157,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); } ?> <tr> <th colspan="2"> - <em><strong>Filter Actions</strong>: Can be overwriten by other filters depending on processing order. </em> + <em><strong>Filter Actions</strong>: Can be overridden by other filters depending on processing order. </em> </th> </tr> <tr> @@ -184,7 +184,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); </td> <td> <input type="checkbox" name="disable_autoresponder" value="1" <?php echo $info['disable_autoresponder']?'checked="checked"':''; ?> > - <strong>Disable</strong> auto-response. <em>(Overwrites Dept. settings)</em> + <strong>Disable</strong> auto-response. <em>(Override Dept. settings)</em> </td> </tr> <tr> @@ -249,7 +249,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); ?> </select> <span class="error">* <?php echo $errors['priority_id']; ?></span> - <em>(Overwrites department's priority)</em> + <em>(Overrides department's priority)</em> </td> </tr> <tr> @@ -269,7 +269,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); ?> </select> <span class="error"> <?php echo $errors['sla_id']; ?></span> - <em>(Overwrites department's SLA)</em> + <em>(Overrides department's SLA)</em> </td> </tr> <tr> diff --git a/include/staff/filters.inc.php b/include/staff/filters.inc.php index 106f80586a0fe783546c281da38b88b6ba5327d4..1f39697189d11b028b345a87cb3e7b58f39b9f58 100644 --- a/include/staff/filters.inc.php +++ b/include/staff/filters.inc.php @@ -43,7 +43,7 @@ else ?> -<div style="width:700;padding-top:5px; float:left;"> +<div style="width:700px;padding-top:5px; float:left;"> <h2>Ticket Filters</h2> </div> <div style="float:right;text-align:right;padding-top:5px;padding-right:5px;"> diff --git a/include/staff/groups.inc.php b/include/staff/groups.inc.php index 64ea1a5b92ef8fff29238587182675a15c83b191..a100647b00ee88699253aa38186efd1604129a9d 100644 --- a/include/staff/groups.inc.php +++ b/include/staff/groups.inc.php @@ -39,7 +39,7 @@ else $showing='No groups found!'; ?> -<div style="width:700;padding-top:5px; float:left;"> +<div style="width:700px;padding-top:5px; float:left;"> <h2>User Groups</h2> </div> <div style="float:right;text-align:right;padding-top:5px;padding-right:5px;"> diff --git a/include/staff/header.inc.php b/include/staff/header.inc.php index 1bb03fe07463a7eb25b913699fd1e88fd959c69b..2e0fe9e52e7a7626670451dd50b0093a5878aaf9 100644 --- a/include/staff/header.inc.php +++ b/include/staff/header.inc.php @@ -100,4 +100,3 @@ <?php }elseif($warn) { ?> <div id="msg_warning"><?php echo $warn; ?></div> <?php } ?> - diff --git a/include/staff/helptopic.inc.php b/include/staff/helptopic.inc.php index e5dac76027006cdbbd6631a66c3df119c758d292..5dafc452d1d05ac9531383ecc7e7a43d6939d65d 100644 --- a/include/staff/helptopic.inc.php +++ b/include/staff/helptopic.inc.php @@ -146,7 +146,27 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); ?> </select> <span class="error"> <?php echo $errors['sla_id']; ?></span> - <em>(Overwrites department's SLA)</em> + <em>(Overrides department's SLA)</em> + </td> + </tr> + <tr> + <td width="180">Thank-you Page:</td> + <td> + <select name="page_id"> + <option value="">— System Default —</option> + <?php + if(($pages = Page::getActiveThankYouPages())) { + foreach($pages as $page) { + if(strcasecmp($page->getType(), 'thank-you')) continue; + echo sprintf('<option value="%d" %s>%s</option>', + $page->getId(), + ($info['page_id']==$page->getId())?'selected="selected"':'', + $page->getName()); + } + } + ?> + </select> <font class="error"><?php echo $errors['page_id']; ?></font> + <em>(Overrides global setting. Applies to web tickets only.)</em> </td> </tr> <tr> @@ -156,14 +176,9 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <td> <select name="assign"> <option value="0">— Unassigned —</option> - - <?php - - $sql=' SELECT staff_id,CONCAT_WS(", ",lastname,firstname) as name '. ' FROM '.STAFF_TABLE.' WHERE isactive=1 ORDER BY name'; - if(($res=db_query($sql)) && db_num_rows($res)){ echo '<OPTGROUP label="Staff Members">'; while (list($id,$name) = db_fetch_row($res)){ @@ -171,10 +186,9 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); $selected = ($info['assign']==$k || $info['staff_id']==$id)?'selected="selected"':''; ?> <option value="<?php echo $k; ?>"<?php echo $selected; ?>><?php echo $name; ?></option> - + <?php } echo '</OPTGROUP>'; - } $sql='SELECT team_id, name FROM '.TEAM_TABLE.' WHERE isenabled=1'; if(($res=db_query($sql)) && db_num_rows($res)){ @@ -199,7 +213,8 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); </td> <td> <input type="checkbox" name="noautoresp" value="1" <?php echo $info['noautoresp']?'checked="checked"':''; ?> > - <strong>Disable</strong> new ticket auto-response for this topic (Overwrites Dept. settings). + <strong>Disable</strong> new ticket auto-response for + this topic (Overrides Dept. settings). </td> </tr> diff --git a/include/staff/helptopics.inc.php b/include/staff/helptopics.inc.php index 1e15a023c5c964311042d0286b1d34d477944f30..ed927d9fe275e0cb1033ca17ea1bd823c61d6719 100644 --- a/include/staff/helptopics.inc.php +++ b/include/staff/helptopics.inc.php @@ -47,7 +47,7 @@ else $showing='No help topic found!'; ?> -<div style="width:700;padding-top:5px; float:left;"> +<div style="width:700px;padding-top:5px; float:left;"> <h2>Help Topics</h2> </div> <div style="float:right;text-align:right;padding-top:5px;padding-right:5px;"> diff --git a/include/staff/login.header.php b/include/staff/login.header.php new file mode 100644 index 0000000000000000000000000000000000000000..cf6fbddba8533edba2f7c4c8d960df48007bcb66 --- /dev/null +++ b/include/staff/login.header.php @@ -0,0 +1,23 @@ +<?php +defined('OSTSCPINC') or die('Invalid path'); +?> +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + <meta http-equiv="refresh" content="7200" /> + <title>osTicket:: SCP Login</title> + <link rel="stylesheet" href="css/login.css" type="text/css" /> + <meta name="robots" content="noindex" /> + <meta http-equiv="cache-control" content="no-cache" /> + <meta http-equiv="pragma" content="no-cache" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"> + <script type="text/javascript" src="../js/jquery-1.7.2.min.js"></script> + <script type="text/javascript"> + $(document).ready(function() { + $("input:not(.dp):visible:enabled:first").focus(); + }); + </script> +</head> +<body id="loginBody"> + diff --git a/include/staff/login.tpl.php b/include/staff/login.tpl.php index b8b136eb56d01282e8b15dbd4c0f11085e437eaa..1f3bb55e0c1565f6d9b6908c7359018c56d1bdaa 100644 --- a/include/staff/login.tpl.php +++ b/include/staff/login.tpl.php @@ -1,26 +1,7 @@ -<?php -defined('OSTSCPINC') or die('Invalid path'); - +<?php +include_once(INCLUDE_DIR.'staff/login.header.php'); $info = ($_POST && $errors)?Format::htmlchars($_POST):array(); ?> -<!DOCTYPE html> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> -<head> - <meta http-equiv="content-type" content="text/html; charset=utf-8" /> - <title>osTicket:: SCP Login</title> - <link rel="stylesheet" href="css/login.css" type="text/css" /> - <meta name="robots" content="noindex" /> - <meta http-equiv="cache-control" content="no-cache" /> - <meta http-equiv="pragma" content="no-cache" /> - <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"> - <script type="text/javascript" src="../js/jquery-1.7.2.min.js"></script> - <script type="text/javascript"> - $(document).ready(function() { - $("input:not(.dp):visible:enabled:first").focus(); - }); - </script> -</head> -<body id="loginBody"> <div id="loginBox"> <h1 id="logo"><a href="index.php">osTicket Staff Control Panel</a></h1> <h3><?php echo Format::htmlchars($msg); ?></h3> @@ -28,9 +9,12 @@ $info = ($_POST && $errors)?Format::htmlchars($_POST):array(); <?php csrf_token(); ?> <input type="hidden" name="do" value="scplogin"> <fieldset> - <input type="text" name="username" id="name" value="<?php echo $info['username']; ?>" placeholder="username" autocorrect="off" autocapitalize="off"> + <input type="text" name="userid" id="name" value="<?php echo $info['userid']; ?>" placeholder="username" autocorrect="off" autocapitalize="off"> <input type="password" name="passwd" id="pass" placeholder="password" autocorrect="off" autocapitalize="off"> </fieldset> + <?php if ($_SESSION['_staff']['strikes'] > 1 && $cfg->allowPasswordReset()) { ?> + <h3 style="display:inline"><a href="pwreset.php">Forgot my password</a></h3> + <?php } ?> <input class="submit" type="submit" name="submit" value="Log In"> </form> </div> diff --git a/include/staff/page.inc.php b/include/staff/page.inc.php new file mode 100644 index 0000000000000000000000000000000000000000..ea9d2c639f848c0e83fa161782c48a4f5928b96c --- /dev/null +++ b/include/staff/page.inc.php @@ -0,0 +1,116 @@ +<?php +if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access Denied'); +$pageTypes = array( + 'landing' => 'Landing page', + 'offline' => 'Offline page', + 'thank-you' => 'Thank you page', + 'other' => 'Other', + ); +$info=array(); +$qstr=''; +if($page && $_REQUEST['a']!='add'){ + $title='Update Page'; + $action='update'; + $submit_text='Save Changes'; + $info=$page->getHashtable(); + $slug = Format::slugify($info['name']); + $qstr.='&id='.$page->getId(); +}else { + $title='Add New Page'; + $action='add'; + $submit_text='Add Page'; + $info['isactive']=isset($info['isactive'])?$info['isactive']:0; + $qstr.='&a='.urlencode($_REQUEST['a']); +} +$info=Format::htmlchars(($errors && $_POST)?$_POST:$info); +?> +<form action="pages.php?<?php echo $qstr; ?>" method="post" id="save"> + <?php csrf_token(); ?> + <input type="hidden" name="do" value="<?php echo $action; ?>"> + <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> + <input type="hidden" name="id" value="<?php echo $info['id']; ?>"> + <h2>Site Pages</h2> + <table class="form_table" width="940" border="0" cellspacing="0" cellpadding="2"> + <thead> + <tr> + <th colspan="2"> + <h4><?php echo $title; ?></h4> + <em>Page information.</em> + </th> + </tr> + </thead> + <tbody> + <tr> + <td width="180" class="required"> + Name: + </td> + <td> + <input type="text" size="40" name="name" value="<?php echo $info['name']; ?>"> + <span class="error">* <?php echo $errors['name']; ?></span> + </td> + </tr> + <tr> + <td width="180" class="required"> + Type: + </td> + <td> + <select name="type"> + <option value="" selected="selected">Select Page Type</option> + <?php + foreach($pageTypes as $k => $v) + echo sprintf('<option value="%s" %s>%s</option>', + $k, (($info['type']==$k)?'selected="selected"':''), $v); + ?> + </select> + <span class="error">* <?php echo $errors['type']; ?></span> + </td> + </tr> + <?php if ($info['name'] && $info['type'] == 'other') { ?> + <tr> + <td width="180" class="required"> + Public URL: + </td> + <td><a href="<?php echo sprintf("%s/pages/%s", + $ost->getConfig()->getBaseUrl(), urlencode($slug)); + ?>">pages/<?php echo $slug; ?></a> + </td> + </tr> + <?php } ?> + <tr> + <td width="180" class="required"> + Status: + </td> + <td> + <input type="radio" name="isactive" value="1" <?php echo $info['isactive']?'checked="checked"':''; ?>><strong>Active</strong> + <input type="radio" name="isactive" value="0" <?php echo !$info['isactive']?'checked="checked"':''; ?>>Disabled + <span class="error">* <?php echo $errors['isactive']; ?></span> + </td> + </tr> + <tr> + <th colspan="2"> + <em><b>Page body</b>: Ticket variables are only supported in thank-you pages.<font class="error">* <?php echo $errors['body']; ?></font></em> + </th> + </tr> + <tr> + <td colspan=2 style="padding-left:3px;"> + <textarea name="body" cols="21" rows="12" style="width:98%;" class="richtext"><?php echo $info['body']; ?></textarea> + </td> + </tr> + <tr> + <th colspan="2"> + <em><strong>Admin Notes</strong>: Internal notes. </em> + </th> + </tr> + <tr> + <td colspan=2> + <textarea name="notes" cols="21" rows="8" style="width: 80%;"><?php echo $info['notes']; ?></textarea> + </td> + </tr> + </tbody> +</table> +<p style="padding-left:225px;"> + <input type="submit" name="submit" value="<?php echo $submit_text; ?>"> + <input type="reset" name="reset" value="Reset"> + <input type="button" name="cancel" value="Cancel" onclick='window.location.href="pages.php"'> +</p> +</form> diff --git a/include/staff/pages.inc.php b/include/staff/pages.inc.php new file mode 100644 index 0000000000000000000000000000000000000000..a1822e26726a0c6ab9d172a15619434d53ea3707 --- /dev/null +++ b/include/staff/pages.inc.php @@ -0,0 +1,153 @@ +<?php +if(!defined('OSTADMININC') || !$thisstaff->isAdmin()) die('Access Denied'); + +$qstr=''; +$sql='SELECT page.id, page.isactive, page.name, page.created, page.updated, ' + .'page.type, count(topic.topic_id) as topics ' + .' FROM '.PAGE_TABLE.' page ' + .' LEFT JOIN '.TOPIC_TABLE.' topic ON(topic.page_id=page.id) ' + .' WHERE 1 '; +$sortOptions=array( + 'name'=>'page.name', 'status'=>'page.isactive', + 'created'=>'page.created', 'updated'=>'page.updated'); + +$orderWays=array('DESC'=>'DESC','ASC'=>'ASC'); +$sort=($_REQUEST['sort'] && $sortOptions[strtolower($_REQUEST['sort'])])?strtolower($_REQUEST['sort']):'name'; +//Sorting options... +if($sort && $sortOptions[$sort]) { + $order_column =$sortOptions[$sort]; +} + +$order_column=$order_column?$order_column:'page.name'; + +if($_REQUEST['order'] && $orderWays[strtoupper($_REQUEST['order'])]) { + $order=$orderWays[strtoupper($_REQUEST['order'])]; +} +$order=$order?$order:'ASC'; + +if($order_column && strpos($order_column,',')){ + $order_column=str_replace(','," $order,",$order_column); +} +$x=$sort.'_sort'; +$$x=' class="'.strtolower($order).'" '; +$order_by="$order_column $order "; + +$total=db_count('SELECT count(*) FROM '.PAGE_TABLE.' page '); +$page=($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1; +$pageNav=new Pagenate($total, $page, PAGE_LIMIT); +$pageNav->setURL('pages.php',$qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order'])); +//Ok..lets roll...create the actual query +$qstr.='&order='.($order=='DESC'?'ASC':'DESC'); +$query="$sql GROUP BY page.id ORDER BY $order_by LIMIT ".$pageNav->getStart().",".$pageNav->getLimit(); +$res=db_query($query); +if($res && ($num=db_num_rows($res))) + $showing=$pageNav->showing(); +else + $showing='No pages found!'; + +?> + +<div style="width:700px;padding-top:5px; float:left;"> + <h2>Site Pages</h2> +</div> +<div style="float:right;text-align:right;padding-top:5px;padding-right:5px;"> + <b><a href="pages.php?a=add" class="Icon newPage">Add New Page</a></b></div> +<div class="clear"></div> +<form action="pages.php" method="POST" name="tpls"> + <?php csrf_token(); ?> + <input type="hidden" name="do" value="mass_process" > +<input type="hidden" id="action" name="a" value="" > + <table class="list" border="0" cellspacing="1" cellpadding="0" width="940"> + <caption><?php echo $showing; ?></caption> + <thead> + <tr> + <th width="7"> </th> + <th width="380"><a <?php echo $name_sort; ?> href="pages.php?<?php echo $qstr; ?>&sort=name">Name</a></th> + <th width="120"><a <?php echo $status_sort; ?> href="pages.php?<?php echo $qstr; ?>&sort=status">Status</a></th> + <th width="150" nowrap><a <?php echo $created_sort; ?>href="pages.php?<?php echo $qstr; ?>&sort=created">Date Added</a></th> + <th width="150" nowrap><a <?php echo $updated_sort; ?>href="pages.php?<?php echo $qstr; ?>&sort=updated">Last Updated</a></th> + </tr> + </thead> + <tbody> + <?php + $total=0; + $ids=($errors && is_array($_POST['ids']))?$_POST['ids']:null; + if($res && db_num_rows($res)): + $defaultPages=$cfg->getDefaultPages(); + while ($row = db_fetch_array($res)) { + $sel=false; + if($ids && in_array($row['id'], $ids)) + $sel=true; + $inuse = ($row['topics'] || in_array($row['id'], $defaultPages)); + ?> + <tr id="<?php echo $row['id']; ?>"> + <td width=7px> + <input type="checkbox" class="ckb" name="ids[]" value="<?php echo $row['id']; ?>" + <?php echo $sel?'checked="checked"':''; ?>> + </td> + <td> <a href="pages.php?id=<?php echo $row['id']; ?>"><?php echo Format::htmlchars($row['name']); ?></a></td> + <td> + <?php echo $row['isactive']?'Active':'<b>Disabled</b>'; ?> + <?php echo $inuse?'<em>(in-use)</em>':''; ?> + </td> + <td> <?php echo Format::db_date($row['created']); ?></td> + <td> <?php echo Format::db_datetime($row['updated']); ?></td> + </tr> + <?php + } //end of while. + endif; ?> + <tfoot> + <tr> + <td colspan="6"> + <?php if($res && $num){ ?> + Select: + <a id="selectAll" href="#ckb">All</a> + <a id="selectNone" href="#ckb">None</a> + <a id="selectToggle" href="#ckb">Toggle</a> + <?php }else{ + echo 'No pages found'; + } ?> + </td> + </tr> + </tfoot> +</table> +<?php +if($res && $num): //Show options.. + echo '<div> Page:'.$pageNav->getPageLinks().' </div>'; +?> +<p class="centered" id="actions"> + <input class="button" type="submit" name="enable" value="Enable" > + <input class="button" type="submit" name="disable" value="Disable" > + <input class="button" type="submit" name="delete" value="Delete" > +</p> +<?php +endif; +?> +</form> + +<div style="display:none;" class="dialog" id="confirm-action"> + <h3>Please Confirm</h3> + <a class="close" href="">×</a> + <hr/> + <p class="confirm-action" style="display:none;" id="enable-confirm"> + Are you sure want to <b>enable</b> selected pages? + </p> + <p class="confirm-action" style="display:none;" id="disable-confirm"> + Are you sure want to <b>disable</b> selected pages? + </p> + <p class="confirm-action" style="display:none;" id="delete-confirm"> + <font color="red"><strong>Are you sure you want to DELETE selected pages?</strong></font> + <br><br>Deleted pages CANNOT be recovered. + </p> + <div>Please confirm to continue.</div> + <hr style="margin-top:1em"/> + <p class="full-width"> + <span class="buttons" style="float:left"> + <input type="button" value="No, Cancel" class="close"> + </span> + <span class="buttons" style="float:right"> + <input type="button" value="Yes, Do it!" class="confirm"> + </span> + </p> + <div class="clear"></div> +</div> diff --git a/include/staff/profile.inc.php b/include/staff/profile.inc.php index 073a7c8a44229e1b18bf7b7b4cc738d8689d8396..2543c80d1cfa68444199a894f117c1521774afb4 100644 --- a/include/staff/profile.inc.php +++ b/include/staff/profile.inc.php @@ -190,6 +190,7 @@ $info['id']=$staff->getId(); <em><strong>Password</strong>: To reset your password, provide your current password and a new password below. <span class="error"> <?php echo $errors['passwd']; ?></span></em> </th> </tr> + <?php if (!isset($_SESSION['_staff']['reset-token'])) { ?> <tr> <td width="180"> Current Password: @@ -199,6 +200,7 @@ $info['id']=$staff->getId(); <span class="error"> <?php echo $errors['cpasswd']; ?></span> </td> </tr> + <?php } ?> <tr> <td width="180"> New Password: diff --git a/include/staff/pwreset.login.php b/include/staff/pwreset.login.php new file mode 100644 index 0000000000000000000000000000000000000000..6f93f1f0118093aefd5eb7b67568a560d2fb9066 --- /dev/null +++ b/include/staff/pwreset.login.php @@ -0,0 +1,26 @@ +<?php +include_once(INCLUDE_DIR.'staff/login.header.php'); +defined('OSTSCPINC') or die('Invalid path'); +$info = ($_POST)?Format::htmlchars($_POST):array(); +?> + +<div id="loginBox"> + <h1 id="logo"><a href="index.php">osTicket Staff Password Reset</a></h1> + <h3><?php echo Format::htmlchars($msg); ?></h3> + + <form action="pwreset.php" method="post"> + <?php csrf_token(); ?> + <input type="hidden" name="do" value="newpasswd"/> + <input type="hidden" name="token" value="<?php echo $_REQUEST['token']; ?>"/> + <fieldset> + <input type="text" name="userid" id="name" value="<?php echo + $info['userid']; ?>" placeholder="username or email" + autocorrect="off" autocapitalize="off"/> + </fieldset> + <input class="submit" type="submit" name="submit" value="Login"/> + </form> +</div> + +<div id="copyRights">Copyright © <a href='http://www.osticket.com' target="_blank">osTicket.com</a></div> +</body> +</html> diff --git a/include/staff/pwreset.php b/include/staff/pwreset.php new file mode 100644 index 0000000000000000000000000000000000000000..6aadeb2fcf10dbb308ed7ec3f548f4afd6c94ee8 --- /dev/null +++ b/include/staff/pwreset.php @@ -0,0 +1,25 @@ +<?php +include_once(INCLUDE_DIR.'staff/login.header.php'); +defined('OSTSCPINC') or die('Invalid path'); +$info = ($_POST && $errors)?Format::htmlchars($_POST):array(); +?> + +<div id="loginBox"> + <h1 id="logo"><a href="index.php">osTicket Staff Password Reset</a></h1> + <h3><?php echo Format::htmlchars($msg); ?></h3> + <form action="pwreset.php" method="post"> + <?php csrf_token(); ?> + <input type="hidden" name="do" value="sendmail"> + <fieldset> + <input type="text" name="userid" id="name" value="<?php echo + $info['userid']; ?>" placeholder="username" autocorrect="off" + autocapitalize="off"> + </fieldset> + <input class="submit" type="submit" name="submit" value="Send Email"/> + </form> + +</div> + +<div id="copyRights">Copyright © <a href='http://www.osticket.com' target="_blank">osTicket.com</a></div> +</body> +</html> diff --git a/include/staff/pwreset.sent.php b/include/staff/pwreset.sent.php new file mode 100644 index 0000000000000000000000000000000000000000..832b78ef57470c4045ed615c152dc228eb414e79 --- /dev/null +++ b/include/staff/pwreset.sent.php @@ -0,0 +1,22 @@ +<?php +include_once(INCLUDE_DIR.'staff/login.header.php'); +defined('OSTSCPINC') or die('Invalid path'); +$info = ($_POST && $errors)?Format::htmlchars($_POST):array(); +?> + +<div id="loginBox"> + <h1 id="logo"><a href="index.php">osTicket Staff Password Reset</a></h1> + <h3>A confirmation email has been sent</h3> + <h3 style="color:black;"><em> + A password reset email was sent to the email on file for your account. + Follow the link in the email to reset your password. + </em></h3> + + <form action="index.php" method="get"> + <input class="submit" type="submit" name="submit" value="Login"/> + </form> +</div> + +<div id="copyRights">Copyright © <a href='http://www.osticket.com' target="_blank">osTicket.com</a></div> +</body> +</html> diff --git a/include/staff/settings-autoresp.inc.php b/include/staff/settings-autoresp.inc.php index b5815915fc346bb97fbfa318db9416bda6bfccce..0ad893b3fb97c4515ca407e9318a164cdc37454e 100644 --- a/include/staff/settings-autoresp.inc.php +++ b/include/staff/settings-autoresp.inc.php @@ -28,7 +28,7 @@ <input type="radio" name="ticket_notice_active" value="1" <?php echo $config['ticket_notice_active']?'checked="checked"':''; ?> /><b>Enable</b> <input type="radio" name="ticket_notice_active" value="0" <?php echo !$config['ticket_notice_active']?'checked="checked"':''; ?> />Disable - <em>(Notice sent when staff creates a ticket on behalf of the user (Staff can overwrite))</em> + <em>(Notice sent when staff creates a ticket on behalf of the user (Staff can override))</em> </td> </tr> <tr> diff --git a/include/staff/settings-emails.inc.php b/include/staff/settings-emails.inc.php index b7175a06e6b9c5b6768690de5d233bdd6a43f5fc..e433cd7a2d9eb0d10243d886ce06e941d8dc7773 100644 --- a/include/staff/settings-emails.inc.php +++ b/include/staff/settings-emails.inc.php @@ -10,7 +10,7 @@ if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$config) <tr> <th colspan="2"> <h4>Email Settings</h4> - <em>Note that some of the global settings can be overwritten at department/email level.</em> + <em>Note that some of the global settings can be overridden at department/email level.</em> </th> </tr> </thead> @@ -58,7 +58,7 @@ if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$config) <td> <input type="text" size=40 name="admin_email" value="<?php echo $config['admin_email']; ?>"> <font class="error">* <?php echo $errors['admin_email']; ?></font> - <em>(System administrator's email)</em> + <em>(System administrator's email)</em> </td> </tr> <tr><th colspan=2><em><strong>Incoming Emails</strong>: For mail fetcher (polling) to work you must set an external cron job or enable auto-cron polling</em></th> diff --git a/include/staff/settings-pages.inc.php b/include/staff/settings-pages.inc.php new file mode 100644 index 0000000000000000000000000000000000000000..43c8ea4fe31ad2be5f76912997e44a05f691ca81 --- /dev/null +++ b/include/staff/settings-pages.inc.php @@ -0,0 +1,185 @@ +<?php +if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$config) die('Access Denied'); +$pages = Page::getPages(); +?> +<h2>Site Pages</h2> +<form action="settings.php?t=pages" method="post" id="save" + enctype="multipart/form-data"> +<?php csrf_token(); ?> +<input type="hidden" name="t" value="pages" > +<table class="form_table settings_table" width="940" border="0" cellspacing="0" cellpadding="2"> + <thead> + <tr> + <th colspan="2"> + <h4>Pages</h4> + <em>To edit or add new pages go to <a href="pages.php">Manage > Site Pages</a></em> + </th> + </tr> + </thead> + <tbody> + <tr> + <td width="220" class="required">Default Landing Page:</td> + <td> + <select name="landing_page_id"> + <option value="">— Select Landing Page —</option> + <?php + foreach($pages as $page) { + if(strcasecmp($page->getType(), 'landing')) continue; + echo sprintf('<option value="%d" %s>%s</option>', + $page->getId(), + ($config['landing_page_id']==$page->getId())?'selected="selected"':'', + $page->getName()); + } ?> + </select> <font class="error">* <?php echo $errors['landing_page_id']; ?></font> + </td> + </tr> + <tr> + <td width="220" class="required">Default Offline Page:</td> + <td> + <select name="offline_page_id"> + <option value="">— Select Offline Page —</option> + <?php + foreach($pages as $page) { + if(strcasecmp($page->getType(), 'offline')) continue; + echo sprintf('<option value="%d" %s>%s</option>', + $page->getId(), + ($config['offline_page_id']==$page->getId())?'selected="selected"':'', + $page->getName()); + } ?> + </select> <font class="error">* <?php echo $errors['offline_page_id']; ?></font> + </td> + </tr> + <tr> + <td width="220" class="required">Default Thank-You Page:</td> + <td> + <select name="thank-you_page_id"> + <option value="">— Select Thank-You Page —</option> + <?php + foreach($pages as $page) { + if(strcasecmp($page->getType(), 'thank-you')) continue; + echo sprintf('<option value="%d" %s>%s</option>', + $page->getId(), + ($config['thank-you_page_id']==$page->getId())?'selected="selected"':'', + $page->getName()); + } ?> + </select> <font class="error">* <?php echo $errors['thank-you_page_id']; ?></font> + </td> + </tr> + </tbody> +</table> +<table class="form_table settings_table" width="940" border="0" cellspacing="0" cellpadding="2"> + <thead> + <tr> + <th colspan="2"> + <h4>Logos</h4> + <em>System Default Logo</em> + </th> + </tr> + </thead> + <tbody> + <tr> + <td colspan="2"> + <label style="display:block"> + <input type="radio" name="selected-logo" value="0" + style="margin-left: 1em" + <?php if (!$ost->getConfig()->getClientLogoId()) + echo 'checked="checked"'; ?>/> + <img src="../assets/default/images/logo.png" + alt="Default Logo" valign="middle" + style="box-shadow: 0 0 0.5em rgba(0,0,0,0.5); + margin: 0.5em; height: 5em; + vertical-align: middle"/> + </label> + </td></tr> + <tr> + <th colspan="2"> + <em>Use a custom logo — Use a delete checkbox to + remove the logo from the system</em> + </th> + </tr> + <tr><td colspan="2"> + <?php + $current = $ost->getConfig()->getClientLogoId(); + foreach (AttachmentFile::allLogos() as $logo) { ?> + <div> + <label> + <input type="radio" name="selected-logo" + style="margin-left: 1em" value="<?php + echo $logo->getId(); ?>" <?php + if ($logo->getId() == $current) + echo 'checked="checked"'; ?>/> + <img src="image.php?h=<?php echo $logo->getDownloadHash(); ?>" + alt="Custom Logo" valign="middle" + style="box-shadow: 0 0 0.5em rgba(0,0,0,0.5); + margin: 0.5em; height: 5em; + vertical-align: middle;"/> + </label> + <?php if ($logo->getId() != $current) { ?> + <label> + <input type="checkbox" name="delete-logo[]" value="<?php + echo $logo->getId(); ?>"/> Delete + </label> + <?php } ?> + </div> + <?php } ?> + <br/> + <b>Upload a new logo:</b> + <input type="file" name="logo[]" size="30" value="" /> + <font class="error"><br/><?php echo $errors['logo']; ?></font> + </td> + </tr> + </tbody> +</table> +<p style="padding-left:250px;"> + <input class="button" type="submit" name="submit-button" value="Save Changes"> + <input class="button" type="reset" name="reset" value="Reset Changes"> +</p> +</form> + +<div style="display:none;" class="dialog" id="confirm-action"> + <h3>Please Confirm</h3> + <a class="close" href="">×</a> + <hr/> + <p class="confirm-action" id="delete-confirm"> + <font color="red"><strong>Are you sure you want to DELETE selected + logos?</strong></font> + <br/><br/>Deleted logos CANNOT be recovered. + </p> + <div>Please confirm to continue.</div> + <hr style="margin-top:1em"/> + <p class="full-width"> + <span class="buttons" style="float:left"> + <input type="button" value="No, Cancel" class="close"> + </span> + <span class="buttons" style="float:right"> + <input type="button" value="Yes, Do it!" class="confirm"> + </span> + </p> + <div class="clear"></div> +</div> + +<script type="text/javascript"> +$(function() { + $('#save input:submit.button').bind('click', function(e) { + var formObj = $('#save'); + if ($('input:checkbox:checked', formObj).length) { + e.preventDefault(); + $('.dialog#confirm-action').undelegate('.confirm'); + $('.dialog#confirm-action').delegate('input.confirm', 'click', function(e) { + e.preventDefault(); + $('.dialog#confirm-action').hide(); + $('#overlay').hide(); + formObj.submit(); + return false; + }); + $('#overlay').show(); + $('.dialog#confirm-action .confirm-action').hide(); + $('.dialog#confirm-action p#delete-confirm') + .show() + .parent('div').show().trigger('click'); + return false; + } + else return true; + }); +}); +</script> diff --git a/include/staff/settings-system.inc.php b/include/staff/settings-system.inc.php index 3fba7f15977390d6882f00894523b90d92d4aced..8dd170e4161a97601b589017b4430ba8a15303ae 100644 --- a/include/staff/settings-system.inc.php +++ b/include/staff/settings-system.inc.php @@ -3,7 +3,7 @@ if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$config) $gmtime = Misc::gmtime(); ?> -<h2>System Settings and Preferences - <span>osTicket (v<?php echo $cfg->getVersion(); ?>)</span></h2> +<h2>System Settings and Preferences - <span>osTicket (<?php echo $cfg->getVersion(); ?>)</span></h2> <form action="settings.php?t=system" method="post" id="save"> <?php csrf_token(); ?> <input type="hidden" name="t" value="system" > @@ -11,7 +11,7 @@ $gmtime = Misc::gmtime(); <thead> <tr> <th colspan="2"> - <h4>System Settings & Preferences</h4> + <h4>System Settings & Preferences</h4> <em><b>General Settings</b>: Offline mode will disable client interface and only allow admins to login to Staff Control Panel</em> </th> </tr> @@ -60,7 +60,7 @@ $gmtime = Misc::gmtime(); <select name="default_template_id"> <option value="">— Select Default Template —</option> <?php - $sql='SELECT tpl_id,name FROM '.EMAIL_TEMPLATE_TABLE.' WHERE isactive=1 AND cfg_id='.db_input($cfg->getId()).' ORDER BY name'; + $sql='SELECT tpl_id,name FROM '.EMAIL_TEMPLATE_GRP_TABLE.' WHERE isactive=1 ORDER BY name'; if(($res=db_query($sql)) && db_num_rows($res)){ while (list($id, $name) = db_fetch_row($res)){ $selected = ($config['default_template_id']==$id)?'selected="selected"':''; ?> @@ -99,7 +99,7 @@ $gmtime = Misc::gmtime(); </tr> <tr> <td>Purge Logs:</td> - <td> + <td> <select name="log_graceperiod"> <option value=0 selected>Never Purge Logs</option> <?php @@ -112,7 +112,12 @@ $gmtime = Misc::gmtime(); </select> </td> </tr> - <tr><td>Password Reset Policy:</th> + <tr> + <th colspan="2"> + <em><b>Authentication Settings</b></em> + </th> + </tr> + <tr><td>Password Change Policy:</th> <td> <select name="passwd_reset_period"> <option value="0"> — None —</option> @@ -126,10 +131,20 @@ $gmtime = Misc::gmtime(); <font class="error"> <?php echo $errors['passwd_reset_period']; ?></font> </td> </tr> - <tr><td>Bind Staff Session to IP:</td> + <tr><td>Allow Password Resets:</th> <td> - <input type="checkbox" name="staff_ip_binding" <?php echo $config['staff_ip_binding']?'checked="checked"':''; ?>> - <em>(binds staff session to originating IP address upon login)</em> + <input type="checkbox" name="allow_pw_reset" <?php echo $config['allow_pw_reset']?'checked="checked"':''; ?>> + <em>Enables the <u>Forgot my password</u> link on the staff + control panel</em> + </td> + </tr> + <tr><td>Password Reset Window:</th> + <td> + <input type="text" name="pw_reset_window" size="6" value="<?php + echo $config['pw_reset_window']; ?>"> + Maximum time <em>in minutes</em> a password reset token can + be valid. + <font class="error"> <?php echo $errors['pw_reset_window']; ?></font> </td> </tr> <tr><td>Staff Excessive Logins:</td> @@ -172,7 +187,7 @@ $gmtime = Misc::gmtime(); echo sprintf('<option value="%d" %s>%d</option>', $i,(($config['client_login_timeout']==$i)?'selected="selected"':''), $i); } ?> - </select> minute lock-out is enforced. + </select> minute lock-out is enforced. </td> </tr> @@ -182,6 +197,12 @@ $gmtime = Misc::gmtime(); Maximum idle time in minutes before a client must log in again (enter 0 to disable). </td> </tr> + <tr><td>Bind Staff Session to IP:</td> + <td> + <input type="checkbox" name="staff_ip_binding" <?php echo $config['staff_ip_binding']?'checked="checked"':''; ?>> + <em>(binds staff session to originating IP address upon login)</em> + </td> + </tr> <tr> <th colspan="2"> <em><b>Date and Time Options</b>: Please refer to <a href="http://php.net/date" target="_blank">PHP Manual</a> for supported parameters.</em> diff --git a/include/staff/settings-tickets.inc.php b/include/staff/settings-tickets.inc.php index 4d3f47f6f90849c6b4b6ed313fa95bcb96df2cc4..0c29ca73a1c6bbc4d478e3e71433031ed2754c22 100644 --- a/include/staff/settings-tickets.inc.php +++ b/include/staff/settings-tickets.inc.php @@ -80,7 +80,7 @@ if(!($maxfileuploads=ini_get('max_file_uploads'))) <td width="180">Web Tickets Priority:</td> <td> <input type="checkbox" name="allow_priority_change" value="1" <?php echo $config['allow_priority_change'] ?'checked="checked"':''; ?>> - <em>(Allow user to overwrite/set priority)</em> + <em>(Allow user to override/set priority)</em> </td> </tr> <tr> diff --git a/include/staff/slaplan.inc.php b/include/staff/slaplan.inc.php index d9c1574feaa1a2bb80e6dc5145f1461dd0fcd0a0..94f1fb1c61a9bf805758339f36d5885f003fa1ee 100644 --- a/include/staff/slaplan.inc.php +++ b/include/staff/slaplan.inc.php @@ -74,16 +74,26 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <strong>Enable</strong> priority escalation on overdue tickets. </td> </tr> + <tr> + <td width="180"> + Transient: + </td> + <td> + <input type="checkbox" name="transient" value="1" <?php echo $info['transient']?'checked="checked"':''; ?> > + SLA can be overridden on ticket transfer or help topic + change + </td> + </tr> <tr> <td width="180"> Ticket Overdue Alerts: </td> <td> <input type="checkbox" name="disable_overdue_alerts" value="1" <?php echo $info['disable_overdue_alerts']?'checked="checked"':''; ?> > - <strong>Disable</strong> overdue alerts notices. <em>(Overwrite global setting)</em> + <strong>Disable</strong> overdue alerts notices. + <em>(Override global setting)</em> </td> </tr> - <tr> <th colspan="2"> <em><strong>Admin Notes</strong>: Internal notes. </em> diff --git a/include/staff/slaplans.inc.php b/include/staff/slaplans.inc.php index a731b7492faa874a43e6aac9c6243faafdeed410..079de4103e5b4de2deda7fa95d8b2ade40c22a41 100644 --- a/include/staff/slaplans.inc.php +++ b/include/staff/slaplans.inc.php @@ -39,7 +39,7 @@ else ?> -<div style="width:700;padding-top:5px; float:left;"> +<div style="width:700px;padding-top:5px; float:left;"> <h2>Service Level Agreements</h2> </div> <div style="float:right;text-align:right;padding-top:5px;padding-right:5px;"> diff --git a/include/staff/staffmembers.inc.php b/include/staff/staffmembers.inc.php index 08877d02a7ab51b35f974b83183e04fd32566406..8820397524951559a8529c936b7b7cabe29cac53 100644 --- a/include/staff/staffmembers.inc.php +++ b/include/staff/staffmembers.inc.php @@ -55,11 +55,11 @@ $query="$select $from $where GROUP BY staff.staff_id ORDER BY $order_by LIMIT ". //echo $query; ?> <h2>Staff Members</h2> -<div style="width:700; float:left;"> +<div style="width:700px; float:left;"> <form action="staff.php" method="GET" name="filter"> <input type="hidden" name="a" value="filter" > <select name="did" id="did"> - <option value="0">— All Department —</option> + <option value="0">— All Departments —</option> <?php $sql='SELECT dept.dept_id, dept.dept_name,count(staff.staff_id) as users '. 'FROM '.DEPT_TABLE.' dept '. diff --git a/include/staff/team.inc.php b/include/staff/team.inc.php index 916582d8e7d95b4d5d188f0174aca1f3bd3411b8..4ca688ac5ae3702b80f2478e67c407330b809bf7 100644 --- a/include/staff/team.inc.php +++ b/include/staff/team.inc.php @@ -81,7 +81,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); </td> <td> <input type="checkbox" name="noalerts" value="1" <?php echo $info['noalerts']?'checked="checked"':''; ?> > - <strong>Disable</strong> assignment alerts for this team (<i>overwrite global settings.</i>) + <strong>Disable</strong> assignment alerts for this team (<i>override global settings.</i>) </td> </tr> <?php @@ -97,8 +97,8 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <b><a href="staff.php?id=%d">%s</a></span></b> <input type="checkbox" name="remove[]" value="%d"><i>Remove</i></td></tr>', $staff->getId(),$staff->getName(),$staff->getId()); - - + + } } ?> <tr> diff --git a/include/staff/teams.inc.php b/include/staff/teams.inc.php index f63cdb40e6c864657b630b88b4ce059c17ab817e..9724714f71956fcdc0fb3ddb9f4cd5e3fb3a26f5 100644 --- a/include/staff/teams.inc.php +++ b/include/staff/teams.inc.php @@ -38,7 +38,7 @@ else $showing='No teams found!'; ?> -<div style="width:700;padding-top:5px; float:left;"> +<div style="width:700px;padding-top:5px; float:left;"> <h2>Teams</h2> </div> <div style="float:right;text-align:right;padding-top:5px;padding-right:5px;"> @@ -52,7 +52,7 @@ else <caption><?php echo $showing; ?></caption> <thead> <tr> - <th width="7px"> </th> + <th width="7px"> </th> <th width="250"><a <?php echo $name_sort; ?> href="teams.php?<?php echo $qstr; ?>&sort=name">Team Name</a></th> <th width="80"><a <?php echo $status_sort; ?> href="teams.php?<?php echo $qstr; ?>&sort=status">Status</a></th> <th width="80"><a <?php echo $members_sort; ?>href="teams.php?<?php echo $qstr; ?>&sort=members">Members</a></th> @@ -73,7 +73,7 @@ else ?> <tr id="<?php echo $row['team_id']; ?>"> <td width=7px> - <input type="checkbox" class="ckb" name="ids[]" value="<?php echo $row['team_id']; ?>" + <input type="checkbox" class="ckb" name="ids[]" value="<?php echo $row['team_id']; ?>" <?php echo $sel?'checked="checked"':''; ?>> </td> <td><a href="teams.php?id=<?php echo $row['team_id']; ?>"><?php echo $row['name']; ?></a> </td> <td> <?php echo $row['isenabled']?'Active':'<b>Disabled</b>'; ?></td> diff --git a/include/staff/template.inc.php b/include/staff/template.inc.php index aff5f8e3b780e46bd54bc23e0ded14694fe63e71..e4c52705acc893f63c623695517ee9077932bc4d 100644 --- a/include/staff/template.inc.php +++ b/include/staff/template.inc.php @@ -8,8 +8,8 @@ if($template && $_REQUEST['a']!='add'){ $action='update'; $submit_text='Save Changes'; $info=$template->getInfo(); - $info['id']=$template->getId(); - $qstr.='&id='.$template->getId(); + $info['tpl_id']=$template->getId(); + $qstr.='&tpl_id='.$template->getId(); }else { $title='Add New Template'; $action='add'; @@ -23,7 +23,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <?php csrf_token(); ?> <input type="hidden" name="do" value="<?php echo $action; ?>"> <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>"> - <input type="hidden" name="id" value="<?php echo $info['id']; ?>"> + <input type="hidden" name="tpl_id" value="<?php echo $info['tpl_id']; ?>"> <h2>Email Template</h2> <table class="form_table" width="940" border="0" cellspacing="0" cellpadding="2"> <thead> @@ -74,10 +74,31 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); </th> </tr> <?php - foreach(Template::message_templates() as $k=>$tpl){ - echo sprintf('<tr><td colspan=2> <strong><a href="templates.php?id=%d&a=manage&tpl=%s">%s</a></strong> - <em>%s</em></td></tr>', - $template->getId(),$k,Format::htmlchars($tpl['name']),Format::htmlchars($tpl['desc'])); + foreach($template->getTemplates() as $tpl){ + $info = $tpl->getDescription(); + if (!$info['name']) + continue; + echo sprintf('<tr><td colspan=2> <strong><a href="templates.php?id=%d&a=manage">%s</a></strong> - <em>%s</em></td></tr>', + $tpl->getId(),Format::htmlchars($info['name']), + Format::htmlchars($info['desc'])); } + if (($undef = $template->getUndefinedTemplateNames())) { ?> + <tr> + <th colspan="2"> + <em><strong>Unimplemented Template Messages</strong>: Click + on the message to implement</em> + </th> + </tr> + <?php + foreach($template->getUndefinedTemplateNames() as $cn=>$info){ + echo sprintf('<tr><td colspan=2> <strong><a + href="templates.php?tpl_id=%d&a=implement&code_name=%s" + style="color:red;text-decoration:underline" + >%s</a></strong> - <em>%s</em></td></tr>', + $template->getId(),$cn,Format::htmlchars($info['name']), + Format::htmlchars($info['desc'])); + } + } }else{ ?> <tr> <td width="180" class="required"> @@ -87,7 +108,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <select name="tpl_id"> <option value="0">— Select One ‐</option> <?php - $sql='SELECT tpl_id,name FROM '.EMAIL_TEMPLATE_TABLE.' ORDER by name'; + $sql='SELECT tpl_id,name FROM '.EMAIL_TEMPLATE_GRP_TABLE.' ORDER by name'; if(($res=db_query($sql)) && db_num_rows($res)){ while(list($id,$name)=db_fetch_row($res)){ $selected=($info['tpl_id'] && $id==$info['tpl_id'])?'selected="selected"':''; diff --git a/include/staff/templates.inc.php b/include/staff/templates.inc.php index a0f5de468a76bf7cde53e73bdc0767606ea6207b..8f18d82d84eba62ce396136553c7bd70507c934a 100644 --- a/include/staff/templates.inc.php +++ b/include/staff/templates.inc.php @@ -3,7 +3,7 @@ if(!defined('OSTADMININC') || !$thisstaff->isAdmin()) die('Access Denied'); $qstr=''; $sql='SELECT tpl.*,count(dept.tpl_id) as depts '. - 'FROM '.EMAIL_TEMPLATE_TABLE.' tpl '. + 'FROM '.EMAIL_TEMPLATE_GRP_TABLE.' tpl '. 'LEFT JOIN '.DEPT_TABLE.' dept USING(tpl_id) '. 'WHERE 1 '; $sortOptions=array('name'=>'tpl.name','status'=>'tpl.isactive','created'=>'tpl.created','updated'=>'tpl.updated'); @@ -27,7 +27,7 @@ $x=$sort.'_sort'; $$x=' class="'.strtolower($order).'" '; $order_by="$order_column $order "; -$total=db_count('SELECT count(*) FROM '.EMAIL_TEMPLATE_TABLE.' tpl '); +$total=db_count('SELECT count(*) FROM '.EMAIL_TEMPLATE_GRP_TABLE.' tpl '); $page=($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1; $pageNav=new Pagenate($total, $page, PAGE_LIMIT); $pageNav->setURL('templates.php',$qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order'])); @@ -42,7 +42,7 @@ else ?> -<div style="width:700;padding-top:5px; float:left;"> +<div style="width:700px;padding-top:5px; float:left;"> <h2>Email Templates</h2> </div> <div style="float:right;text-align:right;padding-top:5px;padding-right:5px;"> @@ -56,7 +56,7 @@ else <caption><?php echo $showing; ?></caption> <thead> <tr> - <th width="7"> </th> + <th width="7"> </th> <th width="350"><a <?php echo $name_sort; ?> href="templates.php?<?php echo $qstr; ?>&sort=name">Name</a></th> <th width="100"><a <?php echo $status_sort; ?> href="templates.php?<?php echo $qstr; ?>&sort=status">Status</a></th> <th width="80"><a <?php echo $inuse_sort; ?> href="templates.php?<?php echo $qstr; ?>&sort=inuse">In-Use</a></th> @@ -75,15 +75,15 @@ else $sel=false; if($ids && in_array($row['tpl_id'],$ids)) $sel=true; - + $default=($defaultTplId==$row['tpl_id'])?'<small class="fadded">(System Default)</small>':''; ?> <tr id="<?php echo $row['tpl_id']; ?>"> <td width=7px> - <input type="checkbox" class="ckb" name="ids[]" value="<?php echo $row['tpl_id']; ?>" + <input type="checkbox" class="ckb" name="ids[]" value="<?php echo $row['tpl_id']; ?>" <?php echo $sel?'checked="checked"':''; ?> <?php echo $default?'disabled="disabled"':''; ?> > </td> - <td> <a href="templates.php?id=<?php echo $row['tpl_id']; ?>"><?php echo Format::htmlchars($row['name']); ?></a> + <td> <a href="templates.php?tpl_id=<?php echo $row['tpl_id']; ?>"><?php echo Format::htmlchars($row['name']); ?></a> <?php echo $default; ?></td> <td> <?php echo $row['isactive']?'Active':'<b>Disabled</b>'; ?></td> <td> <?php echo ($inuse)?'<b>Yes</b>':'No'; ?></td> diff --git a/include/staff/ticket-edit.inc.php b/include/staff/ticket-edit.inc.php index 5f5a0a4d2e2e86816e64039bfb6ede821f5379b5..93fc68bad4f957eb3044b503eb5dc0c6df8ae4d9 100644 --- a/include/staff/ticket-edit.inc.php +++ b/include/staff/ticket-edit.inc.php @@ -50,7 +50,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$ticket->getUpdateInfo()); </tr> <tr> <th colspan="2"> - <em><strong>Ticket Information</strong>: Due date overwrites SLA's grace period.</em> + <em><strong>Ticket Information</strong>: Due date overrides SLA's grace period.</em> </th> </tr> <tr> @@ -146,7 +146,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$ticket->getUpdateInfo()); $min=$hr=null; if($info['time']) list($hr, $min)=explode(':', $info['time']); - + echo Misc::timeDropdown($hr, $min, 'time'); ?> <font class="error"> <?php echo $errors['duedate']; ?> <?php echo $errors['time']; ?></font> diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php index 45f25805b215f158cf0496da5f4c61340e307737..d67243af734c466eebb31a264a53d013fe3356c0 100644 --- a/include/staff/ticket-view.inc.php +++ b/include/staff/ticket-view.inc.php @@ -54,7 +54,7 @@ if($ticket->isOverdue()) <?php if($thisstaff->canDeleteTickets()) { ?> <a id="ticket-delete" class="action-button" href="#delete"><i class="icon-trash"></i> Delete</a> <?php } ?> - <?php + <?php if($thisstaff->canCloseTickets()) { if($ticket->isOpen()) {?> <a id="ticket-close" class="action-button" href="#close"><i class="icon-remove-circle"></i> Close</a> @@ -63,18 +63,18 @@ if($ticket->isOverdue()) <a id="ticket-reopen" class="action-button" href="#reopen"><i class="icon-undo"></i> Reopen</a> <?php } ?> - <?php + <?php } ?> - <?php + <?php if($thisstaff->canEditTickets()) { ?> <a class="action-button" href="tickets.php?id=<?php echo $ticket->getId(); ?>&a=edit"><i class="icon-edit"></i> Edit</a> - <?php + <?php } ?> <?php if($ticket->isOpen() && !$ticket->isAssigned() && $thisstaff->canAssignTickets()) {?> <a id="ticket-claim" class="action-button" href="#claim"><i class="icon-user"></i> Claim</a> - + <?php }?> @@ -82,19 +82,19 @@ if($ticket->isOverdue()) <div id="action-dropdown-more" class="action-dropdown anchor-right"> <ul> - <?php + <?php if($ticket->isOpen() && ($dept && $dept->isManager($thisstaff))) { - + if($ticket->isAssigned()) { ?> <li><a id="ticket-release" href="#release"><i class="icon-user"></i> Release (unassign) Ticket</a></li> <?php } - + if(!$ticket->isOverdue()) { ?> <li><a id="ticket-overdue" href="#overdue"><i class="icon-bell"></i> Mark as Overdue</a></li> <?php } - + if($ticket->isAnswered()) { ?> <li><a id="ticket-unanswered" href="#unanswered"><i class="icon-circle-arrow-left"></i> Mark as Unanswered</a></li> <?php @@ -103,11 +103,11 @@ if($ticket->isOverdue()) <?php } } - - if($thisstaff->canBanEmails()) { + + if($thisstaff->canBanEmails()) { if(!$emailBanned) {?> <li><a id="ticket-banemail" href="#banemail"><i class="icon-ban-circle"></i> Ban Email (<?php echo $ticket->getEmail(); ?>)</a></li> - <?php + <?php } elseif($unbannable) { ?> <li><a id="ticket-banemail" href="#unbanemail"><i class="icon-undo"></i> Unban Email (<?php echo $ticket->getEmail(); ?>)</a></li> <?php @@ -179,13 +179,13 @@ if($ticket->isOverdue()) </tr> <tr> <th>Source:</th> - <td><?php + <td><?php echo Format::htmlchars($ticket->getSource()); if($ticket->getIP()) echo ' <span class="faded">('.$ticket->getIP().')</span>'; - + ?> </td> </tr> @@ -306,8 +306,8 @@ if(!$cfg->showNotesInline()) { ?> </td> </tr> <?php - if($note['attachments'] - && ($tentry=$ticket->getThreadEntry($note['id'])) + if($note['attachments'] + && ($tentry=$ticket->getThreadEntry($note['id'])) && ($links=$tentry->getAttachmentsLinks())) { ?> <tr> <td class="info" colspan="2"><?php echo $links; ?></td> @@ -332,6 +332,8 @@ if(!$cfg->showNotesInline()) { ?> $types[] = 'N'; if(($thread=$ticket->getThreadEntries($types))) { foreach($thread as $entry) { + if ($entry['body'] == '-') + $entry['body'] = '(EMPTY)'; ?> <table class="<?php echo $threadTypes[$entry['thread_type']]; ?>" cellspacing="0" cellpadding="1" width="940" border="0"> <tr> @@ -341,7 +343,7 @@ if(!$cfg->showNotesInline()) { ?> </tr> <tr><td colspan=3><?php echo Format::display($entry['body']); ?></td></tr> <?php - if($entry['attachments'] + if($entry['attachments'] && ($tentry=$ticket->getThreadEntry($entry['id'])) && ($links=$tentry->getAttachmentsLinks())) {?> <tr> @@ -518,7 +520,7 @@ if(!$cfg->showNotesInline()) { ?> <input type="hidden" name="locktime" value="<?php echo $cfg->getLockTime(); ?>"> <input type="hidden" name="a" value="postnote"> <table border="0" cellspacing="0" cellpadding="3"> - <?php + <?php if($errors['postnote']) {?> <tr> <td width="160"> </td> @@ -535,7 +537,7 @@ if(!$cfg->showNotesInline()) { ?> <span class="error">* <?php echo $errors['note']; ?></span></div> <textarea name="note" id="internal_note" cols="80" rows="9" wrap="soft"><?php echo $info['note']; ?></textarea><br> <div> - <span class="faded">Note title - summarry of the note (optional)</span> + <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']; ?>" > @@ -569,7 +571,7 @@ if(!$cfg->showNotesInline()) { ?> <option value="" selected="selected">— unchanged —</option> <?php $state = $info['state']; - if($ticket->isClosed()){ + if($ticket->isClosed()){ echo sprintf('<option value="open" %s>Reopen Ticket</option>', ($state=='reopen')?'selected="selelected"':''); } else { @@ -579,12 +581,12 @@ if(!$cfg->showNotesInline()) { ?> /* Ticket open - states */ echo '<option value="" disabled="disabled">— Ticket States —</option>'; - + //Answer - state if($ticket->isAnswered()) echo sprintf('<option value="unanswered" %s>Mark As Unanswered</option>', ($state=='unanswered')?'selected="selelected"':''); - else + else echo sprintf('<option value="answered" %s>Mark As Answered</option>', ($state=='answered')?'selected="selelected"':''); @@ -681,7 +683,7 @@ if(!$cfg->showNotesInline()) { ?> <input type="hidden" name="id" value="<?php echo $ticket->getId(); ?>"> <input type="hidden" name="a" value="assign"> <table border="0" cellspacing="0" cellpadding="3"> - + <?php if($errors['assign']) { ?> @@ -837,7 +839,7 @@ if(!$cfg->showNotesInline()) { ?> </p> <p class="confirm-action" style="display:none;" id="answered-confirm"> Are you sure want to flag the ticket as <b>answered</b>? - </p> + </p> <p class="confirm-action" style="display:none;" id="unanswered-confirm"> Are you sure want to flag the ticket as <b>unanswered</b>? </p> diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php index ed5916196bcbb731cd1ced3ed4db19e4a2ea80bb..ac38d5ff8c87eddf718c1a547315f292fed5ccb7 100644 --- a/include/staff/tickets.inc.php +++ b/include/staff/tickets.inc.php @@ -119,21 +119,12 @@ if($search): //This sucks..mass scan! search anything that moves! $deep_search=true; - if($_REQUEST['stype'] && $_REQUEST['stype']=='FT') { //Using full text on big fields. - $qwhere.=" AND ( ticket.email LIKE '%$queryterm%'". - " OR ticket.name LIKE '%$queryterm%'". - " OR ticket.subject LIKE '%$queryterm%'". - " OR thread.title LIKE '%$queryterm%'". - " OR MATCH(thread.body) AGAINST('$queryterm')". - ' ) '; - }else{ - $qwhere.=" AND ( ticket.email LIKE '%$queryterm%'". - " OR ticket.name LIKE '%$queryterm%'". - " OR ticket.subject LIKE '%$queryterm%'". - " OR thread.body LIKE '%$queryterm%'". - " OR thread.title LIKE '%$queryterm%'". - ' ) '; - } + $qwhere.=" AND ( ticket.email LIKE '%$queryterm%'". + " OR ticket.name LIKE '%$queryterm%'". + " OR ticket.subject LIKE '%$queryterm%'". + " OR thread.body LIKE '%$queryterm%'". + " OR thread.title LIKE '%$queryterm%'". + ' ) '; } } //department diff --git a/include/staff/tpl.inc.php b/include/staff/tpl.inc.php index 13dcf25718874fb7eb48371ad68daba9ff078312..c5954090dbd4935f5161851620dbed063c98b13c 100644 --- a/include/staff/tpl.inc.php +++ b/include/staff/tpl.inc.php @@ -1,36 +1,72 @@ <?php -$msgtemplates=Template::message_templates(); $info=Format::htmlchars(($errors && $_POST)?$_POST:$_REQUEST); -$info['tpl']=($info['tpl'] && $msgtemplates[$info['tpl']])?$info['tpl']:'ticket_autoresp'; + +if (is_a($template, EmailTemplateGroup)) { + // New template implementation + $id = 0; + $tpl_id = $template->getId(); + $name = $template->getName(); + $group = $template; + $selected = $_REQUEST['code_name']; + $action = 'implement'; + $extras = array('code_name'=>$selected, 'tpl_id'=>$tpl_id); + $msgtemplates=$template->all_names; + // Attempt to lookup the default data if it is defined + $default = @$template->getMsgTemplate($selected); + if ($default) { + $info['subj'] = $default->getSubject(); + $info['body'] = $default->getBody(); + } +} else { + // Template edit + $id = $template->getId(); + $tpl_id = $template->getTplId(); + $name = $template->getGroup()->getName(); + $group = $template->getGroup(); + $selected = $template->getCodeName(); + $action = 'updatetpl'; + $extras = array(); + $msgtemplates=$template->getGroup()->all_names; + $info=array_merge(array('subj'=>$template->getSubject(), 'body'=>$template->getBody()),$info); +} +$info['tpl']=($info['tpl'] && $msgtemplates[$info['tpl']])?$info['tpl']:'ticket.autoresp'; $tpl=$msgtemplates[$info['tpl']]; -$info=array_merge($template->getMsgTemplate($info['tpl']),$info); ?> -<h2>Email Template Message - <span><?php echo $template->getName(); ?></span></h2> +<h2>Email Template Message - <span><?php echo $name; ?></span></h2> <div style="padding-top:10px;padding-bottom:5px;"> <form method="get" action="templates.php"> - <input type="hidden" name="id" value="<?php echo $template->getId(); ?>"> <input type="hidden" name="a" value="manage"> Message Template: - <select id="tpl_options" name="tpl" style="width:300px;"> + <select id="tpl_options" name="id" style="width:300px;"> <option value="">— Select Setting Group —</option> <?php - foreach($msgtemplates as $k=>$v) { - $sel=($info['tpl']==$k)?'selected="selected"':''; - echo sprintf('<option value="%s" %s>%s</option>',$k,$sel,$v['name']); + foreach($group->getTemplates() as $cn=>$t) { + $nfo=$t->getDescription(); + if (!$nfo['name']) + continue; + $sel=($selected==$cn)?'selected="selected"':''; + echo sprintf('<option value="%s" %s>%s</option>', + $t->getId(),$sel,$nfo['name']); } + if ($id == 0) { ?> + <option selected="selected" value="<?php echo $id; ?>"><?php + echo $msgtemplates[$selected]['name']; ?></option> + <?php } ?> </select> <input type="submit" value="Go"> <font color="red"><?php echo $errors['tpl']; ?></font> </form> </div> -<form action="templates.php?id=<?php echo $template->getId(); ?>" method="post" id="save"> +<form action="templates.php?id=<?php echo $id; ?>" method="post" id="save"> <?php csrf_token(); ?> -<input type="hidden" name="id" value="<?php echo $template->getId(); ?>"> -<input type="hidden" name="tpl" value="<?php echo $info['tpl']; ?>"> +<?php foreach ($extras as $k=>$v) { ?> + <input type="hidden" name="<?php echo $k; ?>" value="<?php echo $v; ?>" /> +<?php } ?> +<input type="hidden" name="id" value="<?php echo $id; ?>"> <input type="hidden" name="a" value="manage"> -<input type="hidden" name="do" value="updatetpl"> +<input type="hidden" name="do" value="<?php echo $action; ?>"> <table class="form_table settings_table" width="940" border="0" cellspacing="0" cellpadding="2"> <thead> @@ -59,6 +95,7 @@ $info=array_merge($template->getMsgTemplate($info['tpl']),$info); <p style="padding-left:210px;"> <input class="button" type="submit" name="submit" value="Save Changes"> <input class="button" type="reset" name="reset" value="Reset Changes"> - <input class="button" type="button" name="cancel" value="Cancel Changes" onclick='window.location.href="templates.php?id=<?php echo $template->getId(); ?>"'> + <input class="button" type="button" name="cancel" value="Cancel Changes" + onclick='window.location.href="templates.php?tpl_id=<?php echo $tpl_id; ?>"'> </p> </form> diff --git a/include/upgrader/aborted.inc.php b/include/upgrader/aborted.inc.php index 44e065b14f87ede8644f284ff1a923e08963f4e1..8b8d8cbdb387c36d51581e58c77096e726f30bd1 100644 --- a/include/upgrader/aborted.inc.php +++ b/include/upgrader/aborted.inc.php @@ -20,7 +20,7 @@ if(!defined('OSTSCPINC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access D echo '<b><font color="red">Internal error occurred - get technical help.</font></b>'; } ?> - <p><b>For detailed - please view <a href="logs.php">system logs</a> or check your email.</b></p> + <p><b>For more detailed information, please view <a href="logs.php">system logs</a> or check your email.</b></p> <br> <p>Please, refer to the <a target="_blank" href="http://osticket.com/wiki/Upgrade_and_Migration">Upgrade Guide</a> on the wiki for more information.</p> </div> diff --git a/include/upgrader/done.inc.php b/include/upgrader/done.inc.php index f4a55538991d4b8fd6f6814bcb7f08c31e390b7f..1c01fb7cba5b353d8b3dc92385f063aa8903da88 100644 --- a/include/upgrader/done.inc.php +++ b/include/upgrader/done.inc.php @@ -7,7 +7,7 @@ $_SESSION['ost_upgrader']=null; <div id="main"> <h1 style="color:green;">Upgrade Completed!</h1> <div id="intro"> - <p>Congratulations osTicket upgrade has been completed successfully.</p> + <p>Congratulations! osTicket upgrade has completed successfully.</p> <p>Please refer to <a href="http://osticket.com/wiki/Release_Notes" target="_blank">Release Notes</a> for more information about changes and/or new features.</p> </div> <p>Once again, thank you for choosing osTicket.</p> @@ -20,7 +20,7 @@ $_SESSION['ost_upgrader']=null; </div> <div id="sidebar"> <h3>What's Next?</h3> - <p><b>Post-upgrade</b>: You can now go to <a href="scp/settings.php" target="_blank">Admin Panel</a> to enable the system and explore the new features. For complete and upto date release notes see <a href="http://osticket.com/wiki/Release_Notes" target="_blank">osTicket wiki</a></p> + <p><b>Post-upgrade</b>: You can now go to <a href="scp/settings.php" target="_blank">Admin Panel</a> to enable the system and explore the new features. For complete and up-to-date release notes, see <a href="http://osticket.com/wiki/Release_Notes" target="_blank">osTicket wiki</a></p> <p><b>Stay up to date</b>: It's important to keep your osTicket installation up to date. Get announcements, security updates and alerts delivered directly to you! <a target="_blank" href="http://osticket.com/support/subscribe.php">Get in the loop</a> today and stay informed!</p> <p><b>Commercial support available</b>: Get guidance and hands-on expertise to address unique challenges and make sure your osTicket runs smoothly, efficiently, and securely. <a target="_blank" href="http://osticket.com/support/commercial_support.php.php">Learn More!</a></p> diff --git a/include/upgrader/msg/upgraded.txt b/include/upgrader/msg/upgraded.txt new file mode 100644 index 0000000000000000000000000000000000000000..84554ec313eaad08b45fd64e7e7bfd781263bc50 --- /dev/null +++ b/include/upgrader/msg/upgraded.txt @@ -0,0 +1,11 @@ +osTicket upgraded successfully! Please refer to the Release Notes (http://osticket.com/wiki/Release_Notes) for more information about changes and new features. + +Be sure to join osTicket forum (http://osticket.com/forums) and our mailing list (http://osticket.com/updates) , if you haven't done so already, to stay up to date on announcements, security updates and alerts! Your contribution to osTicket community will be appreciated! + +The osTicket team is committed to providing support to all users through our free online resources and a full range of commercial support packages and services. For more information, or to discuss your needs, please contact us today at http://osticket.com/support/. Any feedback will be appreciated! + +If managing and upgrading this osTicket installation is daunting, you can try osTicket as a hosted service at http://www.supportsystem.com/ -- no upgrading ever, and we can import your data! With SupportSystem's turnkey infrastructure, you get osTicket at its best, leaving you free to focus on your customers without the burden of making sure the application is stable, maintained, and secure. + +- +osTicket Team +http://osticket.com/ diff --git a/include/upgrader/prereq.inc.php b/include/upgrader/prereq.inc.php index c08c7fedcc3618ab8f9f1e368423f5cf1deaf36e..56a9d347ea0cef5c54c9cd6ac1701cdddf72c5dc 100644 --- a/include/upgrader/prereq.inc.php +++ b/include/upgrader/prereq.inc.php @@ -7,7 +7,7 @@ if(!defined('OSTSCPINC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access D <div id="main"> <div id="intro"> <p>Thank you for being a loyal osTicket user!</p> - <p>The upgrade wizard will guide you every step of the way in the upgrade process. While we try to ensure that the upgrade process is straightforward and painless, we can't guarantee it will be the case for every user.</p> + <p>The upgrade wizard will guide you every step of the way through the upgrade process. While we try to ensure that the upgrade process is straightforward and painless, we can't guarantee this will be the case for every user.</p> </div> <h2>Getting ready!</h2> <p>Before we begin, we'll check your server configuration to make sure you meet the minimum requirements to run the latest version of osTicket.</p> @@ -19,11 +19,11 @@ if(!defined('OSTSCPINC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access D <li class="<?php echo $upgrader->check_mysql()?'yes':'no'; ?>"> MySQL v4.4 or greater - (<small><b><?php echo extension_loaded('mysql')?'module loaded':'missing!'; ?></b></small>)</li> </ul> - <h3>Higly Recommended:</h3> - We hightly recommend that you follow the steps below. + <h3>Highly Recommended:</h3> + We highly recommend that you follow the steps below. <ul> - <li>Backup the current database, if you haven't done so already.</li> - <li>Be patient the upgrade process will take a couple of seconds.</li> + <li>Back up the current database if you haven't done so already.</li> + <li>Be patient. The upgrade process will take a couple of seconds.</li> </ul> <div id="bar"> <form method="post" action="upgrade.php" id="prereq"> @@ -35,10 +35,10 @@ if(!defined('OSTSCPINC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access D </div> <div id="sidebar"> <h3>Upgrade Tips</h3> - <p>1. Remember to backup your osTicket database</p> - <p>2. Refer to <a href="http://osticket.com/wiki/Upgrade_and_Migration" target="_blank">Upgrade Guide</a> for the latest tips</a> + <p>1. Remember to back up your osTicket database</p> + <p>2. Refer to the <a href="http://osticket.com/wiki/Upgrade_and_Migration" target="_blank">Upgrade Guide</a> for the latest tips</a> <p>3. If you experience any problems, you can always restore your files/dabase backup.</p> - <p>4. We can help, feel free to <a href="http://osticket.com/support/" target="_blank">contact us </a> for professional help.</p> + <p>4. We can help! Feel free to <a href="http://osticket.com/support/" target="_blank">contact us </a> for professional help.</p> </div> <div class="clear"></div> diff --git a/include/upgrader/rename.inc.php b/include/upgrader/rename.inc.php index 0b649bfa0a3dc1902e0aad168573719eab905bde..b8fca94bc42351aad885c7fa77733f6f05210df6 100644 --- a/include/upgrader/rename.inc.php +++ b/include/upgrader/rename.inc.php @@ -27,7 +27,7 @@ if(!defined('OSTSCPINC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access D <div id="sidebar"> <h3>Need Help?</h3> <p> - If you are looking for a greater level of support, we provide <u>professional upgrade</u> and commercial support with guaranteed response times, and access to the core development team. We can also help customize osTicket or even add new features to the system to meet your unique needs. <a target="_blank" href="http://osticket.com/support/professional_services.php">Learn More!</a> + If you are looking for a greater level of support, we provide <u>professional upgrade</u> and commercial support with guaranteed response times and access to the core development team. We can also help customize osTicket or even add new features to the system to meet your unique needs. <a target="_blank" href="http://osticket.com/support/professional_services.php">Learn More!</a> </p> </div> <div class="clear"></div> diff --git a/include/upgrader/streams/core.sig b/include/upgrader/streams/core.sig new file mode 100644 index 0000000000000000000000000000000000000000..417c5384577ee48f82f2d201e29d8be11d21a0d2 --- /dev/null +++ b/include/upgrader/streams/core.sig @@ -0,0 +1 @@ +16fcef4a13d6475a5f8bfef462b548e2 diff --git a/include/upgrader/sql/00ff231f-9f3b454c.patch.sql b/include/upgrader/streams/core/00ff231f-9f3b454c.patch.sql similarity index 100% rename from include/upgrader/sql/00ff231f-9f3b454c.patch.sql rename to include/upgrader/streams/core/00ff231f-9f3b454c.patch.sql diff --git a/include/upgrader/sql/02decaa2-60fcbee1.patch.sql b/include/upgrader/streams/core/02decaa2-60fcbee1.patch.sql similarity index 100% rename from include/upgrader/sql/02decaa2-60fcbee1.patch.sql rename to include/upgrader/streams/core/02decaa2-60fcbee1.patch.sql diff --git a/include/upgrader/sql/15719536-dd0022fb.patch.sql b/include/upgrader/streams/core/15719536-dd0022fb.patch.sql similarity index 100% rename from include/upgrader/sql/15719536-dd0022fb.patch.sql rename to include/upgrader/streams/core/15719536-dd0022fb.patch.sql diff --git a/include/upgrader/sql/15af7cd3-98ae1ed2.patch.sql b/include/upgrader/streams/core/15af7cd3-98ae1ed2.patch.sql similarity index 100% rename from include/upgrader/sql/15af7cd3-98ae1ed2.patch.sql rename to include/upgrader/streams/core/15af7cd3-98ae1ed2.patch.sql diff --git a/include/upgrader/sql/15b30765-dd0022fb.cleanup.sql b/include/upgrader/streams/core/15b30765-dd0022fb.cleanup.sql similarity index 100% rename from include/upgrader/sql/15b30765-dd0022fb.cleanup.sql rename to include/upgrader/streams/core/15b30765-dd0022fb.cleanup.sql diff --git a/include/upgrader/sql/15b30765-dd0022fb.patch.sql b/include/upgrader/streams/core/15b30765-dd0022fb.patch.sql similarity index 94% rename from include/upgrader/sql/15b30765-dd0022fb.patch.sql rename to include/upgrader/streams/core/15b30765-dd0022fb.patch.sql index 0006139d679fdf9af8924b639b488e3edc0f2c34..796fbf296b6c5b7e60995c655d0c73262d6036a1 100644 --- a/include/upgrader/sql/15b30765-dd0022fb.patch.sql +++ b/include/upgrader/streams/core/15b30765-dd0022fb.patch.sql @@ -3,7 +3,7 @@ * @signature dd0022fb14892c0bb6a9700392df2de7 * * Migrate file attachment data from %file to %file_chunk - * + * */ DROP TABLE IF EXISTS `%TABLE_PREFIX%file_chunk`; @@ -12,7 +12,7 @@ CREATE TABLE `%TABLE_PREFIX%file_chunk` ( `chunk_id` int(11) NOT NULL, `filedata` longblob NOT NULL, PRIMARY KEY (`file_id`, `chunk_id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; INSERT INTO `%TABLE_PREFIX%file_chunk` (`file_id`, `chunk_id`, `filedata`) SELECT `id`, 0, `filedata` diff --git a/include/upgrader/streams/core/15b30765-dd0022fb.task.php b/include/upgrader/streams/core/15b30765-dd0022fb.task.php new file mode 100644 index 0000000000000000000000000000000000000000..bb2df98bdf338eefc81cc0c29777ea5b95ade1ce --- /dev/null +++ b/include/upgrader/streams/core/15b30765-dd0022fb.task.php @@ -0,0 +1,233 @@ +<?php +/********************************************************************* + AttachmentMigrater + + Attachment migration from file-based attachments in pre-1.7 to + database-backed attachments in osTicket v1.7. This class provides the + hardware to find and retrieve old attachments and move them into the new + database scheme with the data in the actual database. + + Copyright (c) 2006-2013 osTicket + http://www.osticket.com + + Released under the GNU General Public License WITHOUT ANY WARRANTY. + See LICENSE.TXT for details. + + vim: expandtab sw=4 ts=4 sts=4: +**********************************************************************/ +require_once INCLUDE_DIR.'class.migrater.php'; +require_once(INCLUDE_DIR.'class.file.php'); + +class AttachmentMigrater extends MigrationTask { + var $description = "Attachment migration from disk to database"; + + var $queue; + var $skipList; + var $errorList = array(); + + function sleep() { + return array('queue'=>$this->queue, 'skipList'=>$this->skipList); + } + function wakeup($stuff) { + $this->queue = $stuff['queue']; + $this->skipList = $stuff['skipList']; + } + + function run($max_time) { + $this->do_batch($max_time * 0.9, 100); + } + + function isFinished() { + return $this->getQueueLength() == 0; + } + + /** + * Process the migration for a unit of time. This will be used to + * overcome the execution time restriction of PHP. This instance can be + * stashed in a session and have this method called periodically to + * process another batch of attachments + * + * Returns: + * Number of pending attachments to migrate. + */ + function do_batch($time=30, $max=0) { + + if(!$this->queueAttachments($max) || !$this->getQueueLength()) + return 0; + + $this->setStatus("{$this->getQueueLength()} attachments remaining"); + + $count = 0; + $start = Misc::micro_time(); + while ($this->getQueueLength() && (Misc::micro_time()-$start) < $time) + if($this->next() && $max && ++$count>=$max) + break; + + return $this->queueAttachments($max); + + } + + function getSkipList() { + return $this->skipList; + } + + function enqueue($fileinfo) { + $this->queue[] = $fileinfo; + } + + function getQueue() { + return $this->queue; + } + + function getQueueLength() { return count($this->queue); } + /** + * Processes the next item on the work queue. Emits a JSON messages to + * indicate current progress. + * + * Returns: + * TRUE/NULL if the migration was successful + */ + function next() { + # Fetch next item -- use the last item so the array indices don't + # need to be recalculated for every shift() operation. + $info = array_pop($this->queue); + # Attach file to the ticket + if (!($info['data'] = @file_get_contents($info['path']))) { + # Continue with next file + return $this->skip($info['attachId'], + sprintf('%s: Cannot read file contents', $info['path'])); + } + # Get the mime/type of each file + # XXX: Use finfo_buffer for PHP 5.3+ + if(function_exists('mime_content_type')) { + //XXX: function depreciated in newer versions of PHP!!!!! + $info['type'] = mime_content_type($info['path']); + } elseif (function_exists('finfo_file')) { // PHP 5.3.0+ + $finfo = finfo_open(FILEINFO_MIME_TYPE); + $info['type'] = finfo_file($finfo, $info['path']); + } + # TODO: Add extension-based mime-type lookup + + if (!($fileId = AttachmentFile::save($info))) { + return $this->skip($info['attachId'], + sprintf('%s: Unable to migrate attachment', $info['path'])); + } + # Update the ATTACHMENT_TABLE record to set file_id + db_query('update '.TICKET_ATTACHMENT_TABLE + .' set file_id='.db_input($fileId) + .' where attach_id='.db_input($info['attachId'])); + # Remove disk image of the file. If this fails, the migration for + # this file would not be retried, because the file_id in the + # TICKET_ATTACHMENT_TABLE has a nonzero value now + if (!@unlink($info['path'])) //XXX: what should we do on failure? + $this->error( + sprintf('%s: Unable to remove file from disk', + $info['path'])); + # TODO: Log an internal note to the ticket? + return true; + } + /** + * From (class Ticket::fixAttachments), used to detect the locations of + * attachment files + */ + /* static */ function queueAttachments($limit=0){ + global $cfg, $ost; + + # Since the queue is persistent - we want to make sure we get to empty + # before we find more attachments. + if(($qc=$this->getQueueLength())) + return $qc; + + $sql='SELECT attach_id, file_name, file_key, Ti.created' + .' FROM '.TICKET_ATTACHMENT_TABLE.' TA' + .' INNER JOIN '.TICKET_TABLE.' Ti ON (Ti.ticket_id=TA.ticket_id)' + .' WHERE NOT file_id '; + + if(($skipList=$this->getSkipList())) + $sql.= ' AND attach_id NOT IN('.implode(',', db_input($skipList)).')'; + + if($limit && is_numeric($limit)) + $sql.=' LIMIT '.$limit; + + //XXX: Do a hard fail or error querying the database? + if(!($res=db_query($sql))) + return $this->error('Unable to query DB for attached files to migrate!'); + + // Force the log message to the database + $ost->logDebug("Attachment migration", 'Found '.db_num_rows($res) + .' attachments to migrate', true); + if(!db_num_rows($res)) + return 0; //Nothing else to do!! + + $dir=$cfg->getUploadDir(); + if(!$dir || !is_dir($dir)) //XXX: Abort the upgrade??? Attachments are obviously critical! + return $this->error("Attachment directory [$dir] is invalid - aborting attachment migration"); + + //Clear queue + $this->queue = array(); + while (list($id,$name,$key,$created)=db_fetch_row($res)) { + $month=date('my',strtotime($created)); + $info=array( + 'name'=> $name, + 'attachId'=> $id, + ); + $filename15=sprintf("%s/%s_%s",rtrim($dir,'/'),$key,$name); + $filename16=sprintf("%s/%s/%s_%s",rtrim($dir,'/'),$month,$key,$name); //new destination. + if (file_exists($filename15)) { + $info['path'] = $filename15; + } elseif (file_exists($filename16)) { + $info['path'] = $filename16; + } else { + # XXX Cannot find file for attachment + $this->skip($id, + sprintf('%s: Unable to locate attachment file', + $name)); + # No need to further process this file + continue; + } + # TODO: Get the size and mime/type of each file. + # + # NOTE: If filesize() fails and file_get_contents() doesn't, + # then the AttachmentFile::save() method will automatically + # estimate the filesize based on the length of the string data + # received in $info['data'] -- ie. no need to do that here. + # + # NOTE: The size is done here because it should be quick to + # lookup out of file inode already loaded. The mime/type may + # take a while because it will require a second IO to read the + # file data. To ensure this will finish before the + # max_execution_time, perform the type match in the ::next() + # method since the entire file content will be read there + # anyway. + $info['size'] = @filesize($info['path']); + # Coroutines would be nice .. + $this->enqueue($info); + } + + return $this->queueAttachments($limit); + } + + function skip($attachId, $error) { + + $this->skipList[] = $attachId; + + return $this->error($error." (ID #$attachId)"); + } + + function error($what) { + global $ost; + + $this->errors++; + $this->errorList[] = $what; + // Log the error but don't send the alert email + $ost->logError('Upgrader: Attachment Migrater', $what, false); + # Assist in returning FALSE for inline returns with this method + return false; + } + function getErrors() { + return $this->errorList; + } +} + +return 'AttachmentMigrater'; +?> diff --git a/include/upgrader/sql/1da1bcba-15b30765.patch.sql b/include/upgrader/streams/core/1da1bcba-15b30765.patch.sql similarity index 100% rename from include/upgrader/sql/1da1bcba-15b30765.patch.sql rename to include/upgrader/streams/core/1da1bcba-15b30765.patch.sql diff --git a/include/upgrader/sql/2e20a0eb-98ae1ed2.patch.sql b/include/upgrader/streams/core/2e20a0eb-98ae1ed2.patch.sql similarity index 100% rename from include/upgrader/sql/2e20a0eb-98ae1ed2.patch.sql rename to include/upgrader/streams/core/2e20a0eb-98ae1ed2.patch.sql diff --git a/include/upgrader/sql/2e7531a2-d0e37dca.patch.sql b/include/upgrader/streams/core/2e7531a2-d0e37dca.patch.sql similarity index 100% rename from include/upgrader/sql/2e7531a2-d0e37dca.patch.sql rename to include/upgrader/streams/core/2e7531a2-d0e37dca.patch.sql diff --git a/include/upgrader/streams/core/32de1766-852ca89e.patch.sql b/include/upgrader/streams/core/32de1766-852ca89e.patch.sql new file mode 100644 index 0000000000000000000000000000000000000000..79b9a1e6c7563958dab12e271e466267ae817b00 --- /dev/null +++ b/include/upgrader/streams/core/32de1766-852ca89e.patch.sql @@ -0,0 +1,123 @@ +/** + * @version v1.7.1 + * @signature 852ca89e1440e736d763b3b87f039bd7 + * + * - Changes config table to be key/value based and allows for + * configuration key clobbering by defining a namespace for the keys. The + * current configuration settings are stored in the 'core' namespace + */ + +DROP TABLE IF EXISTS `%TABLE_PREFIX%_config`; +CREATE TABLE `%TABLE_PREFIX%_config` ( + `id` int(11) unsigned NOT NULL auto_increment, + `namespace` varchar(64) NOT NULL, + `key` varchar(64) NOT NULL, + `value` text NOT NULL, + `updated` timestamp NOT NULL default CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY (`namespace`, `key`) +) DEFAULT CHARSET=utf8; + +INSERT INTO `%TABLE_PREFIX%_config` (`key`, `value`, `namespace`) VALUES + ('isonline', (SELECT `isonline` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('enable_daylight_saving', (SELECT `enable_daylight_saving` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('staff_ip_binding', (SELECT `staff_ip_binding` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('staff_max_logins', (SELECT `staff_max_logins` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('staff_login_timeout', (SELECT `staff_login_timeout` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('staff_session_timeout', (SELECT `staff_session_timeout` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('passwd_reset_period', (SELECT `passwd_reset_period` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('client_max_logins', (SELECT `client_max_logins` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('client_login_timeout', (SELECT `client_login_timeout` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('client_session_timeout', (SELECT `client_session_timeout` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('max_page_size', (SELECT `max_page_size` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('max_open_tickets', (SELECT `max_open_tickets` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('max_file_size', (SELECT `max_file_size` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('max_user_file_uploads', (SELECT `max_user_file_uploads` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('max_staff_file_uploads', (SELECT `max_staff_file_uploads` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('autolock_minutes', (SELECT `autolock_minutes` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('overdue_grace_period', (SELECT `overdue_grace_period` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('alert_email_id', (SELECT `alert_email_id` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('default_email_id', (SELECT `default_email_id` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('default_dept_id', (SELECT `default_dept_id` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('default_sla_id', (SELECT `default_sla_id` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('default_priority_id', (SELECT `default_priority_id` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('default_template_id', (SELECT `default_template_id` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('default_timezone_id', (SELECT `default_timezone_id` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('default_smtp_id', (SELECT `default_smtp_id` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('allow_email_spoofing', (SELECT `allow_email_spoofing` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('clickable_urls', (SELECT `clickable_urls` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('allow_priority_change', (SELECT `allow_priority_change` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('use_email_priority', (SELECT `use_email_priority` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('enable_kb', (SELECT `enable_kb` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('enable_premade', (SELECT `enable_premade` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('enable_captcha', (SELECT `enable_captcha` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('enable_auto_cron', (SELECT `enable_auto_cron` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('enable_mail_polling', (SELECT `enable_mail_polling` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('send_sys_errors', (SELECT `send_sys_errors` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('send_sql_errors', (SELECT `send_sql_errors` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('send_mailparse_errors', (SELECT `send_mailparse_errors` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('send_login_errors', (SELECT `send_login_errors` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('save_email_headers', (SELECT `save_email_headers` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('strip_quoted_reply', (SELECT `strip_quoted_reply` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('log_ticket_activity', (SELECT `log_ticket_activity` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('ticket_autoresponder', (SELECT `ticket_autoresponder` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('message_autoresponder', (SELECT `message_autoresponder` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('ticket_notice_active', (SELECT `ticket_notice_active` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('ticket_alert_active', (SELECT `ticket_alert_active` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('ticket_alert_admin', (SELECT `ticket_alert_admin` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('ticket_alert_dept_manager', (SELECT `ticket_alert_dept_manager` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('ticket_alert_dept_members', (SELECT `ticket_alert_dept_members` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('message_alert_active', (SELECT `message_alert_active` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('message_alert_laststaff', (SELECT `message_alert_laststaff` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('message_alert_assigned', (SELECT `message_alert_assigned` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('message_alert_dept_manager', (SELECT `message_alert_dept_manager` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('note_alert_active', (SELECT `note_alert_active` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('note_alert_laststaff', (SELECT `note_alert_laststaff` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('note_alert_assigned', (SELECT `note_alert_assigned` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('note_alert_dept_manager', (SELECT `note_alert_dept_manager` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('transfer_alert_active', (SELECT `transfer_alert_active` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('transfer_alert_assigned', (SELECT `transfer_alert_assigned` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('transfer_alert_dept_manager', (SELECT `transfer_alert_dept_manager` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('transfer_alert_dept_members', (SELECT `transfer_alert_dept_members` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('overdue_alert_active', (SELECT `overdue_alert_active` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('overdue_alert_assigned', (SELECT `overdue_alert_assigned` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('overdue_alert_dept_manager', (SELECT `overdue_alert_dept_manager` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('overdue_alert_dept_members', (SELECT `overdue_alert_dept_members` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('assigned_alert_active', (SELECT `assigned_alert_active` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('assigned_alert_staff', (SELECT `assigned_alert_staff` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('assigned_alert_team_lead', (SELECT `assigned_alert_team_lead` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('assigned_alert_team_members', (SELECT `assigned_alert_team_members` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('auto_assign_reopened_tickets', (SELECT `auto_assign_reopened_tickets` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('show_related_tickets', (SELECT `show_related_tickets` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('show_assigned_tickets', (SELECT `show_assigned_tickets` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('show_answered_tickets', (SELECT `show_answered_tickets` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('show_notes_inline', (SELECT `show_notes_inline` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('hide_staff_name', (SELECT `hide_staff_name` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('overlimit_notice_active', (SELECT `overlimit_notice_active` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('email_attachments', (SELECT `email_attachments` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('allow_attachments', (SELECT `allow_attachments` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('allow_email_attachments', (SELECT `allow_email_attachments` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('allow_online_attachments', (SELECT `allow_online_attachments` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('allow_online_attachments_onlogin', (SELECT `allow_online_attachments_onlogin` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('random_ticket_ids', (SELECT `random_ticket_ids` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('log_level', (SELECT `log_level` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('log_graceperiod', (SELECT `log_graceperiod` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('upload_dir', (SELECT `upload_dir` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('allowed_filetypes', (SELECT `allowed_filetypes` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('time_format', (SELECT `time_format` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('date_format', (SELECT `date_format` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('datetime_format', (SELECT `datetime_format` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('daydatetime_format', (SELECT `daydatetime_format` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('reply_separator', (SELECT `reply_separator` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('admin_email', (SELECT `admin_email` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('helpdesk_title', (SELECT `helpdesk_title` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('helpdesk_url', (SELECT `helpdesk_url` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core') +, ('schema_signature', (SELECT `schema_signature` FROM `%TABLE_PREFIX%config` WHERE `id` = 1), 'core'); + +DROP TABLE `%TABLE_PREFIX%config`; +ALTER TABLE `%TABLE_PREFIX%_config` RENAME TO `%TABLE_PREFIX%config`; + +-- Finished with patch +UPDATE `%TABLE_PREFIX%config` + SET `value` = '852ca89e1440e736d763b3b87f039bd7' + WHERE `key` = 'schema_signature' AND `namespace` = 'core'; diff --git a/include/upgrader/sql/435c62c3-2e7531a2.cleanup.sql b/include/upgrader/streams/core/435c62c3-2e7531a2.cleanup.sql similarity index 100% rename from include/upgrader/sql/435c62c3-2e7531a2.cleanup.sql rename to include/upgrader/streams/core/435c62c3-2e7531a2.cleanup.sql diff --git a/include/upgrader/sql/435c62c3-2e7531a2.patch.sql b/include/upgrader/streams/core/435c62c3-2e7531a2.patch.sql similarity index 88% rename from include/upgrader/sql/435c62c3-2e7531a2.patch.sql rename to include/upgrader/streams/core/435c62c3-2e7531a2.patch.sql index 760a6b0c3679bad02e6dd5f7d4c09089a2ddd38e..fd75833c30ca760b971aad04f7c0ef47bf936426 100644 --- a/include/upgrader/sql/435c62c3-2e7531a2.patch.sql +++ b/include/upgrader/streams/core/435c62c3-2e7531a2.patch.sql @@ -10,19 +10,19 @@ CREATE TABLE `%TABLE_PREFIX%group_dept_access` ( `dept_id` int(10) unsigned NOT NULL default '0', UNIQUE KEY `group_dept` (`group_id`,`dept_id`), KEY `dept_id` (`dept_id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; -- Extend membership to groups ALTER TABLE `%TABLE_PREFIX%department` ADD `group_membership` tinyint( 1 ) unsigned NOT NULL DEFAULT '0' AFTER `ispublic`; -- Fix teams dates... -UPDATE `%TABLE_PREFIX%team` +UPDATE `%TABLE_PREFIX%team` SET `created`=IF(TO_DAYS(`created`), `created`, IF(TO_DAYS(`updated`), `updated`, NOW())), `updated`=IF(TO_DAYS(`updated`), `updated`, NOW()); --- Fix groups dates... -UPDATE `%TABLE_PREFIX%groups` +-- Fix groups dates... +UPDATE `%TABLE_PREFIX%groups` SET `created`=IF(TO_DAYS(`created`), `created`, IF(TO_DAYS(`updated`), `updated`, NOW())), `updated`=IF(TO_DAYS(`updated`), `updated`, NOW()); diff --git a/include/upgrader/streams/core/435c62c3-2e7531a2.task.php b/include/upgrader/streams/core/435c62c3-2e7531a2.task.php new file mode 100644 index 0000000000000000000000000000000000000000..d80bc23a2e616248409c3c3780e3eedfca975799 --- /dev/null +++ b/include/upgrader/streams/core/435c62c3-2e7531a2.task.php @@ -0,0 +1,26 @@ +<?php +require_once INCLUDE_DIR.'class.migrater.php'; + +class MigrateGroupDeptAccess extends MigrationTask { + var $description = "Migrate department access for groups from v1.6"; + + function run($max_time) { + $this->setStatus("Migrating department access"); + + $res = db_query('SELECT group_id, dept_access FROM '.GROUP_TABLE); + if(!$res || !db_num_rows($res)) + return false; //No groups?? + + while(list($groupId, $access) = db_fetch_row($res)) { + $depts=array_filter(array_map('trim', explode(',', $access))); + foreach($depts as $deptId) { + $sql='INSERT INTO '.GROUP_DEPT_TABLE + .' SET dept_id='.db_input($deptId).', group_id='.db_input($groupId); + db_query($sql); + } + } + } +} + +return 'MigrateGroupDeptAccess'; +?> diff --git a/include/upgrader/sql/49478749-c2d2fabf.patch.sql b/include/upgrader/streams/core/49478749-c2d2fabf.patch.sql similarity index 100% rename from include/upgrader/sql/49478749-c2d2fabf.patch.sql rename to include/upgrader/streams/core/49478749-c2d2fabf.patch.sql diff --git a/include/upgrader/sql/522e5b78-02decaa2.patch.sql b/include/upgrader/streams/core/522e5b78-02decaa2.patch.sql similarity index 100% rename from include/upgrader/sql/522e5b78-02decaa2.patch.sql rename to include/upgrader/streams/core/522e5b78-02decaa2.patch.sql diff --git a/include/upgrader/sql/60fcbee1-f8856d56.patch.sql b/include/upgrader/streams/core/60fcbee1-f8856d56.patch.sql similarity index 98% rename from include/upgrader/sql/60fcbee1-f8856d56.patch.sql rename to include/upgrader/streams/core/60fcbee1-f8856d56.patch.sql index 624e88968ef3f673bea2f1a96d4b1733fbff9c8a..3d2a60c5d55fd776c39ab0d4d224d0e87e1d2e91 100644 --- a/include/upgrader/sql/60fcbee1-f8856d56.patch.sql +++ b/include/upgrader/streams/core/60fcbee1-f8856d56.patch.sql @@ -10,7 +10,7 @@ CREATE TABLE `%TABLE_PREFIX%ticket_event` ( `timestamp` datetime NOT NULL, KEY `ticket_state` (`ticket_id`, `state`, `timestamp`), KEY `ticket_stats` (`timestamp`, `state`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `%TABLE_PREFIX%ticket_history`; DROP TABLE IF EXISTS `%TABLE_PREFIX%history`; diff --git a/include/upgrader/streams/core/740428f9-8aeda901.patch.sql b/include/upgrader/streams/core/740428f9-8aeda901.patch.sql new file mode 100644 index 0000000000000000000000000000000000000000..686ff67dc0e25ea338ddcba7d8ca9c8bcf524fc3 --- /dev/null +++ b/include/upgrader/streams/core/740428f9-8aeda901.patch.sql @@ -0,0 +1,13 @@ +/** + * @version v1.7.1 + * @signature 8aeda901a16e08c3229f1ac6da568e02 + * + * - Transitional patch to fix DB ENGINE + * + * + * + */ + +UPDATE `%TABLE_PREFIX%config` + SET `value` = '8aeda901a16e08c3229f1ac6da568e02' + WHERE `key` = 'schema_signature' AND `namespace` = 'core'; diff --git a/include/upgrader/sql/7be60a84-522e5b78.patch.sql b/include/upgrader/streams/core/7be60a84-522e5b78.patch.sql similarity index 100% rename from include/upgrader/sql/7be60a84-522e5b78.patch.sql rename to include/upgrader/streams/core/7be60a84-522e5b78.patch.sql diff --git a/include/upgrader/streams/core/852ca89e-740428f9.patch.sql b/include/upgrader/streams/core/852ca89e-740428f9.patch.sql new file mode 100644 index 0000000000000000000000000000000000000000..a122ae1bf08466e55702d9e3ca2aa2912f9299f7 --- /dev/null +++ b/include/upgrader/streams/core/852ca89e-740428f9.patch.sql @@ -0,0 +1,141 @@ +/** + * @version v1.7.1 + * @signature 740428f9986da6ad85f88ec841b57bfe + * + * - Migrates the email template table to two tables, groups and templates. + * Templates organized in a separate table by group will allow for a more + * extensible model for email templates. + * + * - Add site page table and default templates required by the system to function. + * + */ + +DROP TABLE IF EXISTS `%TABLE_PREFIX%email_template_group`; +CREATE TABLE `%TABLE_PREFIX%email_template_group` ( + `tpl_id` int(11) NOT NULL auto_increment, + `isactive` tinyint(1) unsigned NOT NULL default '0', + `name` varchar(32) NOT NULL default '', + `notes` text, + `created` datetime NOT NULL, + `updated` timestamp NOT NULL, + PRIMARY KEY (`tpl_id`) +) DEFAULT CHARSET=utf8; + +INSERT INTO `%TABLE_PREFIX%email_template_group` + SELECT `tpl_id`, `isactive`, `name`, `notes`, `created`, `updated` + FROM `%TABLE_PREFIX%email_template`; + +DROP TABLE IF EXISTS `%TABLE_PREFIX%_email_template`; +CREATE TABLE `%TABLE_PREFIX%_email_template` ( + `id` int(11) unsigned NOT NULL auto_increment, + `tpl_id` int(11) unsigned NOT NULL, + `code_name` varchar(32) NOT NULL, + `subject` varchar(255) NOT NULL default '', + `body` text NOT NULL, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `template_lookup` (`tpl_id`, `code_name`) +) DEFAULT CHARSET=utf8; + +INSERT INTO `%TABLE_PREFIX%_email_template` + (`tpl_id`, `code_name`, `subject`, `body`, `created`, `updated`) + SELECT `tpl_id`, 'ticket.autoresp', ticket_autoresp_subj, ticket_autoresp_body, `created`, `updated` + FROM `%TABLE_PREFIX%email_template`; + +INSERT INTO `%TABLE_PREFIX%_email_template` + (`tpl_id`, `code_name`, `subject`, `body`, `created`, `updated`) + SELECT `tpl_id`, 'ticket.autoreply', ticket_autoreply_subj, ticket_autoreply_body, `created`, `updated` + FROM `%TABLE_PREFIX%email_template`; + +INSERT INTO `%TABLE_PREFIX%_email_template` + (`tpl_id`, `code_name`, `subject`, `body`, `created`, `updated`) + SELECT `tpl_id`, 'message.autoresp', message_autoresp_subj, message_autoresp_body, `created`, `updated` + FROM `%TABLE_PREFIX%email_template`; + +INSERT INTO `%TABLE_PREFIX%_email_template` + (`tpl_id`, `code_name`, `subject`, `body`, `created`, `updated`) + SELECT `tpl_id`, 'ticket.notice', ticket_notice_subj, ticket_notice_body, `created`, `updated` + FROM `%TABLE_PREFIX%email_template`; + +INSERT INTO `%TABLE_PREFIX%_email_template` + (`tpl_id`, `code_name`, `subject`, `body`, `created`, `updated`) + SELECT `tpl_id`, 'ticket.overlimit', ticket_overlimit_subj, ticket_overlimit_body, `created`, `updated` + FROM `%TABLE_PREFIX%email_template`; + +INSERT INTO `%TABLE_PREFIX%_email_template` + (`tpl_id`, `code_name`, `subject`, `body`, `created`, `updated`) + SELECT `tpl_id`, 'ticket.reply', ticket_reply_subj, ticket_reply_body, `created`, `updated` + FROM `%TABLE_PREFIX%email_template`; + +INSERT INTO `%TABLE_PREFIX%_email_template` + (`tpl_id`, `code_name`, `subject`, `body`, `created`, `updated`) + SELECT `tpl_id`, 'ticket.alert', ticket_alert_subj, ticket_alert_body, `created`, `updated` + FROM `%TABLE_PREFIX%email_template`; + +INSERT INTO `%TABLE_PREFIX%_email_template` + (`tpl_id`, `code_name`, `subject`, `body`, `created`, `updated`) + SELECT `tpl_id`, 'message.alert', message_alert_subj, message_alert_body, `created`, `updated` + FROM `%TABLE_PREFIX%email_template`; + +INSERT INTO `%TABLE_PREFIX%_email_template` + (`tpl_id`, `code_name`, `subject`, `body`, `created`, `updated`) + SELECT `tpl_id`, 'note.alert', note_alert_subj, note_alert_body, `created`, `updated` + FROM `%TABLE_PREFIX%email_template`; + +INSERT INTO `%TABLE_PREFIX%_email_template` + (`tpl_id`, `code_name`, `subject`, `body`, `created`, `updated`) + SELECT `tpl_id`, 'assigned.alert', assigned_alert_subj, assigned_alert_body, `created`, `updated` + FROM `%TABLE_PREFIX%email_template`; + +INSERT INTO `%TABLE_PREFIX%_email_template` + (`tpl_id`, `code_name`, `subject`, `body`, `created`, `updated`) + SELECT `tpl_id`, 'transfer.alert', transfer_alert_subj, transfer_alert_body, `created`, `updated` + FROM `%TABLE_PREFIX%email_template`; + +INSERT INTO `%TABLE_PREFIX%_email_template` + (`tpl_id`, `code_name`, `subject`, `body`, `created`, `updated`) + SELECT `tpl_id`, 'ticket.overdue', ticket_overdue_subj, ticket_overdue_body, `created`, `updated` + FROM `%TABLE_PREFIX%email_template`; + +DROP TABLE `%TABLE_PREFIX%email_template`; +ALTER TABLE `%TABLE_PREFIX%_email_template` + RENAME TO `%TABLE_PREFIX%email_template`; + +-- pages +CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%page` ( + `id` int(10) unsigned NOT NULL auto_increment, + `isactive` tinyint(1) unsigned NOT NULL default '0', + `type` enum('landing','offline','thank-you','other') NOT NULL default 'other', + `name` varchar(255) NOT NULL, + `body` text NOT NULL, + `notes` text, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`) +) DEFAULT CHARSET=utf8; + + +INSERT INTO `%TABLE_PREFIX%page` (`isactive`, `type`, `name`, `body`, `notes`, `created`, `updated`) VALUES +(1, 'offline', 'Offline', '<div>\r\n<h1><span style="font-size: medium">Support Ticket System Offline</span></h1>\r\n<p>Thank you for your interest in contacting us.</p>\r\n<p>Our helpdesk is offline at the moment, please check back at a later time.</p>\r\n</div>', 'Default offline page', NOW(), NOW()), +(1, 'thank-you', 'Thank you', '<div>%{ticket.name},<br />\r\n \r\n<p>\r\nThank you for contacting us.</p><p> A support ticket request #%{ticket.number} has been created and a representative will be getting back to you shortly if necessary.</p>\r\n \r\n<p>Support Team </p>\r\n</div>', 'Default "thank you" page displayed after the end-user creates a web ticket.', NOW(), NOW()), +(1, 'landing', 'Landing', '<h1>Welcome to the Support Center</h1>\r\n<p>In order to streamline support requests and better serve you, we utilize a support ticket system. Every support request is assigned a unique ticket number which you can use to track the progress and responses online. For your reference we provide complete archives and history of all your support requests. A valid email address is required to submit a ticket.\r\n</p>\r\n', 'Introduction text on the landing page.', NOW(), NOW()); + +INSERT INTO `%TABLE_PREFIX%config` (`key`, `value`, `namespace`) VALUES + ('landing_page_id', (SELECT `id` FROM `%TABLE_PREFIX%page` WHERE `type` = 'landing'), 'core') +, ('offline_page_id', (SELECT `id` FROM `%TABLE_PREFIX%page` WHERE `type` = 'offline'), 'core') +, ('thank-you_page_id', (SELECT `id` FROM `%TABLE_PREFIX%page` WHERE `type` = 'thank-you'), 'core'); + +ALTER TABLE `%TABLE_PREFIX%help_topic` + ADD `page_id` INT UNSIGNED NOT NULL DEFAULT '0' AFTER `sla_id` , + ADD INDEX ( `page_id` ); + +ALTER TABLE `%TABLE_PREFIX%file` + ADD `ft` CHAR( 1 ) NOT NULL DEFAULT 'T' AFTER `id`, + ADD INDEX ( `ft` ); + +-- Finished with patch +UPDATE `%TABLE_PREFIX%config` + SET `value` = '740428f9986da6ad85f88ec841b57bfe' + WHERE `key` = 'schema_signature' AND `namespace` = 'core'; diff --git a/include/upgrader/streams/core/8aeda901-16fcef4a.patch.sql b/include/upgrader/streams/core/8aeda901-16fcef4a.patch.sql new file mode 100644 index 0000000000000000000000000000000000000000..c2ea1d2a9ab7a62cf513f99b6761ae2a59136174 --- /dev/null +++ b/include/upgrader/streams/core/8aeda901-16fcef4a.patch.sql @@ -0,0 +1,18 @@ + + +/** + * @version v1.7.1 + * @signature 16fcef4a13d6475a5f8bfef462b548e2 + * + * Change email password field to varchar 255 ASCII + * + * + */ + +ALTER TABLE `%TABLE_PREFIX%email` + CHANGE `userpass` `userpass` VARCHAR( 255 ) CHARACTER SET ASCII COLLATE ascii_general_ci NOT NULL; + +-- Finished with patch +UPDATE `%TABLE_PREFIX%config` + SET `value` = '16fcef4a13d6475a5f8bfef462b548e2' + WHERE `key` = 'schema_signature' AND `namespace` = 'core'; diff --git a/include/upgrader/streams/core/8aeda901-16fcef4a.task.php b/include/upgrader/streams/core/8aeda901-16fcef4a.task.php new file mode 100644 index 0000000000000000000000000000000000000000..c2f1aceac420f0633905dd635a6b56b1588584a4 --- /dev/null +++ b/include/upgrader/streams/core/8aeda901-16fcef4a.task.php @@ -0,0 +1,38 @@ +<?php +require_once INCLUDE_DIR.'class.migrater.php'; + +class CryptoMigrater extends MigrationTask { + var $description = "Migrating encrypted password"; + var $status ='Making the world a better place!'; + + function run() { + + $sql='SELECT email_id, userpass, userid FROM '.EMAIL_TABLE + ." WHERE userpass <> ''"; + if(($res=db_query($sql)) && db_num_rows($res)) { + while(list($id, $passwd, $username) = db_fetch_row($res)) { + if(!$passwd) continue; + $ciphertext = Crypto::encrypt(self::_decrypt($passwd, SECRET_SALT), SECRET_SALT, $username); + $sql='UPDATE '.EMAIL_TABLE + .' SET userpass='.db_input($ciphertext) + .' WHERE email_id='.db_input($id); + db_query($sql); + } + } + } + + /* + XXX: This is not a good way of decrypting data - use to descrypt old + data. + */ + function _decrypt($text, $salt) { + + if(!function_exists('mcrypt_encrypt') || !function_exists('mcrypt_decrypt')) + return $text; + + return trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $salt, base64_decode($text), MCRYPT_MODE_ECB, + mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB), MCRYPT_RAND))); + } +} +return 'CryptoMigrater'; +?> diff --git a/include/upgrader/sql/98ae1ed2-e342f869.cleanup.sql b/include/upgrader/streams/core/98ae1ed2-e342f869.cleanup.sql similarity index 100% rename from include/upgrader/sql/98ae1ed2-e342f869.cleanup.sql rename to include/upgrader/streams/core/98ae1ed2-e342f869.cleanup.sql diff --git a/include/upgrader/sql/98ae1ed2-e342f869.patch.sql b/include/upgrader/streams/core/98ae1ed2-e342f869.patch.sql similarity index 94% rename from include/upgrader/sql/98ae1ed2-e342f869.patch.sql rename to include/upgrader/streams/core/98ae1ed2-e342f869.patch.sql index 3c2f57454f852eaef23d59dd603543b0df56177f..567fc17d51abe114a9ece6dd3cee51cca6966415 100644 --- a/include/upgrader/sql/98ae1ed2-e342f869.patch.sql +++ b/include/upgrader/streams/core/98ae1ed2-e342f869.patch.sql @@ -3,7 +3,7 @@ * @signature e342f869c7a537ab3ee937fb6e21cdd4 * * Upgrade from 1.6 RC1-4 to 1.6 RC5 - * + * */ ALTER TABLE `%TABLE_PREFIX%config` @@ -31,7 +31,7 @@ ALTER TABLE `%TABLE_PREFIX%config` ADD `hide_staff_name` TINYINT( 1 ) UNSIGNED NOT NULL DEFAULT '0' AFTER `show_answered_tickets`, ADD `log_graceperiod` INT UNSIGNED NOT NULL DEFAULT '12' AFTER `log_level`; -ALTER TABLE `%TABLE_PREFIX%email` +ALTER TABLE `%TABLE_PREFIX%email` ADD `userid` VARCHAR( 125 ) NOT NULL AFTER `name` , ADD `userpass` VARCHAR( 125 ) NOT NULL AFTER `userid`, ADD `mail_active` TINYINT( 1 ) NOT NULL DEFAULT '0' AFTER `userpass` , @@ -52,7 +52,7 @@ ALTER TABLE `%TABLE_PREFIX%email` -- Transfer old POP3 settings to "new" email table UPDATE `%TABLE_PREFIX%email` as T1 JOIN `%TABLE_PREFIX%email_pop3` as T2 ON(T1.email_id = T2.`email_id`) - SET + SET `updated`=NOW(), `mail_protocol`='POP', `mail_encryption`='NONE', @@ -103,10 +103,10 @@ ALTER TABLE `%TABLE_PREFIX%ticket` ADD `lastresponse` DATETIME NULL AFTER `lastmessage`, ADD INDEX ( `duedate` ) ; -ALTER TABLE `%TABLE_PREFIX%ticket` +ALTER TABLE `%TABLE_PREFIX%ticket` CHANGE `source` `source` ENUM( 'Web', 'Email', 'Phone', 'Other' ) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL DEFAULT 'Other' ; - -ALTER TABLE `%TABLE_PREFIX%email_template` + +ALTER TABLE `%TABLE_PREFIX%email_template` ADD `note_alert_subj` VARCHAR( 255 ) NOT NULL AFTER `message_alert_body` , ADD `note_alert_body` TEXT NOT NULL AFTER `note_alert_subj`, ADD `notes` TEXT NULL AFTER `name`; @@ -123,16 +123,10 @@ UPDATE `%TABLE_PREFIX%email_template` REPLACE(`ticket_reply_body`, 'view.php', 'ticket.php'), '%message', '%response'); -ALTER TABLE `%TABLE_PREFIX%ticket_message` +ALTER TABLE `%TABLE_PREFIX%ticket_message` ADD `messageId` VARCHAR( 255 ) NULL AFTER `ticket_id`, ADD INDEX ( `messageId` ) ; -ALTER TABLE `%TABLE_PREFIX%ticket_message` ADD FULLTEXT (`message`); - -ALTER TABLE `%TABLE_PREFIX%ticket_response` ADD FULLTEXT (`response`); - -ALTER TABLE `%TABLE_PREFIX%ticket_note` ADD FULLTEXT (`note`); - DROP TABLE IF EXISTS `%TABLE_PREFIX%api_key`; CREATE TABLE `%TABLE_PREFIX%api_key` ( `id` int(10) unsigned NOT NULL auto_increment, @@ -143,7 +137,7 @@ CREATE TABLE `%TABLE_PREFIX%api_key` ( `created` datetime NOT NULL default '0000-00-00 00:00:00', PRIMARY KEY (`id`), UNIQUE KEY `ipaddr` (`ipaddr`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `%TABLE_PREFIX%syslog`; CREATE TABLE `%TABLE_PREFIX%syslog` ( @@ -157,7 +151,7 @@ CREATE TABLE `%TABLE_PREFIX%syslog` ( `updated` datetime NOT NULL default '0000-00-00 00:00:00', PRIMARY KEY (`log_id`), KEY `log_type` (`log_type`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; UPDATE `%TABLE_PREFIX%config` SET `ostversion`='1.6 RC5'; diff --git a/include/upgrader/streams/core/98ae1ed2-e342f869.task.php b/include/upgrader/streams/core/98ae1ed2-e342f869.task.php new file mode 100644 index 0000000000000000000000000000000000000000..e479410b55fcbc2662ecab48a95556dc7a1a9cbf --- /dev/null +++ b/include/upgrader/streams/core/98ae1ed2-e342f869.task.php @@ -0,0 +1,25 @@ +<?php +require_once INCLUDE_DIR.'class.migrater.php'; + +class APIKeyMigrater extends MigrationTask { + var $description = "Migrating v1.6 API keys"; + + function run() { + $res = db_query('SELECT api_whitelist, api_key FROM '.CONFIG_TABLE.' WHERE id=1'); + if(!$res || !db_num_rows($res)) + return 0; //Reporting success. + + list($whitelist, $key) = db_fetch_row($res); + + $ips=array_filter(array_map('trim', explode(',', $whitelist))); + foreach($ips as $ip) { + $sql='INSERT INTO '.API_KEY_TABLE.' SET created=NOW(), updated=NOW(), isactive=1 ' + .',ipaddr='.db_input($ip) + .',apikey='.db_input(strtoupper(md5($ip.md5($key)))); + db_query($sql); + } + } +} + +return 'APIKeyMigrater'; +?> diff --git a/include/upgrader/sql/9f3b454c-c0fd16f4.patch.sql b/include/upgrader/streams/core/9f3b454c-c0fd16f4.patch.sql similarity index 89% rename from include/upgrader/sql/9f3b454c-c0fd16f4.patch.sql rename to include/upgrader/streams/core/9f3b454c-c0fd16f4.patch.sql index bcd1eedebedeb73bdbf1bb6f8444319c4adec16f..d23539b4242706d65cc10fe0386b99dc4850c255 100644 --- a/include/upgrader/sql/9f3b454c-c0fd16f4.patch.sql +++ b/include/upgrader/streams/core/9f3b454c-c0fd16f4.patch.sql @@ -6,8 +6,8 @@ -- Increment varchar size for various fields... based on feedback from users. -ALTER TABLE `%TABLE_PREFIX%session` - CHANGE `session_id` `session_id` VARCHAR( 256 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT ''; +ALTER TABLE `%TABLE_PREFIX%session` + CHANGE `session_id` `session_id` VARCHAR(255) collate ascii_general_ci NOT NULL DEFAULT ''; ALTER TABLE `%TABLE_PREFIX%ticket` CHANGE `name` `name` VARCHAR( 255 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', diff --git a/include/upgrader/sql/a67ba35e-98ae1ed2.patch.sql b/include/upgrader/streams/core/a67ba35e-98ae1ed2.patch.sql similarity index 100% rename from include/upgrader/sql/a67ba35e-98ae1ed2.patch.sql rename to include/upgrader/streams/core/a67ba35e-98ae1ed2.patch.sql diff --git a/include/upgrader/sql/aa4664af-b19dc97d.patch.sql b/include/upgrader/streams/core/aa4664af-b19dc97d.patch.sql similarity index 100% rename from include/upgrader/sql/aa4664af-b19dc97d.patch.sql rename to include/upgrader/streams/core/aa4664af-b19dc97d.patch.sql diff --git a/include/upgrader/sql/abe9c0cb-bbb021fb.patch.sql b/include/upgrader/streams/core/abe9c0cb-bbb021fb.patch.sql similarity index 100% rename from include/upgrader/sql/abe9c0cb-bbb021fb.patch.sql rename to include/upgrader/streams/core/abe9c0cb-bbb021fb.patch.sql diff --git a/include/upgrader/sql/aee589ab-98ae1ed2.patch.sql b/include/upgrader/streams/core/aee589ab-98ae1ed2.patch.sql similarity index 100% rename from include/upgrader/sql/aee589ab-98ae1ed2.patch.sql rename to include/upgrader/streams/core/aee589ab-98ae1ed2.patch.sql diff --git a/include/upgrader/sql/b19dc97d-435c62c3.patch.sql b/include/upgrader/streams/core/b19dc97d-435c62c3.patch.sql similarity index 100% rename from include/upgrader/sql/b19dc97d-435c62c3.patch.sql rename to include/upgrader/streams/core/b19dc97d-435c62c3.patch.sql diff --git a/include/upgrader/sql/bbb021fb-49478749.patch.sql b/include/upgrader/streams/core/bbb021fb-49478749.patch.sql similarity index 100% rename from include/upgrader/sql/bbb021fb-49478749.patch.sql rename to include/upgrader/streams/core/bbb021fb-49478749.patch.sql diff --git a/include/upgrader/sql/c00511c7-7be60a84.cleanup.sql b/include/upgrader/streams/core/c00511c7-7be60a84.cleanup.sql similarity index 100% rename from include/upgrader/sql/c00511c7-7be60a84.cleanup.sql rename to include/upgrader/streams/core/c00511c7-7be60a84.cleanup.sql diff --git a/include/upgrader/sql/c00511c7-7be60a84.patch.sql b/include/upgrader/streams/core/c00511c7-7be60a84.patch.sql similarity index 91% rename from include/upgrader/sql/c00511c7-7be60a84.patch.sql rename to include/upgrader/streams/core/c00511c7-7be60a84.patch.sql index 25465e81bfc1aed8ee5399c1a4d6be3db59a29ea..e565227249787174009fddfb6e7f06ed82f01d52 100644 --- a/include/upgrader/sql/c00511c7-7be60a84.patch.sql +++ b/include/upgrader/streams/core/c00511c7-7be60a84.patch.sql @@ -16,7 +16,7 @@ CREATE TABLE `%TABLE_PREFIX%file` ( `created` datetime NOT NULL, PRIMARY KEY (`id`), KEY `hash` (`hash`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; -- update ticket attachments ref. table. ALTER TABLE `%TABLE_PREFIX%ticket_attachment` @@ -44,9 +44,9 @@ CREATE TABLE `%TABLE_PREFIX%ticket_event` ( `timestamp` datetime NOT NULL, KEY `ticket_state` (`ticket_id`, `state`, `timestamp`), KEY `ticket_stats` (`timestamp`, `state`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; -ALTER TABLE `%TABLE_PREFIX%config` +ALTER TABLE `%TABLE_PREFIX%config` ADD `passwd_reset_period` INT UNSIGNED NOT NULL DEFAULT '0' AFTER `staff_session_timeout`, ADD `default_timezone_id` INT UNSIGNED NOT NULL DEFAULT '0' AFTER `default_template_id`, ADD `default_sla_id` INT UNSIGNED NOT NULL DEFAULT '0' AFTER `default_dept_id`, @@ -56,7 +56,7 @@ ALTER TABLE `%TABLE_PREFIX%config` ADD `max_staff_file_uploads` TINYINT UNSIGNED NOT NULL AFTER `max_user_file_uploads`, ADD `assigned_alert_active` TINYINT(1) UNSIGNED NOT NULL DEFAULT '1' AFTER `overdue_alert_dept_members`, ADD `assigned_alert_staff` TINYINT(1) UNSIGNED NOT NULL DEFAULT '1' AFTER `assigned_alert_active`, - ADD `assigned_alert_team_lead` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0' AFTER `assigned_alert_staff`, + ADD `assigned_alert_team_lead` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0' AFTER `assigned_alert_staff`, ADD `assigned_alert_team_members` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0' AFTER `assigned_alert_team_lead`, ADD `transfer_alert_active` TINYINT( 1 ) UNSIGNED NOT NULL DEFAULT '0' AFTER `note_alert_dept_manager` , ADD `transfer_alert_assigned` TINYINT( 1 ) UNSIGNED NOT NULL DEFAULT '0' AFTER `transfer_alert_active` , @@ -72,7 +72,7 @@ ALTER TABLE `%TABLE_PREFIX%config` UPDATE `%TABLE_PREFIX%config` SET default_timezone_id = (SELECT id FROM `%TABLE_PREFIX%timezone` WHERE offset = `%TABLE_PREFIX%config`.timezone_offset); -ALTER TABLE `%TABLE_PREFIX%staff` +ALTER TABLE `%TABLE_PREFIX%staff` ADD `passwdreset` DATETIME NULL DEFAULT NULL AFTER `lastlogin`; DROP TABLE IF EXISTS `%TABLE_PREFIX%sla`; @@ -88,8 +88,8 @@ CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%sla` ( `updated` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `name` (`name`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; - +) DEFAULT CHARSET=utf8; + -- Create a default SLA INSERT INTO `%TABLE_PREFIX%sla` (`isactive`, `enable_priority_escalation`, `disable_overdue_alerts`, `grace_period`, `name`, `notes`, `created`, `updated`) @@ -109,7 +109,7 @@ CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%team` ( UNIQUE KEY `name` (`name`), KEY `isnabled` (`isenabled`), KEY `lead_id` (`lead_id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; -- Create a default TEAM INSERT INTO `%TABLE_PREFIX%team` (`lead_id`, `isenabled`, `noalerts`, `name`, `notes`, `created`, `updated`) @@ -121,7 +121,7 @@ CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%team_member` ( `staff_id` int(10) unsigned NOT NULL, `updated` datetime NOT NULL, PRIMARY KEY (`team_id`,`staff_id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; ALTER TABLE `%TABLE_PREFIX%department` ADD sla_id INT UNSIGNED NOT NULL DEFAULT '0' AFTER tpl_id; @@ -134,7 +134,7 @@ ALTER TABLE `%TABLE_PREFIX%staff` ADD `default_signature_type` ENUM( 'none', 'mine', 'dept' ) NOT NULL DEFAULT 'none' AFTER `auto_refresh_rate`; -- Copy over time zone offet to tz_id -UPDATE `%TABLE_PREFIX%staff` SET timezone_id = +UPDATE `%TABLE_PREFIX%staff` SET timezone_id = (SELECT id FROM `%TABLE_PREFIX%timezone` WHERE offset = `%TABLE_PREFIX%staff`.timezone_offset); ALTER TABLE `%TABLE_PREFIX%groups` @@ -159,14 +159,14 @@ ALTER TABLE `%TABLE_PREFIX%help_topic` ADD team_id INT UNSIGNED NOT NULL DEFAULT '0' AFTER staff_id, ADD sla_id INT UNSIGNED NOT NULL DEFAULT '0' AFTER team_id, ADD INDEX ( staff_id , team_id ), - ADD INDEX ( sla_id ); + ADD INDEX ( sla_id ); ALTER TABLE `%TABLE_PREFIX%email` ADD mail_archivefolder VARCHAR(255) NULL AFTER mail_fetchmax, ADD notes TEXT NULL DEFAULT NULL AFTER smtp_auth, ADD smtp_spoofing TINYINT(1) UNSIGNED NOT NULL DEFAULT '0' AFTER smtp_auth; -ALTER TABLE `%TABLE_PREFIX%api_key` +ALTER TABLE `%TABLE_PREFIX%api_key` ADD notes TEXT NULL DEFAULT NULL AFTER apikey, ADD UNIQUE (apikey); @@ -196,14 +196,14 @@ CREATE TABLE `%TABLE_PREFIX%email_filter` ( `updated` datetime NOT NULL, PRIMARY KEY (`id`), KEY `email_id` (`email_id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; -- Copy banlist to a new email filter -INSERT INTO `%TABLE_PREFIX%email_filter` (`id`, `execorder`, `isactive`, +INSERT INTO `%TABLE_PREFIX%email_filter` (`execorder`, `isactive`, `match_all_rules`, `stop_onmatch`, `reject_email`, `use_replyto_email`, `disable_autoresponder`, `email_id`, `priority_id`, `dept_id`, `staff_id`, `team_id`, `sla_id`, `name`, `notes`) VALUES - (1, 99, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 'SYSTEM BAN LIST', + (99, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 'SYSTEM BAN LIST', 'Internal list for email banning. Do not remove'); DROP TABLE IF EXISTS `%TABLE_PREFIX%email_filter_rule`; @@ -219,12 +219,12 @@ CREATE TABLE `%TABLE_PREFIX%email_filter_rule` ( `updated` datetime NOT NULL, PRIMARY KEY (`id`), KEY `filter_id` (`filter_id`), - UNIQUE `filter` (`filter_id`, `what`, `how`, `val`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; + UNIQUE `filter` (`filter_id`, `what`, `how`, `val`) +) DEFAULT CHARSET=utf8; -- 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 1, 'email', 'equals', email FROM `%TABLE_PREFIX%email_banlist`; +INSERT INTO `%TABLE_PREFIX%email_filter_rule` (`filter_id`, `what`, `how`, `val`) + SELECT LAST_INSERT_ID(), 'email', 'equals', email FROM `%TABLE_PREFIX%email_banlist`; -- Create table session DROP TABLE IF EXISTS `%TABLE_PREFIX%session`; @@ -239,7 +239,7 @@ CREATE TABLE `%TABLE_PREFIX%session` ( PRIMARY KEY (`session_id`), KEY `updated` (`session_updated`), KEY `user_id` (`user_id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +) DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; -- Create tables for FAQ + attachments. DROP TABLE IF EXISTS `%TABLE_PREFIX%faq`; @@ -256,16 +256,15 @@ CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%faq` ( PRIMARY KEY (`faq_id`), UNIQUE KEY `question` (`question`), KEY `category_id` (`category_id`), - KEY `ispublished` (`ispublished`), - FULLTEXT KEY `faq` (`question`,`answer`,`keywords`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; + KEY `ispublished` (`ispublished`) +) DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `%TABLE_PREFIX%faq_attachment`; CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%faq_attachment` ( `faq_id` int(10) unsigned NOT NULL, `file_id` int(10) unsigned NOT NULL, PRIMARY KEY (`faq_id`,`file_id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; -- Add support for attachments to canned responses DROP TABLE IF EXISTS `%TABLE_PREFIX%canned_attachment`; @@ -273,7 +272,7 @@ CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%canned_attachment` ( `canned_id` int(10) unsigned NOT NULL, `file_id` int(10) unsigned NOT NULL, PRIMARY KEY (`canned_id`,`file_id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; -- Rename kb_premade to canned_response ALTER TABLE `%TABLE_PREFIX%kb_premade` @@ -281,9 +280,8 @@ ALTER TABLE `%TABLE_PREFIX%kb_premade` CHANGE `title` `title` VARCHAR( 255 ) NOT NULL DEFAULT '', CHANGE `answer` `response` TEXT NOT NULL, ADD `notes` TEXT NOT NULL AFTER `response`, - DROP INDEX `title`, - ADD FULLTEXT `resp` (`title` ,`response`); - + DROP INDEX `title`; + ALTER TABLE `%TABLE_PREFIX%kb_premade` RENAME TO `%TABLE_PREFIX%canned_response`; DROP TABLE IF EXISTS `%TABLE_PREFIX%faq_category`; @@ -297,15 +295,15 @@ CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%faq_category` ( `updated` date NOT NULL, PRIMARY KEY (`category_id`), KEY (`ispublic`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `%TABLE_PREFIX%faq_topic`; CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%faq_topic` ( `faq_id` int(10) unsigned NOT NULL, `topic_id` int(10) unsigned NOT NULL, PRIMARY KEY (`faq_id`,`topic_id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; UPDATE `%TABLE_PREFIX%config` - SET `schema_signature`='7be60a8432e44989e782d5914ef784d2'; + SET `schema_signature`='7be60a8432e44989e782d5914ef784d2'; diff --git a/include/upgrader/streams/core/c00511c7-7be60a84.task.php b/include/upgrader/streams/core/c00511c7-7be60a84.task.php new file mode 100644 index 0000000000000000000000000000000000000000..eca25aee2c623a13a956d2d7df13843ba4b7bee0 --- /dev/null +++ b/include/upgrader/streams/core/c00511c7-7be60a84.task.php @@ -0,0 +1,14 @@ +<?php +require_once INCLUDE_DIR.'class.migrater.php'; + +class MigrateDbSession extends MigrationTask { + var $description = "Migrate to database-backed sessions"; + + function run() { + # How about 'dis for a hack? + osTicketSession::write(session_id(), session_encode()); + } +} + +return 'MigrateDbSession'; +?> diff --git a/include/upgrader/sql/c0fd16f4-959a00e.patch.sql b/include/upgrader/streams/core/c0fd16f4-d959a00e.patch.sql similarity index 100% rename from include/upgrader/sql/c0fd16f4-959a00e.patch.sql rename to include/upgrader/streams/core/c0fd16f4-d959a00e.patch.sql diff --git a/include/upgrader/sql/c2d2fabf-aa4664af.patch.sql b/include/upgrader/streams/core/c2d2fabf-aa4664af.patch.sql similarity index 100% rename from include/upgrader/sql/c2d2fabf-aa4664af.patch.sql rename to include/upgrader/streams/core/c2d2fabf-aa4664af.patch.sql diff --git a/include/upgrader/sql/d0e37dca-1da1bcba.patch.sql b/include/upgrader/streams/core/d0e37dca-1da1bcba.patch.sql similarity index 100% rename from include/upgrader/sql/d0e37dca-1da1bcba.patch.sql rename to include/upgrader/streams/core/d0e37dca-1da1bcba.patch.sql diff --git a/include/upgrader/streams/core/d959a00e-32de1766.patch.sql b/include/upgrader/streams/core/d959a00e-32de1766.patch.sql new file mode 100644 index 0000000000000000000000000000000000000000..9c636a0a5051e6215b1e350637382a57261d46db --- /dev/null +++ b/include/upgrader/streams/core/d959a00e-32de1766.patch.sql @@ -0,0 +1,15 @@ +/** + * The database install script changed to support installation on cluster + * servers. No significant changes need to be rolled for continuous updaters + * + * @version v1.7.1 + * @signature 32de1766d56e43215041fa982dcb465e + */ + +ALTER TABLE `%TABLE_PREFIX%session` + CHANGE `session_id` `session_id` VARCHAR(255) collate ascii_general_ci, + CHANGE `session_data` `session_data` BLOB; + +-- update schema signature +UPDATE `%TABLE_PREFIX%config` + SET `schema_signature`='32de1766d56e43215041fa982dcb465e'; diff --git a/include/upgrader/sql/dd0022fb-f4da0c9b.patch.sql b/include/upgrader/streams/core/dd0022fb-f4da0c9b.patch.sql similarity index 100% rename from include/upgrader/sql/dd0022fb-f4da0c9b.patch.sql rename to include/upgrader/streams/core/dd0022fb-f4da0c9b.patch.sql diff --git a/include/upgrader/sql/e342f869-c00511c7.patch.sql b/include/upgrader/streams/core/e342f869-c00511c7.patch.sql similarity index 100% rename from include/upgrader/sql/e342f869-c00511c7.patch.sql rename to include/upgrader/streams/core/e342f869-c00511c7.patch.sql diff --git a/include/upgrader/sql/f4da0c9b-00ff231f.patch.sql b/include/upgrader/streams/core/f4da0c9b-00ff231f.patch.sql similarity index 100% rename from include/upgrader/sql/f4da0c9b-00ff231f.patch.sql rename to include/upgrader/streams/core/f4da0c9b-00ff231f.patch.sql diff --git a/include/upgrader/sql/f8856d56-abe9c0cb.patch.sql b/include/upgrader/streams/core/f8856d56-abe9c0cb.patch.sql similarity index 96% rename from include/upgrader/sql/f8856d56-abe9c0cb.patch.sql rename to include/upgrader/streams/core/f8856d56-abe9c0cb.patch.sql index e78ab80cc525fac35fc81061bc970ef89dc950f0..09b593a16a1abe2e546ec88ede4bf0ceb1caff9c 100644 --- a/include/upgrader/sql/f8856d56-abe9c0cb.patch.sql +++ b/include/upgrader/streams/core/f8856d56-abe9c0cb.patch.sql @@ -1,11 +1,11 @@ /** * Merge ticket thread tables into one - * + * * Replace the ticket_{message,response,note} tables with a single * ticket_thread table that will contain data for all three current message - * types. This simplifies much of the ticket thread code and paves the way + * types. This simplifies much of the ticket thread code and paves the way * for other types of messages in the future. - * + * * This patch automagically moves the data from the three federated tables * into the one combined table. */ @@ -29,9 +29,8 @@ CREATE TABLE `%TABLE_PREFIX%ticket_thread` ( KEY `ticket_id` (`ticket_id`), KEY `staff_id` (`staff_id`), KEY `old_pk` (`old_pk`), - KEY `created` (`created`), - FULLTEXT KEY `body` (`body`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; + KEY `created` (`created`) +) DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `%TABLE_PREFIX%ticket_email_info`; CREATE TABLE `%TABLE_PREFIX%ticket_email_info` ( @@ -39,7 +38,7 @@ CREATE TABLE `%TABLE_PREFIX%ticket_email_info` ( `email_mid` varchar(255) NOT NULL, `headers` text, KEY `message_id` (`email_mid`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; -- Transfer messages INSERT INTO `%TABLE_PREFIX%ticket_thread` @@ -54,7 +53,7 @@ INSERT INTO `%TABLE_PREFIX%ticket_thread` (`ticket_id`, `staff_id`, `thread_type`, `poster`, `body`, `ip_address`, `created`, `updated`, `old_pk`, `old_pid`) SELECT `ticket_id`, `staff_id`, 'R', `staff_name`, `response`, `ip_address`, - `created`, COALESCE(`updated`, NOW()), `response_id`, `msg_id` + `created`, COALESCE(`updated`, NOW()), `response_id`, `msg_id` FROM `%TABLE_PREFIX%ticket_response`; -- Connect responses to (new) messages diff --git a/include/upgrader/upgrade.inc.php b/include/upgrader/upgrade.inc.php index b157b6a030e4e0e23165a4a5720e31fe114ea133..d2f3558956a83e10a0e4fed0405000d25cab60f5 100644 --- a/include/upgrader/upgrade.inc.php +++ b/include/upgrader/upgrade.inc.php @@ -18,14 +18,14 @@ $action=$upgrader->getNextAction(); <div id="main"> <div id="intro"> <p>Thank you for taking the time to upgrade your osTicket intallation!</p> - <p>Please don't cancel or close the browser, any errors at this stage will be fatal.</p> + <p>Please don't cancel or close the browser; any errors at this stage will be fatal.</p> </div> <h2 id="task"><?php echo $action ?></h2> <p>The upgrade wizard will now attempt to upgrade your database and core settings!</p> <ul> <li>Database enhancements</li> <li>New and updated features</li> - <li>Enhance settings and security</li> + <li>Enhanced settings and security</li> </ul> <div id="bar"> <form method="post" action="upgrade.php" id="upgrade"> @@ -39,9 +39,9 @@ $action=$upgrader->getNextAction(); </div> <div id="sidebar"> <h3>Upgrade Tips</h3> - <p>1. Be patient the process will take a couple of minutes.</p> + <p>1. Be patient. The process will take a couple of minutes.</p> <p>2. If you experience any problems, you can always restore your files/database backup.</p> - <p>3. We can help, feel free to <a href="http://osticket.com/support/" target="_blank">contact us </a> for professional help.</p> + <p>3. We can help! Feel free to <a href="http://osticket.com/support/" target="_blank">contact us </a> for professional help.</p> </div> <div class="clear"></div> <div id="upgrading"> diff --git a/index.php b/index.php index 0680a520d48df86ac572d36eead4f5b3dd6ec443..9a2ae35d32e47aee05ba40084ec256f840659c8e 100644 --- a/index.php +++ b/index.php @@ -17,13 +17,13 @@ require('client.inc.php'); $section = 'home'; require(CLIENTINC_DIR.'header.inc.php'); ?> - <div id="landing_page"> - <h1>Welcome to the Support Center</h1> - <p> - In order to streamline support requests and better serve you, we utilize a support ticket system. Every support request is assigned a unique ticket number which you can use to track the progress and responses online. For your reference we provide complete archives and history of all your support requests. A valid email address is required to submit a ticket. - </p> - + <?php + if($cfg && ($page = $cfg->getLandingPage())) + echo $page->getBody(); + else + echo '<h1>Welcome to the Support Center</h1>'; + ?> <div id="new_ticket"> <h3>Open A New Ticket</h3> <br> diff --git a/js/osticket.js b/js/osticket.js index 4a0e87b030c0f0ebc26cc21521cf0586acff88bb..7ee8d00a66ebe05ece0efa30b2c8d19f1bad96c2 100644 --- a/js/osticket.js +++ b/js/osticket.js @@ -1,4 +1,4 @@ -/* +/* osticket.js Copyright (c) osTicket.com */ @@ -18,7 +18,7 @@ $(document).ready(function(){ /* loading ... */ $("#loading").css({ top : ($(window).height() / 3), - left : ($(window).width() / 2 - 160) + left : ($(window).width() / 2 - 160) }); $("form :input").change(function() { @@ -49,22 +49,34 @@ $(document).ready(function(){ return true; }); - /* Get config settings from the backend */ - var $config = null; - $.ajax({ - url: "ajax.php/config/client", - dataType: 'json', - async: false, - success: function (config) { - $config = config; - } - }); - + jQuery.fn.exists = function() { return this.length>0; }; + + var getConfig = (function() { + var dfd = $.Deferred(); + return function() { + if (!dfd.isResolved()) + $.ajax({ + url: "ajax.php/config/client", + dataType: 'json', + success: function (json_config) { + dfd.resolve(json_config); + } + }); + return dfd; + } + })(); + /* Multifile uploads */ - $('.multifile').multifile({ - container: '.uploads', - max_uploads: ($config && $config.max_file_uploads)?$config.max_file_uploads:1, - file_types: ($config && $config.file_types)?$config.file_types:".*", - max_file_size: ($config && $config.max_file_size)?$config.max_file_size:0 - }); + var elems = $('.multifile'); + if (elems.exists()) { + /* Get config settings from the backend */ + getConfig().then(function(c) { + elems.multifile({ + container: '.uploads', + max_uploads: c.max_file_uploads || 1, + file_types: c.file_types || ".*", + max_file_size: c.max_file_size || 0 + }); + }); + } }); diff --git a/kb/kb.inc.php b/kb/kb.inc.php index ed0ab8c21f803473c9a324cf033b235a58fc6f8a..a53c515cb7c90906ec930291b3e07a5ded6a5e95 100644 --- a/kb/kb.inc.php +++ b/kb/kb.inc.php @@ -13,7 +13,6 @@ vim: expandtab sw=4 ts=4 sts=4: **********************************************************************/ -define('ROOT_PATH','../'); require_once('../client.inc.php'); require_once(INCLUDE_DIR.'class.faq.php'); /* Bail out if knowledgebase is disabled or if we have no public-published FAQs. */ diff --git a/login.php b/login.php index 8ca3901055291f961390319c0ca54e99c9d29add..789938980ff451cdcc72920d2b9f88080dcc7a47 100644 --- a/login.php +++ b/login.php @@ -2,7 +2,7 @@ /********************************************************************* login.php - Client Login + Client Login Peter Rotich <peter@osticket.com> Copyright (c) 2006-2013 osTicket diff --git a/logo.php b/logo.php new file mode 100644 index 0000000000000000000000000000000000000000..7972433548d8b6165aa31938a51911ce2e8683b1 --- /dev/null +++ b/logo.php @@ -0,0 +1,32 @@ +<?php +/********************************************************************* + logo.php + + Simple logo to facilitate serving a customized client-side logo from + osTicet. The logo is configurable in Admin Panel -> Settings -> Pages + + Peter Rotich <peter@osticket.com> + Jared Hancock <jared@osticket.com> + Copyright (c) 2006-2013 osTicket + http://www.osticket.com + + Released under the GNU General Public License WITHOUT ANY WARRANTY. + See LICENSE.TXT for details. + + vim: expandtab sw=4 ts=4 sts=4: +**********************************************************************/ + +// Don't update the session for inline image fetches +if (!function_exists('noop')) { function noop() {} } +session_set_save_handler('noop','noop','noop','noop','noop','noop'); +define('DISABLE_SESSION', true); + +require('client.inc.php'); + +if (($logo = $ost->getConfig()->getClientLogo())) { + $logo->display(); +} else { + header('Location: '.ASSETS_PATH.'images/logo.png'); +} + +?> diff --git a/main.inc.php b/main.inc.php index c6a6d0fe7240e316e0ad4c704c3a774dc8000208..4b69b3e1638a2fc8f90f9d9533c092f31a9dffbd 100644 --- a/main.inc.php +++ b/main.inc.php @@ -34,8 +34,6 @@ ini_set('session.use_trans_sid', 0); #No cache session_cache_limiter('nocache'); - #Cookies - //ini_set('session.cookie_path','/osticket/'); #Error reporting...Good idea to ENABLE error reporting to a file. i.e display_errors should be set to false $error_reporting = E_ALL & ~E_NOTICE; @@ -46,8 +44,8 @@ error_reporting($error_reporting); //Respect whatever is set in php.ini (sysadmin knows better??) #Don't display errors - ini_set('display_errors', 0); - ini_set('display_startup_errors', 0); + ini_set('display_errors', 1); + ini_set('display_startup_errors', 1); //Default timezone if (!ini_get('date.timezone')) { @@ -62,7 +60,12 @@ } #Set Dir constants - if(!defined('ROOT_PATH')) define('ROOT_PATH','./'); //root path. Damn directories + $here = substr(realpath(dirname(__file__)), + strlen($_SERVER['DOCUMENT_ROOT'])); + // Determine the path in the URI used as the base of the osTicket + // installation + if (!defined('ROOT_PATH')) + define('ROOT_PATH', str_replace('\\', '/', $here.'/')); //root path. Damn directories define('ROOT_DIR',str_replace('\\\\', '/', realpath(dirname(__FILE__))).'/'); #Get real path for root dir ---linux and windows define('INCLUDE_DIR',ROOT_DIR.'include/'); //Change this if include is moved outside the web path. @@ -70,13 +73,12 @@ define('SETUP_DIR',INCLUDE_DIR.'setup/'); define('UPGRADE_DIR', INCLUDE_DIR.'upgrader/'); - define('SQL_DIR', UPGRADE_DIR.'sql/'); + define('I18N_DIR', INCLUDE_DIR.'i18n/'); /*############## Do NOT monkey with anything else beyond this point UNLESS you really know what you are doing ##############*/ #Current version && schema signature (Changes from version to version) - define('THIS_VERSION','1.7.0'); //Shown on admin panel - define('SCHEMA_SIGNATURE', 'd959a00e55c75e0c903b9e37324fd25d'); //MD5 signature of the db schema. (used to trigger upgrades) + define('THIS_VERSION','1.7.0+'); //Shown on admin panel #load config info $configfile=''; if(file_exists(ROOT_DIR.'ostconfig.php')) //Old installs prior to v 1.6 RC5 @@ -114,15 +116,24 @@ require(INCLUDE_DIR.'class.usersession.php'); require(INCLUDE_DIR.'class.pagenate.php'); //Pagenate helper! require(INCLUDE_DIR.'class.log.php'); - require(INCLUDE_DIR.'class.mcrypt.php'); + require(INCLUDE_DIR.'class.crypto.php'); require(INCLUDE_DIR.'class.misc.php'); require(INCLUDE_DIR.'class.timezone.php'); require(INCLUDE_DIR.'class.http.php'); + require(INCLUDE_DIR.'class.signal.php'); require(INCLUDE_DIR.'class.nav.php'); + require(INCLUDE_DIR.'class.page.php'); require(INCLUDE_DIR.'class.format.php'); //format helpers require(INCLUDE_DIR.'class.validator.php'); //Class to help with basic form input validation...please help improve it. require(INCLUDE_DIR.'class.mailer.php'); - require(INCLUDE_DIR.'mysql.php'); + if (extension_loaded('mysqli')) + require_once INCLUDE_DIR.'mysqli.php'; + else + require(INCLUDE_DIR.'mysql.php'); + + #Cookies + session_set_cookie_params(86400, ROOT_PATH, $_SERVER['HTTP_HOST'], + osTicket::is_https()); #CURRENT EXECUTING SCRIPT. define('THISPAGE', Misc::currentURL()); @@ -155,6 +166,8 @@ define('TEAM_TABLE',TABLE_PREFIX.'team'); define('TEAM_MEMBER_TABLE',TABLE_PREFIX.'team_member'); + define('PAGE_TABLE', TABLE_PREFIX.'page'); + define('FAQ_TABLE',TABLE_PREFIX.'faq'); define('FAQ_ATTACHMENT_TABLE',TABLE_PREFIX.'faq_attachment'); define('FAQ_TOPIC_TABLE',TABLE_PREFIX.'faq_topic'); @@ -172,6 +185,7 @@ define('TICKET_EMAIL_INFO_TABLE',TABLE_PREFIX.'ticket_email_info'); define('EMAIL_TABLE',TABLE_PREFIX.'email'); + define('EMAIL_TEMPLATE_GRP_TABLE',TABLE_PREFIX.'email_template_group'); define('EMAIL_TEMPLATE_TABLE',TABLE_PREFIX.'email_template'); define('FILTER_TABLE',TABLE_PREFIX.'filter'); @@ -184,15 +198,27 @@ define('API_KEY_TABLE',TABLE_PREFIX.'api_key'); define('TIMEZONE_TABLE',TABLE_PREFIX.'timezone'); - #Global overwrite - if($_SERVER['HTTP_X_FORWARDED_FOR']) //Can contain multiple IPs - use the last one. - $_SERVER['REMOTE_ADDR'] = array_pop(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])); + #Global override + if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) + // Take the left-most item for X-Forwarded-For + $_SERVER['REMOTE_ADDR'] = array_pop( + explode(',', trim($_SERVER['HTTP_X_FORWARDED_FOR']))); #Connect to the DB && get configuration from database $ferror=null; - if (!db_connect(DBHOST,DBUSER,DBPASS) || !db_select_database(DBNAME)) { - $ferror='Unable to connect to the database'; - } elseif(!($ost=osTicket::start(1)) || !($cfg = $ost->getConfig())) { + $options = array(); + if (defined('DBSSLCA')) + $options['ssl'] = array( + 'ca' => DBSSLCA, + 'cert' => DBSSLCERT, + 'key' => DBSSLKEY + ); + + if (!db_connect(DBHOST, DBUSER, DBPASS, $options)) { + $ferror='Unable to connect to the database -'.db_connect_error(); + }elseif(!db_select_database(DBNAME)) { + $ferror='Unknown or invalid database '.DBNAME; + } elseif(!($ost=osTicket::start()) || !($cfg = $ost->getConfig())) { $ferror='Unable to load config info from DB. Get tech support.'; } @@ -209,7 +235,7 @@ $session = $ost->getSession(); //System defaults we might want to make global// - #pagenation default - user can overwrite it! + #pagenation default - user can override it! define('DEFAULT_PAGE_LIMIT', $cfg->getPageSize()?$cfg->getPageSize():25); #Cleanup magic quotes crap. diff --git a/offline.php b/offline.php index 4e73aff2a856c3529f423c9465d85c2fee804147..04323f9e0a86d6831b6e6149ed65f3933fa88ed1 100644 --- a/offline.php +++ b/offline.php @@ -1,30 +1,34 @@ -<?php -/********************************************************************* - offline.php - - Offline page...modify to fit your needs. - - Peter Rotich <peter@osticket.com> - Copyright (c) 2006-2013 osTicket - http://www.osticket.com - - Released under the GNU General Public License WITHOUT ANY WARRANTY. - See LICENSE.TXT for details. - - vim: expandtab sw=4 ts=4 sts=4: -**********************************************************************/ -require_once('client.inc.php'); -if(is_object($ost) && $ost->isSystemOnline()) { - @header('Location: index.php'); //Redirect if the system is online. - include('index.php'); - exit; -} -$nav=null; -require(CLIENTINC_DIR.'header.inc.php'); -?> -<div id="landing_page"> - <h1>Support Ticket System Offline</h1> - <p>Thank you for your interest in contacting us.</p> - <p>Our helpdesk is offline at the moment, please check back at a later time.</p> -</div> -<?php require(CLIENTINC_DIR.'footer.inc.php'); ?> +<?php +/********************************************************************* + offline.php + + Offline page...modify to fit your needs. + + Peter Rotich <peter@osticket.com> + Copyright (c) 2006-2013 osTicket + http://www.osticket.com + + Released under the GNU General Public License WITHOUT ANY WARRANTY. + See LICENSE.TXT for details. + + vim: expandtab sw=4 ts=4 sts=4: +**********************************************************************/ +require_once('client.inc.php'); +if(is_object($ost) && $ost->isSystemOnline()) { + @header('Location: index.php'); //Redirect if the system is online. + include('index.php'); + exit; +} +$nav=null; +require(CLIENTINC_DIR.'header.inc.php'); +?> +<div id="landing_page"> +<?php +if(($page=$cfg->getOfflinePage())) { + echo $page->getBody(); +} else { + echo '<h1>Support Ticket System Offline</h1>'; +} +?> +</div> +<?php require(CLIENTINC_DIR.'footer.inc.php'); ?> diff --git a/open.php b/open.php index 0ce680707f6930cc324d3c4952a7bdf550ff60e6..3292e8564900e9676784ca54dd0599e9e6f59d2c 100644 --- a/open.php +++ b/open.php @@ -15,7 +15,7 @@ **********************************************************************/ require('client.inc.php'); define('SOURCE','Web'); //Ticket source. -$inc='open.inc.php'; //default include. +$ticket = null; $errors=array(); if($_POST): $vars = $_POST; @@ -44,8 +44,6 @@ if($_POST): session_regenerate_id(); @header('Location: tickets.php?id='.$ticket->getExtId()); } - //Thank the user and promise speedy resolution! - $inc='thankyou.inc.php'; }else{ $errors['err']=$errors['err']?$errors['err']:'Unable to create a ticket. Please correct errors below and try again!'; } @@ -54,6 +52,19 @@ endif; //page $nav->setActiveNav('new'); require(CLIENTINC_DIR.'header.inc.php'); -require(CLIENTINC_DIR.$inc); +if($ticket + && ( + (($topic = $ticket->getTopic()) && ($page = $topic->getPage())) + || ($page = $cfg->getThankYouPage()) + )) { //Thank the user and promise speedy resolution! + //Hide ticket number - it should only be delivered via email for security reasons. + echo Format::safe_html($ticket->replaceVars(str_replace( + array('%{ticket.number}', '%{ticket.extId}', '%{ticket}'), //ticket number vars. + array_fill(0, 3, 'XXXXXX'), + $page->getBody() + ))); +} else { + require(CLIENTINC_DIR.'open.inc.php'); +} require(CLIENTINC_DIR.'footer.inc.php'); ?> diff --git a/pages/.htaccess b/pages/.htaccess new file mode 100644 index 0000000000000000000000000000000000000000..ff79be5c961ac099b5d0dd6cb2c73d9cb1fdc77c --- /dev/null +++ b/pages/.htaccess @@ -0,0 +1,11 @@ +<IfModule mod_rewrite.c> + +RewriteEngine On + +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteCond %{REQUEST_URI} (.*/pages) + +RewriteRule ^(.*)$ %1/index.php/$1 [L] + +</IfModule> diff --git a/pages/index.php b/pages/index.php new file mode 100644 index 0000000000000000000000000000000000000000..75c5490dd7be32bd7be1de368cefeeee830ec432 --- /dev/null +++ b/pages/index.php @@ -0,0 +1,54 @@ +<?php +/********************************************************************* + pages/index.php + + Custom pages servlet + + Peter Rotich <peter@osticket.com> + Jared Hancock <jared@osticket.com> + Copyright (c) 2006-2013 osTicket + http://www.osticket.com + + Released under the GNU General Public License WITHOUT ANY WARRANTY. + See LICENSE.TXT for details. + + vim: expandtab sw=4 ts=4 sts=4: +**********************************************************************/ +@chdir(realpath(dirname(__file__).'/../')); + +require_once('client.inc.php'); +require_once(INCLUDE_DIR.'class.format.php'); +require_once(INCLUDE_DIR.'class.page.php'); + +// Determine the requested page +// - Strip extension +$slug = Format::slugify($ost->get_path_info()); + +// Get the part before the first dash +$first_word = explode('-', $slug); +$first_word = $first_word[0]; + +$sql = 'SELECT id, name FROM '.PAGE_TABLE + .' WHERE name LIKE '.db_input("$first_word%"); +$page_id = null; + +$res = db_query($sql); +while (list($id, $name) = db_fetch_row($res)) { + if (Format::slugify($name) == $slug) { + $page_id = $id; + break; + } +} + +if (!$page_id || !($page = Page::lookup($page_id))) + Http::response(404, 'Page Not Found'); + +if (!$page->isActive() || $page->getType() != 'other') + Http::response(404, 'Page Not Found'); + +require(CLIENTINC_DIR.'header.inc.php'); + +print $page->getBody(); + +require(CLIENTINC_DIR.'footer.inc.php'); +?> diff --git a/scp/admin.inc.php b/scp/admin.inc.php index 19d2d5fbcf97008b689ee6a07204c1780e3ad455..97dc168406af6c36909cdcfd135eca8947fc5624 100644 --- a/scp/admin.inc.php +++ b/scp/admin.inc.php @@ -34,11 +34,11 @@ if($ost->isUpgradePending()) { exit; } } else { - + if(!strcasecmp(basename(CONFIG_FILE), 'settings.php')) { $sysnotice=sprintf('Please rename config file include/%s to include/ost-config.php to avoid possible conflicts', basename(CONFIG_FILE)); - //Die gracefully - otherwise upgraded RC5 installations will die with confusing message. + //Die gracefully - otherwise upgraded RC5 installations will die with confusing message. if(!strcasecmp(basename($_SERVER['SCRIPT_NAME']), 'settings.php')) die($sysnotice); diff --git a/scp/autocron.php b/scp/autocron.php index a57071b78a7fc468b9395432a38345b58960f130..ec7cb4c744b6b170da3d5186d09b59fc0e424342 100644 --- a/scp/autocron.php +++ b/scp/autocron.php @@ -32,7 +32,7 @@ ob_start(); //Keep the image output clean. Hide our dirt. $sec=time()-$_SESSION['lastcroncall']; $caller = $thisstaff->getUserName(); -if($sec>180): //user can call cron once every 3 minutes. +if($sec>180 && $ost && !$ost->isUpgradePending()): //user can call cron once every 3 minutes. require_once(INCLUDE_DIR.'class.cron.php'); $thisstaff = null; //Clear staff obj to avoid false credit internal notes & auto-assignment diff --git a/scp/css/login.css b/scp/css/login.css index a15d4d12e5b0aec8970d802e0095ad53be00620f..c2512a2bf8cb478236298561a7b88da18ccccd6c 100644 --- a/scp/css/login.css +++ b/scp/css/login.css @@ -1,15 +1,6 @@ -* { - box-sizing: border-box; - position: relative; - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; -} - -*:focus { - outline-color: rgb(207,16,118); - outline-style: auto; - outline-width: 5px; - z-index:1000; +input:focus { + border: 1px solid rgb(207,16,118); + box-shadow: 0 0 4px rgb(207,16,118); } :-webkit-input-placeholder { @@ -43,7 +34,6 @@ body { body, input { font-family: helvetica, arial, sans-serif; - font-size: 100%/1.5; color: #000; } @@ -59,11 +49,16 @@ input[type=reset], input[type=submit], input[type=button] { border:1px solid #2a67ac; border-right:2px solid #2a67ac; border-bottom:3px solid #2a67ac; + box-shadow: 2px 2px 8px rgba(42, 103, 172, 0.5); background:#fff; width:400px; margin:10% auto 0 auto; padding:1em; + padding-bottom: 1em; text-align:center; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; } h1 { @@ -96,23 +91,24 @@ form { fieldset { border:none; - margin:0; + margin:0.25em; padding:0; } fieldset input { display:block; - width:100%; margin-bottom:1em; border:1px solid #ccc; + border:1px solid rgba(0,0,0,0.3); background:#fff; - padding:2px; + padding:2px 4px; + width: 96%; } input.submit { display:inline-block; float:right; - margin:0; + margin:0.25em; height:24px; line-height:24px; font-weight:bold; diff --git a/scp/css/scp.css b/scp/css/scp.css index 8fd39dd21f6e539df9442f20006a8e8f8720ede1..560eaae90a0c87e41d976a05a787ae3a7f2b3fea 100644 --- a/scp/css/scp.css +++ b/scp/css/scp.css @@ -292,6 +292,9 @@ a.departments { background:url(../images/icons/list_departments.gif) } a.newDepartment { background:url(../images/icons/new_department.gif) } +a.pages { background:url(../images/icons/pages.gif) } +a.newPage { background:url(../images/icons/new_page.gif) } + /* Generic CSS based Icons. use=> <tag class="Icon iconname">text</tag> */ .Icon { @@ -541,8 +544,6 @@ a.print { } .form_table input[type=radio], .form_table input[type=checkbox] { - position:relative; - top:3px; margin-left:0; padding-left:0; } @@ -1121,7 +1122,7 @@ h2 .reload { #faq { clear: both; margin: 0; - padding: 5 0 10px 5px; + padding: 5px 0 10px 5px; } #faq ol { font-size: 15px; @@ -1378,27 +1379,27 @@ ul.progress li.no small {color:red;} #bar.error { background: #ffd; text-align: center; color: #a00; font-weight: bold; } /* Overlay */ -#overlay { - display: none; - position: fixed; +#overlay { + display: none; + position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: #000; z-index: 1000; - -webkit-transform: translate3d(0,0,0); + -webkit-transform: translate3d(0,0,0); } #loading, #upgrading { border:1px solid #2a67ac; - padding: 10px 10px 10px 60px; - width: 300px; - height: 100px; + padding: 10px 10px 10px 60px; + width: 300px; + height: 100px; background: rgb( 255, 255, 255) url('../images/FhHRx-Spinner.gif') 10px 50% no-repeat; - position: fixed; - display: none; - z-index: 3000; + position: fixed; + display: none; + z-index: 3000; } #loading h4, #upgrading h4 { margin: 3px 0 0 0; padding: 0; color: #d80; } diff --git a/scp/image.php b/scp/image.php new file mode 100644 index 0000000000000000000000000000000000000000..089625ca1b340ff5718be52bdb31cc4b43c579ea --- /dev/null +++ b/scp/image.php @@ -0,0 +1,31 @@ +<?php +/********************************************************************* + image.php + + Simply downloads the file...on hash validation as follows; + + * Hash must be 64 chars long. + * First 32 chars is the perm. file hash + * Next 32 chars is md5(file_id.session_id().file_hash) + + Peter Rotich <peter@osticket.com> + Copyright (c) 2006-2013 osTicket + http://www.osticket.com + + Released under the GNU General Public License WITHOUT ANY WARRANTY. + See LICENSE.TXT for details. + + vim: expandtab sw=4 ts=4 sts=4: +**********************************************************************/ + +require('staff.inc.php'); +require_once(INCLUDE_DIR.'class.file.php'); +$h=trim($_GET['h']); +//basic checks +if(!$h || strlen($h)!=64 //32*2 + || !($file=AttachmentFile::lookup(substr($h,0,32))) //first 32 is the file hash. + || strcasecmp($h, $file->getDownloadHash())) //next 32 is file id + session hash. + die('Unknown or invalid file. #'.Format::htmlchars($_GET['h'])); + +$file->display(); +?> diff --git a/scp/images/icons/new_page.gif b/scp/images/icons/new_page.gif new file mode 100644 index 0000000000000000000000000000000000000000..0a675850c713e6b6ea9eda5920c39e158c2f05f8 Binary files /dev/null and b/scp/images/icons/new_page.gif differ diff --git a/scp/images/icons/pages.gif b/scp/images/icons/pages.gif new file mode 100644 index 0000000000000000000000000000000000000000..10a29ef0490733aaeee4f9b658cd1bb4962f021f Binary files /dev/null and b/scp/images/icons/pages.gif differ diff --git a/scp/images/nicEditorIcons.gif b/scp/images/nicEditorIcons.gif index 5cf1ebedfb7fab0a944c6e8a8e1afe2450153eca..a0fdc7e4f59fcb8b8a9f404d0576ca950c125431 100644 Binary files a/scp/images/nicEditorIcons.gif and b/scp/images/nicEditorIcons.gif differ diff --git a/scp/js/nicEdit.js b/scp/js/nicEdit.js index 86cdffd44350a44df70090f4b472524cc2d152aa..37b66e2a3dc7c7ebcf449e34e2e29dda43c7607c 100644 --- a/scp/js/nicEdit.js +++ b/scp/js/nicEdit.js @@ -4,8 +4,8 @@ * NicEdit is distributed under the terms of the MIT license * For more information visit http://nicedit.com/ * Do not remove this copyright message - */ -var bkExtend=function(){var A=arguments;if(A.length==1){A=[this,A[0]]}for(var B in A[1]){A[0][B]=A[1][B]}return A[0]};function bkClass(){}bkClass.prototype.construct=function(){};bkClass.extend=function(C){var A=function(){if(arguments[0]!==bkClass){return this.construct.apply(this,arguments)}};var B=new this(bkClass);bkExtend(B,C);A.prototype=B;A.extend=this.extend;return A};var bkElement=bkClass.extend({construct:function(B,A){if(typeof (B)=="string"){B=(A||document).createElement(B)}B=$BK(B);return B},appendTo:function(A){A.appendChild(this);return this},appendBefore:function(A){A.parentNode.insertBefore(this,A);return this},addEvent:function(B,A){bkLib.addEvent(this,B,A);return this},setContent:function(A){this.innerHTML=A;return this},pos:function(){var C=curtop=0;var B=obj=this;if(obj.offsetParent){do{C+=obj.offsetLeft;curtop+=obj.offsetTop}while(obj=obj.offsetParent)}var A=(!window.opera)?parseInt(this.getStyle("border-width")||this.style.border)||0:0;return[C+A,curtop+A+this.offsetHeight]},noSelect:function(){bkLib.noSelect(this);return this},parentTag:function(A){var B=this;do{if(B&&B.nodeName&&B.nodeName.toUpperCase()==A){return B}B=B.parentNode}while(B);return false},hasClass:function(A){return this.className.match(new RegExp("(\\s|^)nicEdit-"+A+"(\\s|$)"))},addClass:function(A){if(!this.hasClass(A)){this.className+=" nicEdit-"+A}return this},removeClass:function(A){if(this.hasClass(A)){this.className=this.className.replace(new RegExp("(\\s|^)nicEdit-"+A+"(\\s|$)")," ")}return this},setStyle:function(A){var B=this.style;for(var C in A){switch(C){case"float":B.cssFloat=B.styleFloat=A[C];break;case"opacity":B.opacity=A[C];B.filter="alpha(opacity="+Math.round(A[C]*100)+")";break;case"className":this.className=A[C];break;default:B[C]=A[C]}}return this},getStyle:function(A,C){var B=(!C)?document.defaultView:C;if(this.nodeType==1){return(B&&B.getComputedStyle)?B.getComputedStyle(this,null).getPropertyValue(A):this.currentStyle[bkLib.camelize(A)]}},remove:function(){this.parentNode.removeChild(this);return this},setAttributes:function(A){for(var B in A){this[B]=A[B]}return this}});var bkLib={isMSIE:(navigator.appVersion.indexOf("MSIE")!=-1),addEvent:function(C,B,A){(C.addEventListener)?C.addEventListener(B,A,false):C.attachEvent("on"+B,A)},toArray:function(C){var B=C.length,A=new Array(B);while(B--){A[B]=C[B]}return A},noSelect:function(B){if(B.setAttribute&&B.nodeName.toLowerCase()!="input"&&B.nodeName.toLowerCase()!="textarea"){B.setAttribute("unselectable","on")}for(var A=0;A<B.childNodes.length;A++){bkLib.noSelect(B.childNodes[A])}},camelize:function(A){return A.replace(/\-(.)/g,function(B,C){return C.toUpperCase()})},inArray:function(A,B){return(bkLib.search(A,B)!=null)},search:function(A,C){for(var B=0;B<A.length;B++){if(A[B]==C){return B}}return null},cancelEvent:function(A){A=A||window.event;if(A.preventDefault&&A.stopPropagation){A.preventDefault();A.stopPropagation()}return false},domLoad:[],domLoaded:function(){if(arguments.callee.done){return }arguments.callee.done=true;for(i=0;i<bkLib.domLoad.length;i++){bkLib.domLoad[i]()}},onDomLoaded:function(A){this.domLoad.push(A);if(document.addEventListener){document.addEventListener("DOMContentLoaded",bkLib.domLoaded,null)}else{if(bkLib.isMSIE){document.write("<style>.nicEdit-main p { margin: 0; }</style><script id=__ie_onload defer "+((location.protocol=="https:")?"src='javascript:void(0)'":"src=//0")+"><\/script>");$BK("__ie_onload").onreadystatechange=function(){if(this.readyState=="complete"){bkLib.domLoaded()}}}}window.onload=bkLib.domLoaded}};function $BK(A){if(typeof (A)=="string"){A=document.getElementById(A)}return(A&&!A.appendTo)?bkExtend(A,bkElement.prototype):A}var bkEvent={addEvent:function(A,B){if(B){this.eventList=this.eventList||{};this.eventList[A]=this.eventList[A]||[];this.eventList[A].push(B)}return this},fireEvent:function(){var A=bkLib.toArray(arguments),C=A.shift();if(this.eventList&&this.eventList[C]){for(var B=0;B<this.eventList[C].length;B++){this.eventList[C][B].apply(this,A)}}}};function __(A){return A}Function.prototype.closure=function(){var A=this,B=bkLib.toArray(arguments),C=B.shift();return function(){if(typeof (bkLib)!="undefined"){return A.apply(C,B.concat(bkLib.toArray(arguments)))}}};Function.prototype.closureListener=function(){var A=this,C=bkLib.toArray(arguments),B=C.shift();return function(E){E=E||window.event;if(E.target){var D=E.target}else{var D=E.srcElement}return A.apply(B,[E,D].concat(C))}}; + */ +var bkExtend=function(){var A=arguments;if(A.length==1){A=[this,A[0]]}for(var B in A[1]){A[0][B]=A[1][B]}return A[0]};function bkClass(){}bkClass.prototype.construct=function(){};bkClass.extend=function(C){var A=function(){if(arguments[0]!==bkClass){return this.construct.apply(this,arguments)}};var B=new this(bkClass);bkExtend(B,C);A.prototype=B;A.extend=this.extend;return A};var bkElement=bkClass.extend({construct:function(B,A){if(typeof (B)=="string"){B=(A||document).createElement(B)}B=$BK(B);return B},appendTo:function(A){A.appendChild(this);return this},appendBefore:function(A){A.parentNode.insertBefore(this,A);return this},addEvent:function(B,A){bkLib.addEvent(this,B,A);return this},setContent:function(A){this.innerHTML=A;return this},pos:function(){var C=curtop=0;var B=obj=this;if(obj.offsetParent){do{C+=obj.offsetLeft;curtop+=obj.offsetTop}while(obj=obj.offsetParent)}var A=(!window.opera)?parseInt(this.getStyle("border-width")||this.style.border)||0:0;return[C+A,curtop+A+this.offsetHeight]},noSelect:function(){bkLib.noSelect(this);return this},parentTag:function(A){var B=this;do{if(B&&B.nodeName&&B.nodeName.toUpperCase()==A){return B}B=B.parentNode}while(B);return false},hasClass:function(A){return this.className.match(new RegExp("(\\s|^)nicEdit-"+A+"(\\s|$)"))},addClass:function(A){if(!this.hasClass(A)){this.className+=" nicEdit-"+A}return this},removeClass:function(A){if(this.hasClass(A)){this.className=this.className.replace(new RegExp("(\\s|^)nicEdit-"+A+"(\\s|$)")," ")}return this},setStyle:function(A){var B=this.style;for(var C in A){switch(C){case"float":B.cssFloat=B.styleFloat=A[C];break;case"opacity":B.opacity=A[C];B.filter="alpha(opacity="+Math.round(A[C]*100)+")";break;case"className":this.className=A[C];break;default:B[C]=A[C]}}return this},getStyle:function(A,C){var B=(!C)?document.defaultView:C;if(this.nodeType==1){return(B&&B.getComputedStyle)?B.getComputedStyle(this,null).getPropertyValue(A):this.currentStyle[bkLib.camelize(A)]}},remove:function(){this.parentNode.removeChild(this);return this},setAttributes:function(A){for(var B in A){this[B]=A[B]}return this}});var bkLib={isMSIE:(navigator.appVersion.indexOf("MSIE")!=-1),addEvent:function(C,B,A){(C.addEventListener)?C.addEventListener(B,A,false):C.attachEvent("on"+B,A)},toArray:function(C){var B=C.length,A=new Array(B);while(B--){A[B]=C[B]}return A},noSelect:function(B){if(B.setAttribute&&B.nodeName.toLowerCase()!="input"&&B.nodeName.toLowerCase()!="textarea"){B.setAttribute("unselectable","on")}for(var A=0;A<B.childNodes.length;A++){bkLib.noSelect(B.childNodes[A])}},camelize:function(A){return A.replace(/\-(.)/g,function(B,C){return C.toUpperCase()})},inArray:function(A,B){return(bkLib.search(A,B)!=null)},search:function(A,C){for(var B=0;B<A.length;B++){if(A[B]==C){return B}}return null},cancelEvent:function(A){A=A||window.event;if(A.preventDefault&&A.stopPropagation){A.preventDefault();A.stopPropagation()}return false},domLoad:[],domLoaded:function(){if(arguments.callee.done){return }arguments.callee.done=true;for(i=0;i<bkLib.domLoad.length;i++){bkLib.domLoad[i]()}},onDomLoaded:function(A){this.domLoad.push(A);if(document.addEventListener){document.addEventListener("DOMContentLoaded",bkLib.domLoaded,null)}else{if(bkLib.isMSIE){document.write("<style>.nicEdit-main p { margin: 0; }</style><script id=__ie_onload defer "+((location.protocol=="https:")?"src='javascript:void(0)'":"src=//0")+"><\/script>");$BK("__ie_onload").onreadystatechange=function(){if(this.readyState=="complete"){bkLib.domLoaded()}}}}window.onload=bkLib.domLoaded}};function $BK(A){if(typeof (A)=="string"){A=document.getElementById(A)}return(A&&!A.appendTo)?bkExtend(A,bkElement.prototype):A}var bkEvent={addEvent:function(A,B){if(B){this.eventList=this.eventList||{};this.eventList[A]=this.eventList[A]||[];this.eventList[A].push(B)}return this},fireEvent:function(){var A=bkLib.toArray(arguments),C=A.shift();if(this.eventList&&this.eventList[C]){for(var B=0;B<this.eventList[C].length;B++){this.eventList[C][B].apply(this,A)}}}};function __(A){return A}Function.prototype.closure=function(){var A=this,B=bkLib.toArray(arguments),C=B.shift();return function(){if(typeof (bkLib)!="undefined"){return A.apply(C,B.concat(bkLib.toArray(arguments)))}}};Function.prototype.closureListener=function(){var A=this,C=bkLib.toArray(arguments),B=C.shift();return function(E){E=E||window.event;if(E.target){var D=E.target}else{var D=E.srcElement}return A.apply(B,[E,D].concat(C))}}; @@ -29,27 +29,27 @@ var nicEditorConfig = bkClass.extend({ 'hr' : {name : __('Horizontal Rule'), command : 'insertHorizontalRule', noActive : true} }, iconsPath : '../nicEditorIcons.gif', - buttonList : ['save','bold','italic','underline','left','center','right','justify','ol','ul','fontSize','fontFamily','fontFormat','indent','outdent','image','upload','link','unlink','forecolor','bgcolor'], - iconList : {"bgcolor":1,"forecolor":2,"bold":3,"center":4,"hr":5,"indent":6,"italic":7,"justify":8,"left":9,"ol":10,"outdent":11,"removeformat":12,"right":13,"save":24,"strikethrough":15,"subscript":16,"superscript":17,"ul":18,"underline":19,"image":20,"link":21,"unlink":22,"close":23,"arrow":25} - + buttonList : ['xhtml','save','bold','italic','underline','left','center','right','justify','ol','ul','fontSize','fontFamily','fontFormat','indent','outdent','image','upload','link','unlink','forecolor','bgcolor'], + iconList : {"xhtml":1,"bgcolor":2,"forecolor":3,"bold":4,"center":5,"hr":6,"indent":7,"italic":8,"justify":9,"left":10,"ol":11,"outdent":12,"removeformat":13,"right":14,"save":25,"strikethrough":16,"subscript":17,"superscript":18,"ul":19,"underline":20,"image":21,"link":22,"unlink":23,"close":24,"arrow":26} + }); -; -var nicEditors={nicPlugins:[],editors:[],registerPlugin:function(B,A){this.nicPlugins.push({p:B,o:A})},allTextAreas:function(C){var A=document.getElementsByTagName("textarea");for(var B=0;B<A.length;B++){nicEditors.editors.push(new nicEditor(C).panelInstance(A[B]))}return nicEditors.editors},findEditor:function(C){var B=nicEditors.editors;for(var A=0;A<B.length;A++){if(B[A].instanceById(C)){return B[A].instanceById(C)}}}};var nicEditor=bkClass.extend({construct:function(C){this.options=new nicEditorConfig();bkExtend(this.options,C);this.nicInstances=new Array();this.loadedPlugins=new Array();var A=nicEditors.nicPlugins;for(var B=0;B<A.length;B++){this.loadedPlugins.push(new A[B].p(this,A[B].o))}nicEditors.editors.push(this);bkLib.addEvent(document.body,"mousedown",this.selectCheck.closureListener(this))},panelInstance:function(B,C){B=this.checkReplace($BK(B));var A=new bkElement("DIV").setStyle({width:(B.clientWidth || parseInt(B.getStyle("width")))+"px"}).appendBefore(B);this.setPanel(A);return this.addInstance(B,C)},checkReplace:function(B){var A=nicEditors.findEditor(B);if(A){A.removeInstance(B);A.removePanel()}return B},addInstance:function(B,C){B=this.checkReplace($BK(B));if(B.contentEditable||!!window.opera){var A=new nicEditorInstance(B,C,this)}else{var A=new nicEditorIFrameInstance(B,C,this)}this.nicInstances.push(A);return this},removeInstance:function(C){C=$BK(C);var B=this.nicInstances;for(var A=0;A<B.length;A++){if(B[A].e==C){B[A].remove();this.nicInstances.splice(A,1)}}},removePanel:function(A){if(this.nicPanel){this.nicPanel.remove();this.nicPanel=null}},instanceById:function(C){C=$BK(C);var B=this.nicInstances;for(var A=0;A<B.length;A++){if(B[A].e==C){return B[A]}}},setPanel:function(A){this.nicPanel=new nicEditorPanel($BK(A),this.options,this);this.fireEvent("panel",this.nicPanel);return this},nicCommand:function(B,A){if(this.selectedInstance){this.selectedInstance.nicCommand(B,A)}},getIcon:function(D,A){var C=this.options.iconList[D];var B=(A.iconFiles)?A.iconFiles[D]:"";return{backgroundImage:"url('"+((C)?this.options.iconsPath:B)+"')",backgroundPosition:((C)?((C-1)*-18):0)+"px 0px"}},selectCheck:function(C,A){var B=false;do{if(A.className&&A.className.indexOf("nicEdit")!=-1){return false}}while(A=A.parentNode);this.fireEvent("blur",this.selectedInstance,A);this.lastSelectedInstance=this.selectedInstance;this.selectedInstance=null;return false}});nicEditor=nicEditor.extend(bkEvent); -var nicEditorInstance=bkClass.extend({isSelected:false,construct:function(G,D,C){this.ne=C;this.elm=this.e=G;this.options=D||{};newX=parseInt(G.clientWidth || G.getStyle("width"));newY=parseInt(G.getStyle("height"))||G.clientHeight;this.initialHeight=newY-8;var H=(G.nodeName.toLowerCase()=="textarea");if(H||this.options.hasPanel){var B=(bkLib.isMSIE&&!((typeof document.body.style.maxHeight!="undefined")&&document.compatMode=="CSS1Compat"));var E={width:newX+"px",border:"1px solid #ccc",borderTop:0,overflowY:"auto",overflowX:"hidden"};E[(B)?"height":"maxHeight"]=(this.ne.options.maxHeight)?this.ne.options.maxHeight+"px":null;this.editorContain=new bkElement("DIV").setStyle(E).appendBefore(G);var A=new bkElement("DIV").setStyle({width:(newX-8)+"px",margin:"4px",minHeight:newY+"px"}).addClass("main").appendTo(this.editorContain);G.setStyle({display:"none"});A.innerHTML=G.innerHTML;if(H){A.setContent(G.value);this.copyElm=G;var F=G.parentTag("FORM");if(F){bkLib.addEvent(F,"submit",this.saveContent.closure(this))}}A.setStyle((B)?{height:newY+"px"}:{overflow:"hidden"});this.elm=A}this.ne.addEvent("blur",this.blur.closure(this));this.init();this.blur()},init:function(){this.elm.setAttribute("contentEditable","true");if(this.getContent()==""){this.setContent("")}this.instanceDoc=document.defaultView;this.elm.addEvent("mousedown",this.selected.closureListener(this)).addEvent("keypress",this.keyDown.closureListener(this)).addEvent("focus",this.selected.closure(this)).addEvent("blur",this.blur.closure(this)).addEvent("keyup",this.selected.closure(this));this.ne.fireEvent("add",this)},remove:function(){this.saveContent();if(this.copyElm||this.options.hasPanel){this.editorContain.remove();this.e.setStyle({display:"block"});this.ne.removePanel()}this.disable();this.ne.fireEvent("remove",this)},disable:function(){this.elm.setAttribute("contentEditable","false")},getSel:function(){return(window.getSelection)?window.getSelection():document.selection},getRng:function(){var A=this.getSel();if(!A){return null}return(A.rangeCount>0)?A.getRangeAt(0):A.createRange()},selRng:function(A,B){if(window.getSelection){B.removeAllRanges();B.addRange(A)}else{A.select()}},selElm:function(){var C=this.getRng();if(C.startContainer){var D=C.startContainer;if(C.cloneContents().childNodes.length==1){for(var B=0;B<D.childNodes.length;B++){var A=D.childNodes[B].ownerDocument.createRange();A.selectNode(D.childNodes[B]);if(C.compareBoundaryPoints(Range.START_TO_START,A)!=1&&C.compareBoundaryPoints(Range.END_TO_END,A)!=-1){return $BK(D.childNodes[B])}}}return $BK(D)}else{return $BK((this.getSel().type=="Control")?C.item(0):C.parentElement())}},saveRng:function(){this.savedRange=this.getRng();this.savedSel=this.getSel()},restoreRng:function(){if(this.savedRange){this.selRng(this.savedRange,this.savedSel)}},keyDown:function(B,A){if(B.ctrlKey){this.ne.fireEvent("key",this,B)}},selected:function(C,A){if(!A){A=this.selElm()}if(!C.ctrlKey){var B=this.ne.selectedInstance;if(B!=this){if(B){this.ne.fireEvent("blur",B,A)}this.ne.selectedInstance=this;this.ne.fireEvent("focus",B,A)}this.ne.fireEvent("selected",B,A);this.isFocused=true;this.elm.addClass("selected")}return false},blur:function(){this.isFocused=false;this.elm.removeClass("selected")},saveContent:function(){if(this.copyElm||this.options.hasPanel){this.ne.fireEvent("save",this);(this.copyElm)?this.copyElm.value=this.getContent():this.e.innerHTML=this.getContent()}},getElm:function(){return this.elm},getContent:function(){this.content=this.getElm().innerHTML;this.ne.fireEvent("get",this);return this.content},setContent:function(A){this.content=A;this.ne.fireEvent("set",this);this.elm.innerHTML=this.content},nicCommand:function(B,A){document.execCommand(B,false,A)}}); -var nicEditorIFrameInstance=nicEditorInstance.extend({savedStyles:[],init:function(){var B=this.elm.innerHTML.replace(/^\s+|\s+$/g,"");this.elm.innerHTML="";(!B)?B="":B;this.initialContent=B;this.elmFrame=new bkElement("iframe").setAttributes({src:"javascript:;",frameBorder:0,allowTransparency:"true",scrolling:"no"}).setStyle({height:"100px",width:"100%"}).addClass("frame").appendTo(this.elm);if(this.copyElm){this.elmFrame.setStyle({width:(this.elm.offsetWidth-4)+"px"})}var A=["font-size","font-family","font-weight","color"];for(itm in A){this.savedStyles[bkLib.camelize(itm)]=this.elm.getStyle(itm)}setTimeout(this.initFrame.closure(this),50)},disable:function(){this.elm.innerHTML=this.getContent()},initFrame:function(){var B=$BK(this.elmFrame.contentWindow.document);B.designMode="on";B.open();var A=this.ne.options.externalCSS;B.write("<html><head>"+((A)?'<link href="'+A+'" rel="stylesheet" type="text/css" />':"")+'</head><body id="nicEditContent" style="margin: 0 !important; background-color: transparent !important;">'+this.initialContent+"</body></html>");B.close();this.frameDoc=B;this.frameWin=$BK(this.elmFrame.contentWindow);this.frameContent=$BK(this.frameWin.document.body).setStyle(this.savedStyles);this.instanceDoc=this.frameWin.document.defaultView;this.heightUpdate();this.frameDoc.addEvent("mousedown",this.selected.closureListener(this)).addEvent("keyup",this.heightUpdate.closureListener(this)).addEvent("keydown",this.keyDown.closureListener(this)).addEvent("keyup",this.selected.closure(this));this.ne.fireEvent("add",this)},getElm:function(){return this.frameContent},setContent:function(A){this.content=A;this.ne.fireEvent("set",this);this.frameContent.innerHTML=this.content;this.heightUpdate()},getSel:function(){return(this.frameWin)?this.frameWin.getSelection():this.frameDoc.selection},heightUpdate:function(){this.elmFrame.style.height=Math.max(this.frameContent.offsetHeight,this.initialHeight)+"px"},nicCommand:function(B,A){this.frameDoc.execCommand(B,false,A);setTimeout(this.heightUpdate.closure(this),100)}}); -var nicEditorPanel=bkClass.extend({construct:function(E,B,A){this.elm=E;this.options=B;this.ne=A;this.panelButtons=new Array();this.buttonList=bkExtend([],this.ne.options.buttonList);this.panelContain=new bkElement("DIV").setStyle({overflow:"hidden",width:"100%",border:"1px solid #cccccc",backgroundColor:"#efefef"}).addClass("panelContain");this.panelElm=new bkElement("DIV").setStyle({margin:"2px",marginTop:"0px",zoom:1,overflow:"hidden"}).addClass("panel").appendTo(this.panelContain);this.panelContain.appendTo(E);var C=this.ne.options;var D=C.buttons;for(button in D){this.addButton(button,C,true)}this.reorder();E.noSelect()},addButton:function(buttonName,options,noOrder){var button=options.buttons[buttonName];var type=(button.type)?eval("(typeof("+button.type+') == "undefined") ? null : '+button.type+";"):nicEditorButton;var hasButton=bkLib.inArray(this.buttonList,buttonName);if(type&&(hasButton||this.ne.options.fullPanel)){this.panelButtons.push(new type(this.panelElm,buttonName,options,this.ne));if(!hasButton){this.buttonList.push(buttonName)}}},findButton:function(B){for(var A=0;A<this.panelButtons.length;A++){if(this.panelButtons[A].name==B){return this.panelButtons[A]}}},reorder:function(){var C=this.buttonList;for(var B=0;B<C.length;B++){var A=this.findButton(C[B]);if(A){this.panelElm.appendChild(A.margin)}}},remove:function(){this.elm.remove()}}); -var nicEditorButton=bkClass.extend({construct:function(D,A,C,B){this.options=C.buttons[A];this.name=A;this.ne=B;this.elm=D;this.margin=new bkElement("DIV").setStyle({"float":"left",marginTop:"2px"}).appendTo(D);this.contain=new bkElement("DIV").setStyle({width:"20px",height:"20px"}).addClass("buttonContain").appendTo(this.margin);this.border=new bkElement("DIV").setStyle({backgroundColor:"#efefef",border:"1px solid #efefef"}).appendTo(this.contain);this.button=new bkElement("DIV").setStyle({width:"18px",height:"18px",overflow:"hidden",zoom:1,cursor:"pointer"}).addClass("button").setStyle(this.ne.getIcon(A,C)).appendTo(this.border);this.button.addEvent("mouseover",this.hoverOn.closure(this)).addEvent("mouseout",this.hoverOff.closure(this)).addEvent("mousedown",this.mouseClick.closure(this)).noSelect();if(!window.opera){this.button.onmousedown=this.button.onclick=bkLib.cancelEvent}B.addEvent("selected",this.enable.closure(this)).addEvent("blur",this.disable.closure(this)).addEvent("key",this.key.closure(this));this.disable();this.init()},init:function(){},hide:function(){this.contain.setStyle({display:"none"})},updateState:function(){if(this.isDisabled){this.setBg()}else{if(this.isHover){this.setBg("hover")}else{if(this.isActive){this.setBg("active")}else{this.setBg()}}}},setBg:function(A){switch(A){case"hover":var B={border:"1px solid #666",backgroundColor:"#ddd"};break;case"active":var B={border:"1px solid #666",backgroundColor:"#ccc"};break;default:var B={border:"1px solid #efefef",backgroundColor:"#efefef"}}this.border.setStyle(B).addClass("button-"+A)},checkNodes:function(A){var B=A;do{if(this.options.tags&&bkLib.inArray(this.options.tags,B.nodeName)){this.activate();return true}}while(B=B.parentNode&&B.className!="nicEdit");B=$BK(A);while(B.nodeType==3){B=$BK(B.parentNode)}if(this.options.css){for(itm in this.options.css){if(B.getStyle(itm,this.ne.selectedInstance.instanceDoc)==this.options.css[itm]){this.activate();return true}}}this.deactivate();return false},activate:function(){if(!this.isDisabled){this.isActive=true;this.updateState();this.ne.fireEvent("buttonActivate",this)}},deactivate:function(){this.isActive=false;this.updateState();if(!this.isDisabled){this.ne.fireEvent("buttonDeactivate",this)}},enable:function(A,B){this.isDisabled=false;this.contain.setStyle({opacity:1}).addClass("buttonEnabled");this.updateState();this.checkNodes(B)},disable:function(A,B){this.isDisabled=true;this.contain.setStyle({opacity:0.6}).removeClass("buttonEnabled");this.updateState()},toggleActive:function(){(this.isActive)?this.deactivate():this.activate()},hoverOn:function(){if(!this.isDisabled){this.isHover=true;this.updateState();this.ne.fireEvent("buttonOver",this)}},hoverOff:function(){this.isHover=false;this.updateState();this.ne.fireEvent("buttonOut",this)},mouseClick:function(){if(this.options.command){this.ne.nicCommand(this.options.command,this.options.commandArgs);if(!this.options.noActive){this.toggleActive()}}this.ne.fireEvent("buttonClick",this)},key:function(A,B){if(this.options.key&&B.ctrlKey&&String.fromCharCode(B.keyCode||B.charCode).toLowerCase()==this.options.key){this.mouseClick();if(B.preventDefault){B.preventDefault()}}}}); -var nicPlugin=bkClass.extend({construct:function(B,A){this.options=A;this.ne=B;this.ne.addEvent("panel",this.loadPanel.closure(this));this.init()},loadPanel:function(C){var B=this.options.buttons;for(var A in B){C.addButton(A,this.options)}C.reorder()},init:function(){}}); - +; +var nicEditors={nicPlugins:[],editors:[],registerPlugin:function(B,A){this.nicPlugins.push({p:B,o:A})},allTextAreas:function(C){var A=document.getElementsByTagName("textarea");for(var B=0;B<A.length;B++){nicEditors.editors.push(new nicEditor(C).panelInstance(A[B]))}return nicEditors.editors},findEditor:function(C){var B=nicEditors.editors;for(var A=0;A<B.length;A++){if(B[A].instanceById(C)){return B[A].instanceById(C)}}}};var nicEditor=bkClass.extend({construct:function(C){this.options=new nicEditorConfig();bkExtend(this.options,C);this.nicInstances=new Array();this.loadedPlugins=new Array();var A=nicEditors.nicPlugins;for(var B=0;B<A.length;B++){this.loadedPlugins.push(new A[B].p(this,A[B].o))}nicEditors.editors.push(this);bkLib.addEvent(document.body,"mousedown",this.selectCheck.closureListener(this))},panelInstance:function(B,C){B=this.checkReplace($BK(B));var A=new bkElement("DIV").setStyle({width:(parseInt(B.getStyle("width"))||B.clientWidth)+"px"}).appendBefore(B);this.setPanel(A);return this.addInstance(B,C)},checkReplace:function(B){var A=nicEditors.findEditor(B);if(A){A.removeInstance(B);A.removePanel()}return B},addInstance:function(B,C){B=this.checkReplace($BK(B));if(B.contentEditable||!!window.opera){var A=new nicEditorInstance(B,C,this)}else{var A=new nicEditorIFrameInstance(B,C,this)}this.nicInstances.push(A);return this},removeInstance:function(C){C=$BK(C);var B=this.nicInstances;for(var A=0;A<B.length;A++){if(B[A].e==C){B[A].remove();this.nicInstances.splice(A,1)}}},removePanel:function(A){if(this.nicPanel){this.nicPanel.remove();this.nicPanel=null}},instanceById:function(C){C=$BK(C);var B=this.nicInstances;for(var A=0;A<B.length;A++){if(B[A].e==C){return B[A]}}},setPanel:function(A){this.nicPanel=new nicEditorPanel($BK(A),this.options,this);this.fireEvent("panel",this.nicPanel);return this},nicCommand:function(B,A){if(this.selectedInstance){this.selectedInstance.nicCommand(B,A)}},getIcon:function(D,A){var C=this.options.iconList[D];var B=(A.iconFiles)?A.iconFiles[D]:"";return{backgroundImage:"url('"+((C)?this.options.iconsPath:B)+"')",backgroundPosition:((C)?((C-1)*-18):0)+"px 0px"}},selectCheck:function(C,A){var B=false;do{if(A.className&&A.className.indexOf("nicEdit")!=-1){return false}}while(A=A.parentNode);this.fireEvent("blur",this.selectedInstance,A);this.lastSelectedInstance=this.selectedInstance;this.selectedInstance=null;return false}});nicEditor=nicEditor.extend(bkEvent); +var nicEditorInstance=bkClass.extend({isSelected:false,construct:function(G,D,C){this.ne=C;this.elm=this.e=G;this.options=D||{};newX=parseInt(G.getStyle("width"))||G.clientWidth;newY=parseInt(G.getStyle("height"))||G.clientHeight;this.initialHeight=newY-8;var H=(G.nodeName.toLowerCase()=="textarea");if(H||this.options.hasPanel){var B=(bkLib.isMSIE&&!((typeof document.body.style.maxHeight!="undefined")&&document.compatMode=="CSS1Compat"));var E={width:newX+"px",border:"1px solid #ccc",borderTop:0,overflowY:"auto",overflowX:"hidden"};E[(B)?"height":"maxHeight"]=(this.ne.options.maxHeight)?this.ne.options.maxHeight+"px":null;this.editorContain=new bkElement("DIV").setStyle(E).appendBefore(G);var A=new bkElement("DIV").setStyle({width:(newX-8)+"px",margin:"4px",minHeight:newY+"px"}).addClass("main").appendTo(this.editorContain);G.setStyle({display:"none"});A.innerHTML=G.innerHTML;if(H){A.setContent(G.value);this.copyElm=G;var F=G.parentTag("FORM");if(F){bkLib.addEvent(F,"submit",this.saveContent.closure(this))}}A.setStyle((B)?{height:newY+"px"}:{overflow:"hidden"});this.elm=A}this.ne.addEvent("blur",this.blur.closure(this));this.init();this.blur()},init:function(){this.elm.setAttribute("contentEditable","true");if(this.getContent()==""){this.setContent("<br />")}this.instanceDoc=document.defaultView;this.elm.addEvent("mousedown",this.selected.closureListener(this)).addEvent("keypress",this.keyDown.closureListener(this)).addEvent("focus",this.selected.closure(this)).addEvent("blur",this.blur.closure(this)).addEvent("keyup",this.selected.closure(this));this.ne.fireEvent("add",this)},remove:function(){this.saveContent();if(this.copyElm||this.options.hasPanel){this.editorContain.remove();this.e.setStyle({display:"block"});this.ne.removePanel()}this.disable();this.ne.fireEvent("remove",this)},disable:function(){this.elm.setAttribute("contentEditable","false")},getSel:function(){return(window.getSelection)?window.getSelection():document.selection},getRng:function(){var A=this.getSel();if(!A||A.rangeCount===0){return }return(A.rangeCount>0)?A.getRangeAt(0):A.createRange()},selRng:function(A,B){if(window.getSelection){B.removeAllRanges();B.addRange(A)}else{A.select()}},selElm:function(){var C=this.getRng();if(!C){return }if(C.startContainer){var D=C.startContainer;if(C.cloneContents().childNodes.length==1){for(var B=0;B<D.childNodes.length;B++){var A=D.childNodes[B].ownerDocument.createRange();A.selectNode(D.childNodes[B]);if(C.compareBoundaryPoints(Range.START_TO_START,A)!=1&&C.compareBoundaryPoints(Range.END_TO_END,A)!=-1){return $BK(D.childNodes[B])}}}return $BK(D)}else{return $BK((this.getSel().type=="Control")?C.item(0):C.parentElement())}},saveRng:function(){this.savedRange=this.getRng();this.savedSel=this.getSel()},restoreRng:function(){if(this.savedRange){this.selRng(this.savedRange,this.savedSel)}},keyDown:function(B,A){if(B.ctrlKey){this.ne.fireEvent("key",this,B)}},selected:function(C,A){if(!A&&!(A=this.selElm)){A=this.selElm()}if(!C.ctrlKey){var B=this.ne.selectedInstance;if(B!=this){if(B){this.ne.fireEvent("blur",B,A)}this.ne.selectedInstance=this;this.ne.fireEvent("focus",B,A)}this.ne.fireEvent("selected",B,A);this.isFocused=true;this.elm.addClass("selected")}return false},blur:function(){this.isFocused=false;this.elm.removeClass("selected")},saveContent:function(){if(this.copyElm||this.options.hasPanel){this.ne.fireEvent("save",this);(this.copyElm)?this.copyElm.value=this.getContent():this.e.innerHTML=this.getContent()}},getElm:function(){return this.elm},getContent:function(){this.content=this.getElm().innerHTML;this.ne.fireEvent("get",this);return this.content},setContent:function(A){this.content=A;this.ne.fireEvent("set",this);this.elm.innerHTML=this.content},nicCommand:function(B,A){document.execCommand(B,false,A)}}); +var nicEditorIFrameInstance=nicEditorInstance.extend({savedStyles:[],init:function(){var B=this.elm.innerHTML.replace(/^\s+|\s+$/g,"");this.elm.innerHTML="";(!B)?B="<br />":B;this.initialContent=B;this.elmFrame=new bkElement("iframe").setAttributes({src:"javascript:;",frameBorder:0,allowTransparency:"true",scrolling:"no"}).setStyle({height:"100px",width:"100%"}).addClass("frame").appendTo(this.elm);if(this.copyElm){this.elmFrame.setStyle({width:(this.elm.offsetWidth-4)+"px"})}var A=["font-size","font-family","font-weight","color"];for(itm in A){this.savedStyles[bkLib.camelize(itm)]=this.elm.getStyle(itm)}setTimeout(this.initFrame.closure(this),50)},disable:function(){this.elm.innerHTML=this.getContent()},initFrame:function(){var B=$BK(this.elmFrame.contentWindow.document);B.designMode="on";B.open();var A=this.ne.options.externalCSS;B.write("<html><head>"+((A)?'<link href="'+A+'" rel="stylesheet" type="text/css" />':"")+'</head><body id="nicEditContent" style="margin: 0 !important; background-color: transparent !important;">'+this.initialContent+"</body></html>");B.close();this.frameDoc=B;this.frameWin=$BK(this.elmFrame.contentWindow);this.frameContent=$BK(this.frameWin.document.body).setStyle(this.savedStyles);this.instanceDoc=this.frameWin.document.defaultView;this.heightUpdate();this.frameDoc.addEvent("mousedown",this.selected.closureListener(this)).addEvent("keyup",this.heightUpdate.closureListener(this)).addEvent("keydown",this.keyDown.closureListener(this)).addEvent("keyup",this.selected.closure(this));this.ne.fireEvent("add",this)},getElm:function(){return this.frameContent},setContent:function(A){this.content=A;this.ne.fireEvent("set",this);this.frameContent.innerHTML=this.content;this.heightUpdate()},getSel:function(){return(this.frameWin)?this.frameWin.getSelection():this.frameDoc.selection},heightUpdate:function(){this.elmFrame.style.height=Math.max(this.frameContent.offsetHeight,this.initialHeight)+"px"},nicCommand:function(B,A){this.frameDoc.execCommand(B,false,A);setTimeout(this.heightUpdate.closure(this),100)}}); +var nicEditorPanel=bkClass.extend({construct:function(E,B,A){this.elm=E;this.options=B;this.ne=A;this.panelButtons=new Array();this.buttonList=bkExtend([],this.ne.options.buttonList);this.panelContain=new bkElement("DIV").setStyle({overflow:"hidden",width:"100%",border:"1px solid #cccccc",backgroundColor:"#efefef"}).addClass("panelContain");this.panelElm=new bkElement("DIV").setStyle({margin:"2px",marginTop:"0px",zoom:1,overflow:"hidden"}).addClass("panel").appendTo(this.panelContain);this.panelContain.appendTo(E);var C=this.ne.options;var D=C.buttons;for(button in D){this.addButton(button,C,true)}this.reorder();E.noSelect()},addButton:function(buttonName,options,noOrder){var button=options.buttons[buttonName];var type=(button.type)?eval("(typeof("+button.type+') == "undefined") ? null : '+button.type+";"):nicEditorButton;var hasButton=bkLib.inArray(this.buttonList,buttonName);if(type&&(hasButton||this.ne.options.fullPanel)){this.panelButtons.push(new type(this.panelElm,buttonName,options,this.ne));if(!hasButton){this.buttonList.push(buttonName)}}},findButton:function(B){for(var A=0;A<this.panelButtons.length;A++){if(this.panelButtons[A].name==B){return this.panelButtons[A]}}},reorder:function(){var C=this.buttonList;for(var B=0;B<C.length;B++){var A=this.findButton(C[B]);if(A){this.panelElm.appendChild(A.margin)}}},remove:function(){this.elm.remove()}}); +var nicEditorButton=bkClass.extend({construct:function(D,A,C,B){this.options=C.buttons[A];this.name=A;this.ne=B;this.elm=D;this.margin=new bkElement("DIV").setStyle({"float":"left",marginTop:"2px"}).appendTo(D);this.contain=new bkElement("DIV").setStyle({width:"20px",height:"20px"}).addClass("buttonContain").appendTo(this.margin);this.border=new bkElement("DIV").setStyle({backgroundColor:"#efefef",border:"1px solid #efefef"}).appendTo(this.contain);this.button=new bkElement("DIV").setStyle({width:"18px",height:"18px",overflow:"hidden",zoom:1,cursor:"pointer"}).addClass("button").setStyle(this.ne.getIcon(A,C)).appendTo(this.border);this.button.addEvent("mouseover",this.hoverOn.closure(this)).addEvent("mouseout",this.hoverOff.closure(this)).addEvent("mousedown",this.mouseClick.closure(this)).noSelect();if(!window.opera){this.button.onmousedown=this.button.onclick=bkLib.cancelEvent}B.addEvent("selected",this.enable.closure(this)).addEvent("blur",this.disable.closure(this)).addEvent("key",this.key.closure(this));this.disable();this.init()},init:function(){},hide:function(){this.contain.setStyle({display:"none"})},updateState:function(){if(this.isDisabled){this.setBg()}else{if(this.isHover){this.setBg("hover")}else{if(this.isActive){this.setBg("active")}else{this.setBg()}}}},setBg:function(A){switch(A){case"hover":var B={border:"1px solid #666",backgroundColor:"#ddd"};break;case"active":var B={border:"1px solid #666",backgroundColor:"#ccc"};break;default:var B={border:"1px solid #efefef",backgroundColor:"#efefef"}}this.border.setStyle(B).addClass("button-"+A)},checkNodes:function(A){var B=A;do{if(this.options.tags&&bkLib.inArray(this.options.tags,B.nodeName)){this.activate();return true}}while(B=B.parentNode&&B.className!="nicEdit");B=$BK(A);while(B.nodeType==3){B=$BK(B.parentNode)}if(this.options.css){for(itm in this.options.css){if(B.getStyle(itm,this.ne.selectedInstance.instanceDoc)==this.options.css[itm]){this.activate();return true}}}this.deactivate();return false},activate:function(){if(!this.isDisabled){this.isActive=true;this.updateState();this.ne.fireEvent("buttonActivate",this)}},deactivate:function(){this.isActive=false;this.updateState();if(!this.isDisabled){this.ne.fireEvent("buttonDeactivate",this)}},enable:function(A,B){this.isDisabled=false;this.contain.setStyle({opacity:1}).addClass("buttonEnabled");this.updateState();this.checkNodes(B)},disable:function(A,B){this.isDisabled=true;this.contain.setStyle({opacity:0.6}).removeClass("buttonEnabled");this.updateState()},toggleActive:function(){(this.isActive)?this.deactivate():this.activate()},hoverOn:function(){if(!this.isDisabled){this.isHover=true;this.updateState();this.ne.fireEvent("buttonOver",this)}},hoverOff:function(){this.isHover=false;this.updateState();this.ne.fireEvent("buttonOut",this)},mouseClick:function(){if(this.options.command){this.ne.nicCommand(this.options.command,this.options.commandArgs);if(!this.options.noActive){this.toggleActive()}}this.ne.fireEvent("buttonClick",this)},key:function(A,B){if(this.options.key&&B.ctrlKey&&String.fromCharCode(B.keyCode||B.charCode).toLowerCase()==this.options.key){this.mouseClick();if(B.preventDefault){B.preventDefault()}}}}); +var nicPlugin=bkClass.extend({construct:function(B,A){this.options=A;this.ne=B;this.ne.addEvent("panel",this.loadPanel.closure(this));this.init()},loadPanel:function(C){var B=this.options.buttons;for(var A in B){C.addButton(A,this.options)}C.reorder()},init:function(){}}); + var nicPaneOptions = { }; -var nicEditorPane=bkClass.extend({construct:function(D,C,B,A){this.ne=C;this.elm=D;this.pos=D.pos();this.contain=new bkElement("div").setStyle({zIndex:"99999",overflow:"hidden",position:"absolute",left:this.pos[0]+"px",top:this.pos[1]+"px"});this.pane=new bkElement("div").setStyle({fontSize:"12px",border:"1px solid #ccc",overflow:"hidden",padding:"4px",textAlign:"left",backgroundColor:"#ffffc9"}).addClass("pane").setStyle(B).appendTo(this.contain);if(A&&!A.options.noClose){this.close=new bkElement("div").setStyle({"float":"right",height:"16px",width:"16px",cursor:"pointer"}).setStyle(this.ne.getIcon("close",nicPaneOptions)).addEvent("mousedown",A.removePane.closure(this)).appendTo(this.pane)}this.contain.noSelect().appendTo(document.body);this.position();this.init()},init:function(){},position:function(){if(this.ne.nicPanel){var B=this.ne.nicPanel.elm;var A=B.pos();var C=A[0]+parseInt(B.getStyle("width"))-(parseInt(this.pane.getStyle("width"))+8);if(C<this.pos[0]){this.contain.setStyle({left:C+"px"})}}},toggle:function(){this.isVisible=!this.isVisible;this.contain.setStyle({display:((this.isVisible)?"block":"none")})},remove:function(){if(this.contain){this.contain.remove();this.contain=null}},append:function(A){A.appendTo(this.pane)},setContent:function(A){this.pane.setContent(A)}}); - -var nicEditorAdvancedButton=nicEditorButton.extend({init:function(){this.ne.addEvent("selected",this.removePane.closure(this)).addEvent("blur",this.removePane.closure(this))},mouseClick:function(){if(!this.isDisabled){if(this.pane&&this.pane.pane){this.removePane()}else{this.pane=new nicEditorPane(this.contain,this.ne,{width:(this.width||"270px"),backgroundColor:"#fff"},this);this.addPane();this.ne.selectedInstance.saveRng()}}},addForm:function(C,G){this.form=new bkElement("form").addEvent("submit",this.submit.closureListener(this));this.pane.append(this.form);this.inputs={};for(itm in C){var D=C[itm];var F="";if(G){F=G.getAttribute(itm)}if(!F){F=D.value||""}var A=C[itm].type;if(A=="title"){new bkElement("div").setContent(D.txt).setStyle({fontSize:"14px",fontWeight:"bold",padding:"0px",margin:"2px 0"}).appendTo(this.form)}else{var B=new bkElement("div").setStyle({overflow:"hidden",clear:"both"}).appendTo(this.form);if(D.txt){new bkElement("label").setAttributes({"for":itm}).setContent(D.txt).setStyle({margin:"2px 4px",fontSize:"13px",width:"50px",lineHeight:"20px",textAlign:"right","float":"left"}).appendTo(B)}switch(A){case"text":this.inputs[itm]=new bkElement("input").setAttributes({id:itm,value:F,type:"text"}).setStyle({margin:"2px 0",fontSize:"13px","float":"left",height:"20px",border:"1px solid #ccc",overflow:"hidden"}).setStyle(D.style).appendTo(B);break;case"select":this.inputs[itm]=new bkElement("select").setAttributes({id:itm}).setStyle({border:"1px solid #ccc","float":"left",margin:"2px 0"}).appendTo(B);for(opt in D.options){var E=new bkElement("option").setAttributes({value:opt,selected:(opt==F)?"selected":""}).setContent(D.options[opt]).appendTo(this.inputs[itm])}break;case"content":this.inputs[itm]=new bkElement("textarea").setAttributes({id:itm}).setStyle({border:"1px solid #ccc","float":"left"}).setStyle(D.style).appendTo(B);this.inputs[itm].value=F}}}new bkElement("input").setAttributes({type:"submit"}).setStyle({backgroundColor:"#efefef",border:"1px solid #ccc",margin:"3px 0","float":"left",clear:"both"}).appendTo(this.form);this.form.onsubmit=bkLib.cancelEvent},submit:function(){},findElm:function(B,A,E){var D=this.ne.selectedInstance.getElm().getElementsByTagName(B);for(var C=0;C<D.length;C++){if(D[C].getAttribute(A)==E){return $BK(D[C])}}},removePane:function(){if(this.pane){this.pane.remove();this.pane=null;this.ne.selectedInstance.restoreRng()}}}); - -var nicButtonTips=bkClass.extend({construct:function(A){this.ne=A;A.addEvent("buttonOver",this.show.closure(this)).addEvent("buttonOut",this.hide.closure(this))},show:function(A){this.timer=setTimeout(this.create.closure(this,A),400)},create:function(A){this.timer=null;if(!this.pane){this.pane=new nicEditorPane(A.button,this.ne,{fontSize:"12px",marginTop:"5px"});this.pane.setContent(A.options.name)}},hide:function(A){if(this.timer){clearTimeout(this.timer)}if(this.pane){this.pane=this.pane.remove()}}});nicEditors.registerPlugin(nicButtonTips); - +var nicEditorPane=bkClass.extend({construct:function(D,C,B,A){this.ne=C;this.elm=D;this.pos=D.pos();this.contain=new bkElement("div").setStyle({zIndex:"99999",overflow:"hidden",position:"absolute",left:this.pos[0]+"px",top:this.pos[1]+"px"});this.pane=new bkElement("div").setStyle({fontSize:"12px",border:"1px solid #ccc",overflow:"hidden",padding:"4px",textAlign:"left",backgroundColor:"#ffffc9"}).addClass("pane").setStyle(B).appendTo(this.contain);if(A&&!A.options.noClose){this.close=new bkElement("div").setStyle({"float":"right",height:"16px",width:"16px",cursor:"pointer"}).setStyle(this.ne.getIcon("close",nicPaneOptions)).addEvent("mousedown",A.removePane.closure(this)).appendTo(this.pane)}this.contain.noSelect().appendTo(document.body);this.position();this.init()},init:function(){},position:function(){if(this.ne.nicPanel){var B=this.ne.nicPanel.elm;var A=B.pos();var C=A[0]+parseInt(B.getStyle("width"))-(parseInt(this.pane.getStyle("width"))+8);if(C<this.pos[0]){this.contain.setStyle({left:C+"px"})}}},toggle:function(){this.isVisible=!this.isVisible;this.contain.setStyle({display:((this.isVisible)?"block":"none")})},remove:function(){if(this.contain){this.contain.remove();this.contain=null}},append:function(A){A.appendTo(this.pane)},setContent:function(A){this.pane.setContent(A)}}); + +var nicEditorAdvancedButton=nicEditorButton.extend({init:function(){this.ne.addEvent("selected",this.removePane.closure(this)).addEvent("blur",this.removePane.closure(this))},mouseClick:function(){if(!this.isDisabled){if(this.pane&&this.pane.pane){this.removePane()}else{this.pane=new nicEditorPane(this.contain,this.ne,{width:(this.width||"270px"),backgroundColor:"#fff"},this);this.addPane();this.ne.selectedInstance.saveRng()}}},addForm:function(C,G){this.form=new bkElement("form").addEvent("submit",this.submit.closureListener(this));this.pane.append(this.form);this.inputs={};for(itm in C){var D=C[itm];var F="";if(G){F=G.getAttribute(itm)}if(!F){F=D.value||""}var A=C[itm].type;if(A=="title"){new bkElement("div").setContent(D.txt).setStyle({fontSize:"14px",fontWeight:"bold",padding:"0px",margin:"2px 0"}).appendTo(this.form)}else{var B=new bkElement("div").setStyle({overflow:"hidden",clear:"both"}).appendTo(this.form);if(D.txt){new bkElement("label").setAttributes({"for":itm}).setContent(D.txt).setStyle({margin:"2px 4px",fontSize:"13px",width:"50px",lineHeight:"20px",textAlign:"right","float":"left"}).appendTo(B)}switch(A){case"text":this.inputs[itm]=new bkElement("input").setAttributes({id:itm,value:F,type:"text"}).setStyle({margin:"2px 0",fontSize:"13px","float":"left",height:"20px",border:"1px solid #ccc",overflow:"hidden"}).setStyle(D.style).appendTo(B);break;case"select":this.inputs[itm]=new bkElement("select").setAttributes({id:itm}).setStyle({border:"1px solid #ccc","float":"left",margin:"2px 0"}).appendTo(B);for(opt in D.options){var E=new bkElement("option").setAttributes({value:opt,selected:(opt==F)?"selected":""}).setContent(D.options[opt]).appendTo(this.inputs[itm])}break;case"content":this.inputs[itm]=new bkElement("textarea").setAttributes({id:itm}).setStyle({border:"1px solid #ccc","float":"left"}).setStyle(D.style).appendTo(B);this.inputs[itm].value=F}}}new bkElement("input").setAttributes({type:"submit"}).setStyle({backgroundColor:"#efefef",border:"1px solid #ccc",margin:"3px 0","float":"left",clear:"both"}).appendTo(this.form);this.form.onsubmit=bkLib.cancelEvent},submit:function(){},findElm:function(B,A,E){var D=this.ne.selectedInstance.getElm().getElementsByTagName(B);for(var C=0;C<D.length;C++){if(D[C].getAttribute(A)==E){return $BK(D[C])}}},removePane:function(){if(this.pane){this.pane.remove();this.pane=null;this.ne.selectedInstance.restoreRng()}}}); + +var nicButtonTips=bkClass.extend({construct:function(A){this.ne=A;A.addEvent("buttonOver",this.show.closure(this)).addEvent("buttonOut",this.hide.closure(this))},show:function(A){this.timer=setTimeout(this.create.closure(this,A),400)},create:function(A){this.timer=null;if(!this.pane){this.pane=new nicEditorPane(A.button,this.ne,{fontSize:"12px",marginTop:"5px"});this.pane.setContent(A.options.name)}},hide:function(A){if(this.timer){clearTimeout(this.timer)}if(this.pane){this.pane=this.pane.remove()}}});nicEditors.registerPlugin(nicButtonTips); + var nicSelectOptions = { buttons : { @@ -59,8 +59,8 @@ var nicSelectOptions = { } }; -var nicEditorSelect=bkClass.extend({construct:function(D,A,C,B){this.options=C.buttons[A];this.elm=D;this.ne=B;this.name=A;this.selOptions=new Array();this.margin=new bkElement("div").setStyle({"float":"left",margin:"2px 1px 0 1px"}).appendTo(this.elm);this.contain=new bkElement("div").setStyle({width:"90px",height:"20px",cursor:"pointer",overflow:"hidden"}).addClass("selectContain").addEvent("click",this.toggle.closure(this)).appendTo(this.margin);this.items=new bkElement("div").setStyle({overflow:"hidden",zoom:1,border:"1px solid #ccc",paddingLeft:"3px",backgroundColor:"#fff"}).appendTo(this.contain);this.control=new bkElement("div").setStyle({overflow:"hidden","float":"right",height:"18px",width:"16px"}).addClass("selectControl").setStyle(this.ne.getIcon("arrow",C)).appendTo(this.items);this.txt=new bkElement("div").setStyle({overflow:"hidden","float":"left",width:"66px",height:"14px",marginTop:"1px",fontFamily:"sans-serif",textAlign:"center",fontSize:"12px"}).addClass("selectTxt").appendTo(this.items);if(!window.opera){this.contain.onmousedown=this.control.onmousedown=this.txt.onmousedown=bkLib.cancelEvent}this.margin.noSelect();this.ne.addEvent("selected",this.enable.closure(this)).addEvent("blur",this.disable.closure(this));this.disable();this.init()},disable:function(){this.isDisabled=true;this.close();this.contain.setStyle({opacity:0.6})},enable:function(A){this.isDisabled=false;this.close();this.contain.setStyle({opacity:1})},setDisplay:function(A){this.txt.setContent(A)},toggle:function(){if(!this.isDisabled){(this.pane)?this.close():this.open()}},open:function(){this.pane=new nicEditorPane(this.items,this.ne,{width:"88px",padding:"0px",borderTop:0,borderLeft:"1px solid #ccc",borderRight:"1px solid #ccc",borderBottom:"0px",backgroundColor:"#fff"});for(var C=0;C<this.selOptions.length;C++){var B=this.selOptions[C];var A=new bkElement("div").setStyle({overflow:"hidden",borderBottom:"1px solid #ccc",width:"88px",textAlign:"left",overflow:"hidden",cursor:"pointer"});var D=new bkElement("div").setStyle({padding:"0px 4px"}).setContent(B[1]).appendTo(A).noSelect();D.addEvent("click",this.update.closure(this,B[0])).addEvent("mouseover",this.over.closure(this,D)).addEvent("mouseout",this.out.closure(this,D)).setAttributes("id",B[0]);this.pane.append(A);if(!window.opera){D.onmousedown=bkLib.cancelEvent}}},close:function(){if(this.pane){this.pane=this.pane.remove()}},over:function(A){A.setStyle({backgroundColor:"#ccc"})},out:function(A){A.setStyle({backgroundColor:"#fff"})},add:function(B,A){this.selOptions.push(new Array(B,A))},update:function(A){this.ne.nicCommand(this.options.command,A);this.close()}});var nicEditorFontSizeSelect=nicEditorSelect.extend({sel:{1:"1 (8pt)",2:"2 (10pt)",3:"3 (12pt)",4:"4 (14pt)",5:"5 (18pt)",6:"6 (24pt)"},init:function(){this.setDisplay("Font Size...");for(itm in this.sel){this.add(itm,'<font size="'+itm+'">'+this.sel[itm]+"</font>")}}});var nicEditorFontFamilySelect=nicEditorSelect.extend({sel:{arial:"Arial","comic sans ms":"Comic Sans","courier new":"Courier New",georgia:"Georgia",helvetica:"Helvetica",impact:"Impact","times new roman":"Times","trebuchet ms":"Trebuchet",verdana:"Verdana"},init:function(){this.setDisplay("Font Family...");for(itm in this.sel){this.add(itm,'<font face="'+itm+'">'+this.sel[itm]+"</font>")}}});var nicEditorFontFormatSelect=nicEditorSelect.extend({sel:{p:"Paragraph",pre:"Pre",h6:"Heading 6",h5:"Heading 5",h4:"Heading 4",h3:"Heading 3",h2:"Heading 2",h1:"Heading 1"},init:function(){this.setDisplay("Font Format...");for(itm in this.sel){var A=itm.toUpperCase();this.add("<"+A+">","<"+itm+' style="padding: 0px; margin: 0px;">'+this.sel[itm]+"</"+A+">")}}});nicEditors.registerPlugin(nicPlugin,nicSelectOptions); - +var nicEditorSelect=bkClass.extend({construct:function(D,A,C,B){this.options=C.buttons[A];this.elm=D;this.ne=B;this.name=A;this.selOptions=new Array();this.margin=new bkElement("div").setStyle({"float":"left",margin:"2px 1px 0 1px"}).appendTo(this.elm);this.contain=new bkElement("div").setStyle({width:"90px",height:"20px",cursor:"pointer",overflow:"hidden"}).addClass("selectContain").addEvent("click",this.toggle.closure(this)).appendTo(this.margin);this.items=new bkElement("div").setStyle({overflow:"hidden",zoom:1,border:"1px solid #ccc",paddingLeft:"3px",backgroundColor:"#fff"}).appendTo(this.contain);this.control=new bkElement("div").setStyle({overflow:"hidden","float":"right",height:"18px",width:"16px"}).addClass("selectControl").setStyle(this.ne.getIcon("arrow",C)).appendTo(this.items);this.txt=new bkElement("div").setStyle({overflow:"hidden","float":"left",width:"66px",height:"14px",marginTop:"1px",fontFamily:"sans-serif",textAlign:"center",fontSize:"12px"}).addClass("selectTxt").appendTo(this.items);if(!window.opera){this.contain.onmousedown=this.control.onmousedown=this.txt.onmousedown=bkLib.cancelEvent}this.margin.noSelect();this.ne.addEvent("selected",this.enable.closure(this)).addEvent("blur",this.disable.closure(this));this.disable();this.init()},disable:function(){this.isDisabled=true;this.close();this.contain.setStyle({opacity:0.6})},enable:function(A){this.isDisabled=false;this.close();this.contain.setStyle({opacity:1})},setDisplay:function(A){this.txt.setContent(A)},toggle:function(){if(!this.isDisabled){(this.pane)?this.close():this.open()}},open:function(){this.pane=new nicEditorPane(this.items,this.ne,{width:"88px",padding:"0px",borderTop:0,borderLeft:"1px solid #ccc",borderRight:"1px solid #ccc",borderBottom:"0px",backgroundColor:"#fff"});for(var C=0;C<this.selOptions.length;C++){var B=this.selOptions[C];var A=new bkElement("div").setStyle({overflow:"hidden",borderBottom:"1px solid #ccc",width:"88px",textAlign:"left",overflow:"hidden",cursor:"pointer"});var D=new bkElement("div").setStyle({padding:"0px 4px"}).setContent(B[1]).appendTo(A).noSelect();D.addEvent("click",this.update.closure(this,B[0])).addEvent("mouseover",this.over.closure(this,D)).addEvent("mouseout",this.out.closure(this,D)).setAttributes("id",B[0]);this.pane.append(A);if(!window.opera){D.onmousedown=bkLib.cancelEvent}}},close:function(){if(this.pane){this.pane=this.pane.remove()}},over:function(A){A.setStyle({backgroundColor:"#ccc"})},out:function(A){A.setStyle({backgroundColor:"#fff"})},add:function(B,A){this.selOptions.push(new Array(B,A))},update:function(A){this.ne.nicCommand(this.options.command,A);this.close()}});var nicEditorFontSizeSelect=nicEditorSelect.extend({sel:{1:"1 (8pt)",2:"2 (10pt)",3:"3 (12pt)",4:"4 (14pt)",5:"5 (18pt)",6:"6 (24pt)"},init:function(){this.setDisplay("Font Size...");for(itm in this.sel){this.add(itm,'<font size="'+itm+'">'+this.sel[itm]+"</font>")}}});var nicEditorFontFamilySelect=nicEditorSelect.extend({sel:{arial:"Arial","comic sans ms":"Comic Sans","courier new":"Courier New",georgia:"Georgia",helvetica:"Helvetica",impact:"Impact","times new roman":"Times","trebuchet ms":"Trebuchet",verdana:"Verdana"},init:function(){this.setDisplay("Font Family...");for(itm in this.sel){this.add(itm,'<font face="'+itm+'">'+this.sel[itm]+"</font>")}}});var nicEditorFontFormatSelect=nicEditorSelect.extend({sel:{p:"Paragraph",pre:"Pre",h6:"Heading 6",h5:"Heading 5",h4:"Heading 4",h3:"Heading 3",h2:"Heading 2",h1:"Heading 1"},init:function(){this.setDisplay("Font Format...");for(itm in this.sel){var A=itm.toUpperCase();this.add("<"+A+">","<"+itm+' style="padding: 0px; margin: 0px;">'+this.sel[itm]+"</"+A+">")}}});nicEditors.registerPlugin(nicPlugin,nicSelectOptions); + var nicLinkOptions = { buttons : { @@ -69,8 +69,8 @@ var nicLinkOptions = { } }; -var nicLinkButton=nicEditorAdvancedButton.extend({addPane:function(){this.ln=this.ne.selectedInstance.selElm().parentTag("A");this.addForm({"":{type:"title",txt:"Add/Edit Link"},href:{type:"text",txt:"URL",value:"http://",style:{width:"150px"}},title:{type:"text",txt:"Title"},target:{type:"select",txt:"Open In",options:{"":"Current Window",_blank:"New Window"},style:{width:"100px"}}},this.ln)},submit:function(C){var A=this.inputs.href.value;if(A=="http://"||A==""){alert("You must enter a URL to Create a Link");return false}this.removePane();if(!this.ln){var B="javascript:nicTemp();";this.ne.nicCommand("createlink",B);this.ln=this.findElm("A","href",B)}if(this.ln){this.ln.setAttributes({href:this.inputs.href.value,title:this.inputs.title.value,target:this.inputs.target.options[this.inputs.target.selectedIndex].value})}}});nicEditors.registerPlugin(nicPlugin,nicLinkOptions); - +var nicLinkButton=nicEditorAdvancedButton.extend({addPane:function(){this.ln=this.ne.selectedInstance.selElm().parentTag("A");this.addForm({"":{type:"title",txt:"Add/Edit Link"},href:{type:"text",txt:"URL",value:"http://",style:{width:"150px"}},title:{type:"text",txt:"Title"},target:{type:"select",txt:"Open In",options:{"":"Current Window",_blank:"New Window"},style:{width:"100px"}}},this.ln)},submit:function(C){var A=this.inputs.href.value;if(A=="http://"||A==""){alert("You must enter a URL to Create a Link");return false}this.removePane();if(!this.ln){var B="javascript:nicTemp();";this.ne.nicCommand("createlink",B);this.ln=this.findElm("A","href",B)}if(this.ln){this.ln.setAttributes({href:this.inputs.href.value,title:this.inputs.title.value,target:this.inputs.target.options[this.inputs.target.selectedIndex].value})}}});nicEditors.registerPlugin(nicPlugin,nicLinkOptions); + var nicColorOptions = { buttons : { @@ -79,18 +79,18 @@ var nicColorOptions = { } }; -var nicEditorColorButton=nicEditorAdvancedButton.extend({addPane:function(){var D={0:"00",1:"33",2:"66",3:"99",4:"CC",5:"FF"};var H=new bkElement("DIV").setStyle({width:"270px"});for(var A in D){for(var F in D){for(var E in D){var I="#"+D[A]+D[E]+D[F];var C=new bkElement("DIV").setStyle({cursor:"pointer",height:"15px","float":"left"}).appendTo(H);var G=new bkElement("DIV").setStyle({border:"2px solid "+I}).appendTo(C);var B=new bkElement("DIV").setStyle({backgroundColor:I,overflow:"hidden",width:"11px",height:"11px"}).addEvent("click",this.colorSelect.closure(this,I)).addEvent("mouseover",this.on.closure(this,G)).addEvent("mouseout",this.off.closure(this,G,I)).appendTo(G);if(!window.opera){C.onmousedown=B.onmousedown=bkLib.cancelEvent}}}}this.pane.append(H.noSelect())},colorSelect:function(A){this.ne.nicCommand("foreColor",A);this.removePane()},on:function(A){A.setStyle({border:"2px solid #000"})},off:function(A,B){A.setStyle({border:"2px solid "+B})}});var nicEditorBgColorButton=nicEditorColorButton.extend({colorSelect:function(A){this.ne.nicCommand("hiliteColor",A);this.removePane()}});nicEditors.registerPlugin(nicPlugin,nicColorOptions); - +var nicEditorColorButton=nicEditorAdvancedButton.extend({addPane:function(){var D={0:"00",1:"33",2:"66",3:"99",4:"CC",5:"FF"};var H=new bkElement("DIV").setStyle({width:"270px"});for(var A in D){for(var F in D){for(var E in D){var I="#"+D[A]+D[E]+D[F];var C=new bkElement("DIV").setStyle({cursor:"pointer",height:"15px","float":"left"}).appendTo(H);var G=new bkElement("DIV").setStyle({border:"2px solid "+I}).appendTo(C);var B=new bkElement("DIV").setStyle({backgroundColor:I,overflow:"hidden",width:"11px",height:"11px"}).addEvent("click",this.colorSelect.closure(this,I)).addEvent("mouseover",this.on.closure(this,G)).addEvent("mouseout",this.off.closure(this,G,I)).appendTo(G);if(!window.opera){C.onmousedown=B.onmousedown=bkLib.cancelEvent}}}}this.pane.append(H.noSelect())},colorSelect:function(A){this.ne.nicCommand("foreColor",A);this.removePane()},on:function(A){A.setStyle({border:"2px solid #000"})},off:function(A,B){A.setStyle({border:"2px solid "+B})}});var nicEditorBgColorButton=nicEditorColorButton.extend({colorSelect:function(A){this.ne.nicCommand("hiliteColor",A);this.removePane()}});nicEditors.registerPlugin(nicPlugin,nicColorOptions); + var nicImageOptions = { buttons : { 'image' : {name : 'Add Image', type : 'nicImageButton', tags : ['IMG']} } - + }; -var nicImageButton=nicEditorAdvancedButton.extend({addPane:function(){this.im=this.ne.selectedInstance.selElm().parentTag("IMG");this.addForm({"":{type:"title",txt:"Add/Edit Image"},src:{type:"text",txt:"URL",value:"http://",style:{width:"150px"}},alt:{type:"text",txt:"Alt Text",style:{width:"100px"}},align:{type:"select",txt:"Align",options:{none:"Default",left:"Left",right:"Right"}}},this.im)},submit:function(B){var C=this.inputs.src.value;if(C==""||C=="http://"){alert("You must enter a Image URL to insert");return false}this.removePane();if(!this.im){var A="javascript:nicImTemp();";this.ne.nicCommand("insertImage",A);this.im=this.findElm("IMG","src",A)}if(this.im){this.im.setAttributes({src:this.inputs.src.value,alt:this.inputs.alt.value,align:this.inputs.align.value})}}});nicEditors.registerPlugin(nicPlugin,nicImageOptions); - +var nicImageButton=nicEditorAdvancedButton.extend({addPane:function(){this.im=this.ne.selectedInstance.selElm().parentTag("IMG");this.addForm({"":{type:"title",txt:"Add/Edit Image"},src:{type:"text",txt:"URL",value:"http://",style:{width:"150px"}},alt:{type:"text",txt:"Alt Text",style:{width:"100px"}},align:{type:"select",txt:"Align",options:{none:"Default",left:"Left",right:"Right"}}},this.im)},submit:function(B){var C=this.inputs.src.value;if(C==""||C=="http://"){alert("You must enter a Image URL to insert");return false}this.removePane();if(!this.im){var A="javascript:nicImTemp();";this.ne.nicCommand("insertImage",A);this.im=this.findElm("IMG","src",A)}if(this.im){this.im.setAttributes({src:this.inputs.src.value,alt:this.inputs.alt.value,align:this.inputs.align.value})}}});nicEditors.registerPlugin(nicPlugin,nicImageOptions); + var nicSaveOptions = { buttons : { @@ -98,5 +98,15 @@ var nicSaveOptions = { } }; -var nicEditorSaveButton=nicEditorButton.extend({init:function(){if(!this.ne.options.onSave){this.margin.setStyle({display:"none"})}},mouseClick:function(){var B=this.ne.options.onSave;var A=this.ne.selectedInstance;B(A.getContent(),A.elm.id,A)}});nicEditors.registerPlugin(nicPlugin,nicSaveOptions); - +var nicEditorSaveButton=nicEditorButton.extend({init:function(){if(!this.ne.options.onSave){this.margin.setStyle({display:"none"})}},mouseClick:function(){var B=this.ne.options.onSave;var A=this.ne.selectedInstance;B(A.getContent(),A.elm.id,A)}});nicEditors.registerPlugin(nicPlugin,nicSaveOptions); + + +var nicCodeOptions = { + buttons : { + 'xhtml' : {name : 'Edit HTML', type : 'nicCodeButton'} + } + +}; + +var nicCodeButton=nicEditorAdvancedButton.extend({width:"750px",addPane:function(){this.addForm({"":{type:"title",txt:"Edit HTML"},code:{type:"content",value:this.ne.selectedInstance.getContent(),style:{width:"740px",height:"200px"}}})},submit:function(B){var A=this.inputs.code.value;this.ne.selectedInstance.setContent(A);this.removePane()}});nicEditors.registerPlugin(nicPlugin,nicCodeOptions); + diff --git a/scp/js/scp.js b/scp/js/scp.js index aa80487bddfcf77353bbc5b3307e92b38c1ec37b..ec2daf54261907dfaf64bc6fa64dcb71ccdf73f8 100644 --- a/scp/js/scp.js +++ b/scp/js/scp.js @@ -1,9 +1,9 @@ -/* +/* scp.js osTicket SCP Copyright (c) osTicket.com - + */ function checkbox_checker(formObj, min, max) { @@ -98,18 +98,20 @@ $(document).ready(function(){ $('.dialog#confirm-action p#'+this.name+'-confirm') .show() .parent('div').show().trigger('click'); - } - + } + return false; }); $(window).scroll(function () { - - $('.dialog').css({ - top : (($(this).height() /5)+$(this).scrollTop()), - left : ($(this).width() / 2 - 300) - }); - }); + var w = $(window); + $('.dialog').each(function() { + $(this).css({ + top : w.height() / 5 + w.scrollTop(), + left : (w.width() - $(this).outerWidth()) / 2 + }); + }); + }); if($.browser.msie) { $('.inactive').mouseenter(function() { @@ -228,11 +230,11 @@ $(document).ready(function(){ var sr_origin = '//' + host; var origin = protocol + sr_origin; // Allow absolute or scheme relative URLs to same origin - return (url == origin || url.slice(0, origin.length + 1) == origin + '/') || + return (url == origin || url.slice(0, origin.length + 1) == origin + '/') || (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') || // or any other URL that isn't scheme relative or absolute i.e // relative. - !(/^(\/\/|http:|https:).*/.test(url)); + !(/^(\/\/|http:|https:).*/.test(url)); } function safeMethod(method) { @@ -245,23 +247,35 @@ $(document).ready(function(){ }); /* Get config settings from the backend */ - var $config = null; - $.ajax({ - url: "ajax.php/config/scp", - dataType: 'json', - async: false, - success: function (config) { - $config = config; - } - }); - + jQuery.fn.exists = function() { return this.length>0; }; + + var getConfig = (function() { + var dfd = $.Deferred(); + return function() { + if (!dfd.isResolved()) + $.ajax({ + url: "ajax.php/config/scp", + dataType: 'json', + success: function (json_config) { + dfd.resolve(json_config); + } + }); + return dfd; + } + })(); /* Multifile uploads */ - $('.multifile').multifile({ - container: '.uploads', - max_uploads: ($config && $config.max_file_uploads)?$config.max_file_uploads:1, - file_types: ($config && $config.file_types)?$config.file_types:".*", - max_file_size: ($config && $config.max_file_size)?$config.max_file_size:0 + var elems = $('.multifile'); + if (elems.exists()) { + /* Get config settings from the backend */ + getConfig().then(function(c) { + elems.multifile({ + container: '.uploads', + max_uploads: c.max_file_uploads || 1, + file_types: c.file_types || ".*", + max_file_size: c.max_file_size || 0 + }); }); + } /* Datepicker */ $('.dp').datepicker({ @@ -327,13 +341,16 @@ $(document).ready(function(){ top : 0, left : 0 }); - + //Dialog - $('.dialog').css({ - top : ($(window).height() /5), - left : ($(window).width() / 2 - 300) + $('.dialog').each(function() { + var w = $(window); + $(this).css({ + top : w.height() / 5 + w.scrollTop(), + left : (w.width() - $(this).outerWidth()) / 2 + }); }); - + $('.dialog').delegate('input.close, a.close', 'click', function(e) { e.preventDefault(); $(this).parents('div.dialog').hide() @@ -348,11 +365,11 @@ $(document).ready(function(){ left : ($(window).width() / 2 - 300) }); - /* loading ... */ + /* loading ... */ $("#loading").css({ top : ($(window).height() / 3), - left : ($(window).width() / 2 - 160) - }); + left : ($(window).width() - $("#loading").outerWidth()) / 2 + }); $('#go-advanced').click(function(e) { e.preventDefault(); @@ -381,7 +398,7 @@ $(document).ready(function(){ } }); - $('#advanced-search form#search').submit(function(e) { + $('#advanced-search form#search').submit(function(e) { e.preventDefault(); var fObj = $(this); var elem = $('#advanced-search'); @@ -396,7 +413,7 @@ $(document).ready(function(){ return true; }, success: function (resp) { - + if(resp.success) { $('#result-count').html('<div class="success">' + resp.success +'</div>'); } else if (resp.fail) { diff --git a/scp/js/ticket.js b/scp/js/ticket.js index 56b02f20136a6218ee937c63de3705ccc8916285..ab83eab43ae8be4a65af0b15177d4ea93b9f5fe4 100644 --- a/scp/js/ticket.js +++ b/scp/js/ticket.js @@ -14,7 +14,7 @@ vim: expandtab sw=4 ts=4 sts=4: **********************************************************************/ var autoLock = { - + addEvent: function(elm, evType, fn, useCapture) { if(elm.addEventListener) { elm.addEventListener(evType, fn, useCapture); @@ -55,7 +55,7 @@ var autoLock = { return "Any changes or info you've entered will be discarded!"; }); } - + autoLock.lasteventTime=new Date().getTime(); }, @@ -75,7 +75,7 @@ var autoLock = { break; case 'select-one': case 'select-multiple': - if(fObj.name!='reply') //Bug on double ajax call since select make it's own ajax call. TODO: fix it + if(fObj.name!='reply') //Bug on double ajax call since select make it's own ajax call. TODO: fix it autoLock.addEvent(fObj[i],'change',autoLock.handleEvent,true); break; default: @@ -98,8 +98,8 @@ var autoLock = { //make sure we are on ticket view page & locking is enabled! var fObj=$('form#note'); - if(!fObj - || !$(':input[name=id]',fObj).length + if(!fObj + || !$(':input[name=id]',fObj).length || !$(':input[name=locktime]',fObj).length || $(':input[name=locktime]',fObj).val()==0) { return; @@ -124,7 +124,7 @@ var autoLock = { autoLock.resetTimer(); autoLock.addEvent(window,'unload',autoLock.releaseLock,true); //Release lock regardless of any activity. }, - + onSubmit: function(e) { if(e.type=='submit') { //Submit. double check! @@ -146,8 +146,8 @@ var autoLock = { } return true; }, - - acquireLock: function(e,warn) { + + acquireLock: function(e,warn) { if(!autoLock.tid) { return false; } @@ -168,16 +168,16 @@ var autoLock = { .done(function() { }) .fail(function() { }); } - + return autoLock.lockId; }, - //Renewal only happens on form activity.. + //Renewal only happens on form activity.. renewLock: function(e) { - + if(!autoLock.lockId) { return false; } - - var now= new Date().getTime(); + + var now= new Date().getTime(); if(!autoLock.lastcheckTime || (now-autoLock.lastcheckTime)>=(autoLock.renewFreq*1000)){ $.ajax({ type: 'POST', @@ -191,8 +191,8 @@ var autoLock = { .done(function() { }) .fail(function() { }); } - }, - + }, + releaseLock: function(e) { if(!autoLock.tid) { return false; } @@ -202,7 +202,7 @@ var autoLock = { data: 'delete', cache: false, success: function(){ - + } }) .done(function() { }) @@ -211,15 +211,15 @@ var autoLock = { setLock: function(lock, action, warn) { var warn = warn || false; - + if(!lock) return false; if(lock.id) { autoLock.renewFreq=lock.time?(lock.time/2):30; autoLock.lastcheckTime=new Date().getTime(); } - autoLock.lockId=lock.id; //overwrite the lockid. - + autoLock.lockId=lock.id; //override the lockid. + switch(action){ case 'renew': if(!lock.id && lock.retry) { @@ -232,18 +232,18 @@ var autoLock = { autoLock.lockAttempts++; if(warn && (!lock.retry || autoLock.lockAttempts>=autoLock.maxattempts)) { autoLock.retry=false; - alert('Unable to lock the ticket. Someone else could be working on the same ticket.'); + alert('Unable to lock the ticket. Someone else could be working on the same ticket.'); } - } + } break; } }, - + discardWarning: function(e) { e.returnValue="Any changes or info you've entered will be discarded!"; }, - //TODO: Monitor events and elapsed time and warn user when the lock is about to expire. + //TODO: Monitor events and elapsed time and warn user when the lock is about to expire. monitorEvents: function() { // warn user when lock is about to expire??; //autoLock.resetTimer(); @@ -252,7 +252,7 @@ var autoLock = { clearTimer: function() { clearTimeout(autoLock.timerId); }, - + resetTimer: function() { clearTimeout(autoLock.timerId); autoLock.timerId=setTimeout(function () { autoLock.monitorEvents() },30000); @@ -283,7 +283,7 @@ jQuery(function($) { } $('#reply_tab').click(function() { - $(this).removeClass('tell'); + $(this).removeClass('tell'); }); $('#note_tab').click(function() { @@ -341,7 +341,7 @@ jQuery(function($) { $('.dialog#ticket-status').show(); return false; }); - + //ticket actions confirmation - Delete + more $('a#ticket-delete, a#ticket-claim, #action-dropdown-more li a').click(function(e) { e.preventDefault(); diff --git a/scp/login.php b/scp/login.php index fcefaafd666660cdaf2d44c5e32e4b0bcfaeab37..2f3cf2236e9f4996bb10b94764fb6d0a14d99d22 100644 --- a/scp/login.php +++ b/scp/login.php @@ -24,7 +24,7 @@ $msg = $_SESSION['_staff']['auth']['msg']; $msg = $msg?$msg:'Authentication Required'; if($_POST) { //$_SESSION['_staff']=array(); #Uncomment to disable login strikes. - if(($user=Staff::login($_POST['username'], $_POST['passwd'], $errors))){ + if(($user=Staff::login($_POST['userid'], $_POST['passwd'], $errors))){ $dest=($dest && (!strstr($dest,'login.php') && !strstr($dest,'ajax.php')))?$dest:'index.php'; @header("Location: $dest"); require_once('index.php'); //Just incase header is messed up. diff --git a/scp/pages.php b/scp/pages.php new file mode 100644 index 0000000000000000000000000000000000000000..295852bafd112f2e4a1254e480862845a3cba558 --- /dev/null +++ b/scp/pages.php @@ -0,0 +1,108 @@ +<?php +/********************************************************************* + pages.php + + Site pages. + + Peter Rotich <peter@osticket.com> + Copyright (c) 2006-2013 osTicket + http://www.osticket.com + + Released under the GNU General Public License WITHOUT ANY WARRANTY. + See LICENSE.TXT for details. + + vim: expandtab sw=4 ts=4 sts=4: +**********************************************************************/ +require('admin.inc.php'); +require_once(INCLUDE_DIR.'class.page.php'); + +$page = null; +if($_REQUEST['id'] && !($page=Page::lookup($_REQUEST['id']))) + $errors['err']='Unknown or invalid page'; + +if($_POST) { + switch(strtolower($_POST['do'])) { + case 'add': + if(($pageId=Page::create($_POST, $errors))) { + $_REQUEST['a'] = null; + $msg='Page added successfully'; + } elseif(!$errors['err']) + $errors['err'] = 'Unable to add page. Try again!'; + break; + case 'update': + if(!$page) + $errors['err'] = 'Invalid or unknown page'; + elseif($page->update($_POST, $errors)) { + $msg='Page updated successfully'; + $_REQUEST['a']=null; //Go back to view + } elseif(!$errors['err']) + $errors['err'] = 'Unable to update page. Try again!'; + break; + case 'mass_process': + if(!$_POST['ids'] || !is_array($_POST['ids']) || !count($_POST['ids'])) { + $errors['err'] = 'You must select at least one page.'; + } elseif(array_intersect($_POST['ids'], $cfg->getDefaultPages()) && strcasecmp($_POST['a'], 'enable')) { + $errors['err'] = 'One or more of the selected pages is in-use and CANNOT be disabled/deleted.'; + } else { + $count=count($_POST['ids']); + switch(strtolower($_POST['a'])) { + case 'enable': + $sql='UPDATE '.PAGE_TABLE.' SET isactive=1 ' + .' WHERE id IN ('.implode(',', db_input($_POST['ids'])).')'; + if(db_query($sql) && ($num=db_affected_rows())) { + if($num==$count) + $msg = 'Selected pages enabled'; + else + $warn = "$num of $count selected pages enabled"; + } else { + $errors['err'] = 'Unable to enable selected pages'; + } + break; + case 'disable': + $i = 0; + foreach($_POST['ids'] as $k=>$v) { + if(($p=Page::lookup($v)) && $p->disable()) + $i++; + } + + if($i && $i==$count) + $msg = 'Selected pages disabled'; + elseif($i>0) + $warn = "$num of $count selected pages disabled"; + elseif(!$errors['err']) + $errors['err'] = 'Unable to disable selected pages'; + break; + case 'delete': + $i=0; + foreach($_POST['ids'] as $k=>$v) { + if(($p=Page::lookup($v)) && $p->delete()) + $i++; + } + + if($i && $i==$count) + $msg = 'Selected pages deleted successfully'; + elseif($i>0) + $warn = "$i of $count selected pages deleted"; + elseif(!$errors['err']) + $errors['err'] = 'Unable to delete selected pages'; + break; + default: + $errors['err']='Unknown action - get technical help.'; + } + } + break; + default: + $errors['err']='Unknown action/command'; + break; + } +} + +$inc='pages.inc.php'; +if($page || $_REQUEST['a']=='add') + $inc='page.inc.php'; + +$nav->setTabActive('manage'); +require_once(STAFFINC_DIR.'header.inc.php'); +require_once(STAFFINC_DIR.$inc); +require_once(STAFFINC_DIR.'footer.inc.php'); +?> diff --git a/scp/pwreset.php b/scp/pwreset.php new file mode 100644 index 0000000000000000000000000000000000000000..5b7a20fa86ab35ce1c9e701107a8a9e8be93dee0 --- /dev/null +++ b/scp/pwreset.php @@ -0,0 +1,90 @@ +<?php +/********************************************************************* + pwreset.php + + Handles step 2, 3 and 5 of password resetting + 1. Fail to login (2+ fail login attempts) + 2. Visit password reset form and enter username or email + 3. Receive an email with a link and follow it + 4. Visit password reset form again, with the link + 5. Enter the username or email address again and login + 6. Password change is now required, user changes password and + continues on with the session + + Peter Rotich <peter@osticket.com> + Jared Hancock <jared@osticket.com> + Copyright (c) 2006-2013 osTicket + http://www.osticket.com + + Released under the GNU General Public License WITHOUT ANY WARRANTY. + See LICENSE.TXT for details. + + vim: expandtab sw=4 ts=4 sts=4: +**********************************************************************/ +require_once('../main.inc.php'); +if(!defined('INCLUDE_DIR')) die('Fatal Error. Kwaheri!'); + +require_once(INCLUDE_DIR.'class.staff.php'); +require_once(INCLUDE_DIR.'class.csrf.php'); + +$tpl = 'pwreset.php'; +if($_POST) { + if (!$ost->checkCSRFToken()) { + Http::response(400, 'Valid CSRF Token Required'); + exit; + } + switch ($_POST['do']) { + case 'sendmail': + if (($staff=Staff::lookup($_POST['userid']))) { + if (!$staff->sendResetEmail()) { + $tpl = 'pwreset.sent.php'; + } + } + else + $msg = 'Unable to verify username ' + .Format::htmlchars($_POST['userid']); + break; + case 'newpasswd': + // TODO: Compare passwords + $tpl = 'pwreset.login.php'; + $_config = new Config('pwreset'); + if (($staff = new StaffSession($_POST['userid'])) && + !$staff->getId()) + $msg = 'Invalid user-id given'; + elseif (!($id = $_config->get($_POST['token'])) + || $id != $staff->getId()) + $msg = 'Invalid reset token'; + elseif (!($ts = $_config->lastModified($_POST['token'])) + && ($ost->getConfig()->getPwResetWindow() < (time() - strtotime($ts)))) + $msg = 'Invalid reset token'; + elseif (!$staff->forcePasswdRest()) + $msg = 'Unable to reset password'; + else { + $info = array('page' => 'index.php'); + Signal::send('auth.pwreset.login', $staff, $info); + Staff::_do_login($staff, $_POST['userid']); + $_SESSION['_staff']['reset-token'] = $_POST['token']; + header('Location: '.$info['page']); + exit(); + } + break; + } +} +elseif ($_GET['token']) { + $msg = 'Re-enter your username or email'; + $_config = new Config('pwreset'); + if (($id = $_config->get($_GET['token'])) + && ($staff = Staff::lookup($id))) + $tpl = 'pwreset.login.php'; + else + header('Location: index.php'); +} +elseif ($cfg->allowPasswordReset()) { + $msg = 'Enter your username or email address below'; +} +else { + $_SESSION['_staff']['auth']['msg']='Password resets are disabled'; + return header('Location: index.php'); +} +define("OSTSCPINC",TRUE); //Make includes happy! +include_once(INCLUDE_DIR.'staff/'. $tpl); diff --git a/scp/settings.php b/scp/settings.php index 845659408fba46dbc6c4319da7fe6098d8940311..9c8f70ac43d5cfd2b158334f690fd21529bbecaa 100644 --- a/scp/settings.php +++ b/scp/settings.php @@ -3,7 +3,7 @@ settings.php Handles all admin settings. - + Peter Rotich <peter@osticket.com> Copyright (c) 2006-2013 osTicket http://www.osticket.com @@ -19,6 +19,7 @@ $settingOptions=array( 'system' => 'System Settings', 'tickets' => 'Ticket Settings and Options', 'emails' => 'Email Settings', + 'pages' => 'Site Pages', 'kb' => 'Knowledgebase Settings', 'autoresp' => 'Autoresponder Settings', 'alerts' => 'Alerts and Notices Settings'); @@ -26,7 +27,6 @@ $settingOptions=array( if($_POST && !$errors) { if($cfg && $cfg->updateSettings($_POST,$errors)) { $msg=Format::htmlchars($settingOptions[$_POST['t']]).' Updated Successfully'; - $cfg->reload(); } elseif(!$errors['err']) { $errors['err']='Unable to update settings - correct errors below and try again'; } diff --git a/scp/staff.inc.php b/scp/staff.inc.php index 577fdd12c6a124a0d98dd50b792d080ab40f983e..0c835d46a780a5acf439af2268da039a73335905 100644 --- a/scp/staff.inc.php +++ b/scp/staff.inc.php @@ -1,7 +1,7 @@ <?php /************************************************************************* staff.inc.php - + File included on every staff page...handles logins (security) and file path issues. Peter Rotich <peter@osticket.com> @@ -17,7 +17,6 @@ if(basename($_SERVER['SCRIPT_NAME'])==basename(__FILE__)) die('Access denied'); if(!file_exists('../main.inc.php')) die('Fatal error... get technical support'); -define('ROOT_PATH','../'); //Path to the root dir. require_once('../main.inc.php'); if(!defined('INCLUDE_DIR')) die('Fatal error... invalid setting.'); @@ -42,13 +41,14 @@ require_once(INCLUDE_DIR.'class.nav.php'); require_once(INCLUDE_DIR.'class.csrf.php'); /* First order of the day is see if the user is logged in and with a valid session. - * User must be valid staff beyond this point + * User must be valid staff beyond this point * ONLY super admins can access the helpdesk on offline state. */ if(!function_exists('staffLoginPage')) { //Ajax interface can pre-declare the function to trap expired sessions. function staffLoginPage($msg) { + global $ost, $cfg; $_SESSION['_staff']['auth']['dest']=THISURI; $_SESSION['_staff']['auth']['msg']=$msg; require(SCP_DIR.'login.php'); @@ -58,8 +58,16 @@ if(!function_exists('staffLoginPage')) { //Ajax interface can pre-declare the fu $thisstaff = new StaffSession($_SESSION['_staff']['userID']); //Set staff object. //1) is the user Logged in for real && is staff. -if(!$thisstaff || !is_object($thisstaff) || !$thisstaff->getId() || !$thisstaff->isValid()){ - $msg=(!$thisstaff || !$thisstaff->isValid())?'Authentication Required':'Session timed out due to inactivity'; +if(!$thisstaff->getId() || !$thisstaff->isValid()){ + if (isset($_SESSION['_staff']['auth']['msg'])) { + $msg = $_SESSION['_staff']['auth']['msg']; + unset($_SESSION['_staff']['auth']['msg']); + } + elseif (isset($_SESSION['_staff']['userID']) && !$thisstaff->isValid()) + $msg = 'Session timed out due to inactivity'; + else + $msg = 'Authentication Required'; + staffLoginPage($msg); exit; } @@ -88,7 +96,7 @@ if ($_POST && !$ost->checkCSRFToken()) { exit; } -//Add token to the header - used on ajax calls [DO NOT CHANGE THE NAME] +//Add token to the header - used on ajax calls [DO NOT CHANGE THE NAME] $ost->addExtraHeader('<meta name="csrf_token" content="'.$ost->getCSRFToken().'" />'); /******* SET STAFF DEFAULTS **********/ diff --git a/scp/templates.php b/scp/templates.php index a27d69b08a67ac1748ff7b2457d92b238bc390e3..fd0ed3dde61e306030d5ee5d89fc40d393526ea8 100644 --- a/scp/templates.php +++ b/scp/templates.php @@ -16,7 +16,11 @@ require('admin.inc.php'); include_once(INCLUDE_DIR.'class.template.php'); $template=null; -if($_REQUEST['id'] && !($template=Template::lookup($_REQUEST['id']))) +if($_REQUEST['tpl_id'] && + !($template=EmailTemplateGroup::lookup($_REQUEST['tpl_id']))) + $errors['err']='Unknown or invalid template group ID.'; +elseif($_REQUEST['id'] && + !($template=EmailTemplate::lookup($_REQUEST['id']))) $errors['err']='Unknown or invalid template ID.'; if($_POST){ @@ -24,13 +28,23 @@ if($_POST){ case 'updatetpl': if(!$template){ $errors['err']='Unknown or invalid template'; - }elseif($template->updateMsgTemplate($_POST,$errors)){ + }elseif($template->update($_POST,$errors)){ $template->reload(); $msg='Message template updated successfully'; }elseif(!$errors['err']){ $errors['err']='Error updating message template. Try again!'; } break; + case 'implement': + if(!$template){ + $errors['err']='Unknown or invalid template'; + }elseif($new = EmailTemplate::add($_POST,$errors)){ + $template = $new; + $msg='Message template updated successfully'; + }elseif(!$errors['err']){ + $errors['err']='Error updating message template. Try again!'; + } + break; case 'update': if(!$template){ $errors['err']='Unknown or invalid template'; @@ -41,7 +55,8 @@ if($_POST){ } break; case 'add': - if((Template::create($_POST,$errors))){ + if(($new=EmailTemplateGroup::add($_POST,$errors))){ + $template=$new; $msg='Template added successfully'; $_REQUEST['a']=null; }elseif(!$errors['err']){ @@ -55,7 +70,7 @@ if($_POST){ $count=count($_POST['ids']); switch(strtolower($_POST['a'])) { case 'enable': - $sql='UPDATE '.EMAIL_TEMPLATE_TABLE.' SET isactive=1 ' + $sql='UPDATE '.EMAIL_TEMPLATE_GRP_TABLE.' SET isactive=1 ' .' WHERE tpl_id IN ('.implode(',', db_input($_POST['ids'])).')'; if(db_query($sql) && ($num=db_affected_rows())){ if($num==$count) @@ -69,7 +84,7 @@ if($_POST){ case 'disable': $i=0; foreach($_POST['ids'] as $k=>$v) { - if(($t=Template::lookup($v)) && !$t->isInUse() && $t->disable()) + if(($t=EmailTemplateGroup::lookup($v)) && !$t->isInUse() && $t->disable()) $i++; } if($i && $i==$count) @@ -82,7 +97,7 @@ if($_POST){ case 'delete': $i=0; foreach($_POST['ids'] as $k=>$v) { - if(($t=Template::lookup($v)) && !$t->isInUse() && $t->delete()) + if(($t=EmailTemplateGroup::lookup($v)) && !$t->isInUse() && $t->delete()) $i++; } @@ -107,6 +122,8 @@ if($_POST){ $page='templates.inc.php'; if($template && !strcasecmp($_REQUEST['a'],'manage')){ $page='tpl.inc.php'; +}elseif($template && !strcasecmp($_REQUEST['a'],'implement')){ + $page='tpl.inc.php'; }elseif($template || !strcasecmp($_REQUEST['a'],'add')){ $page='template.inc.php'; } diff --git a/scp/upgrade.php b/scp/upgrade.php index c66230a22288d131b3666a1a789df065397880f1..072ca79781e9fb1f9737a7350851fe37ad944941 100644 --- a/scp/upgrade.php +++ b/scp/upgrade.php @@ -17,7 +17,7 @@ require_once 'admin.inc.php'; require_once INCLUDE_DIR.'class.upgrader.php'; //$_SESSION['ost_upgrader']=null; -$upgrader = new Upgrader($cfg->getSchemaSignature(), TABLE_PREFIX, SQL_DIR); +$upgrader = new Upgrader(TABLE_PREFIX, UPGRADE_DIR.'streams/'); $errors=array(); if($_POST && $_POST['s'] && !$upgrader->isAborted()) { switch(strtolower($_POST['s'])) { @@ -32,11 +32,11 @@ if($_POST && $_POST['s'] && !$upgrader->isAborted()) { $errors['err']='Config file rename required to continue!'; } else { $upgrader->setState('upgrade'); - } + } break; case 'upgrade': //Manual upgrade.... when JS (ajax) is not supported. - if($upgrader->getNumPendingTasks()) { - $upgrader->doTasks(); + if($upgrader->getPendingTask()) { + $upgrader->doTask(); } elseif($ost->isUpgradePending() && $upgrader->isUpgradable()) { $upgrader->upgrade(); } elseif(!$ost->isUpgradePending()) { diff --git a/secure.inc.php b/secure.inc.php index 7b94ff92f2b10618de9a2921773c098f475ee4d8..bf6a75b3e032590ca6ec89346e50c67393fd5514 100644 --- a/secure.inc.php +++ b/secure.inc.php @@ -20,6 +20,7 @@ require_once('client.inc.php'); //Client Login page: Ajax interface can pre-declare the function to trap logins. if(!function_exists('clientLoginPage')) { function clientLoginPage($msg ='') { + global $ost; require('./login.php'); exit; } diff --git a/setup/cli/manage.php b/setup/cli/manage.php index c738318291c9fb861bad7139f7fd75403da6d876..bfd59f9f0fe2e1b85fcf3cbbbac90744ea4b2290 100755 --- a/setup/cli/manage.php +++ b/setup/cli/manage.php @@ -3,6 +3,9 @@ require_once "modules/class.module.php"; +if (!function_exists('noop')) { function noop() {} } +session_set_save_handler('noop','noop','noop','noop','noop','noop'); + class Manager extends Module { var $prologue = "Manage one or more osTicket installations"; @@ -20,32 +23,36 @@ class Manager extends Module { include_once $script; global $registered_modules; - $this->epilog = + $this->epilog = "Currently available modules follow. Use 'manage.php <module> --help' for usage regarding each respective module:"; parent::showHelp(); - + echo "\n"; foreach ($registered_modules as $name=>$mod) echo str_pad($name, 20) . $mod->prologue . "\n"; } - function run() { - if ($this->getOption('help') && !$this->getArgument('action')) + function run($args, $options) { + if ($options['help'] && !$args['action']) $this->showHelp(); else { - $action = $this->getArgument('action'); + $action = $args['action']; global $argv; foreach ($argv as $idx=>$val) if ($val == $action) unset($argv[$idx]); - include_once dirname(__file__) . '/modules/' . $action . '.php'; - $module = Module::getInstance($action); - $module->run(); + foreach (glob(dirname(__file__).'/modules/*.php') as $script) + include_once $script; + if (($module = Module::getInstance($action))) + return $module->_run($args['action']); + + $this->stderr->write("Unknown action given\n"); + $this->showHelp(); } } } @@ -55,6 +62,6 @@ if (php_sapi_name() != "cli") $manager = new Manager(); $manager->parseOptions(); -$manager->run(); +$manager->_run(basename(__file__)); ?> diff --git a/setup/cli/modules/class.module.php b/setup/cli/modules/class.module.php index de30f16321457bec2bb0c8762ed790d583e3ca6e..788cb21fbde330821ab9feb9855475ab9b156a57 100644 --- a/setup/cli/modules/class.module.php +++ b/setup/cli/modules/class.module.php @@ -1,7 +1,7 @@ <?php class Option { - + var $default = false; function Option() { @@ -23,6 +23,8 @@ class Option { : null; $this->metavar = (isset($options['metavar'])) ? $options['metavar'] : 'var'; + $this->nargs = (isset($options['nargs'])) ? $options['nargs'] + : 1; } function hasArg() { @@ -32,11 +34,13 @@ class Option { function handleValue(&$destination, $args) { $nargs = 0; - $value = array_shift($args); + $value = ($this->hasArg()) ? array_shift($args) : null; if ($value[0] == '-') $value = null; elseif ($value) $nargs = 1; + if ($this->type == 'int') + $value = (int)$value; switch ($this->action) { case 'store_true': $value = true; @@ -47,10 +51,17 @@ class Option { case 'store_const': $value = $this->const; break; + case 'append': + if (!isset($destination[$this->dest])) + $destination[$this->dest] = array($value); + else { + $T = &$destination[$this->dest]; + $T[] = $value; + $value = $T; + } + break; case 'store': default: - if ($this->type == 'int') - $value = (int)$value; break; } $destination[$this->dest] = $value; @@ -60,16 +71,16 @@ class Option { function toString() { $short = explode(':', $this->short); $long = explode(':', $this->long); - if ($this->nargs == '?') - $switches = sprintf(' %s [%3$s], %s[=%3$s]', $short[0], + if ($this->nargs === '?') + $switches = sprintf(' %s [%3$s], %s[=%3$s]', $short[0], # nolint $long[0], $this->metavar); elseif ($this->hasArg()) - $switches = sprintf(' %s %3$s, %s=%3$s', $short[0], $long[0], + $switches = sprintf(' %s %3$s, %s=%3$s', $short[0], $long[0], # nolint $this->metavar); else $switches = sprintf(" %s, %s", $short[0], $long[0]); $help = preg_replace('/\s+/', ' ', $this->help); - if (strlen($switches) > 24) + if (strlen($switches) > 23) $help = "\n" . str_repeat(" ", 24) . $help; else $switches = str_pad($switches, 24); @@ -78,6 +89,21 @@ class Option { } } +class OutputStream { + var $stream; + + function OutputStream() { + call_user_func_array(array($this, '__construct'), func_get_args()); + } + function __construct($stream) { + $this->stream = fopen($stream, 'w'); + } + + function write($what) { + fwrite($this->stream, $what); + } +} + class Module { var $options = array(); @@ -86,6 +112,13 @@ class Module { var $epilog = ""; var $usage = '$script [options] $args [arguments]'; var $autohelp = true; + var $module_name; + + var $stdout; + var $stderr; + + var $_options; + var $_args; function Module() { call_user_func_array(array($this, '__construct'), func_get_args()); @@ -97,17 +130,21 @@ class Module { 'help'=>"Display this help message"); foreach ($this->options as &$opt) $opt = new Option($opt); + $this->stdout = new OutputStream('php://output'); + $this->stderr = new OutputStream('php://stderr'); } function showHelp() { if ($this->prologue) echo $this->prologue . "\n\n"; - echo "Usage:\n"; global $argv; + $manager = @$argv[0]; + + echo "Usage:\n"; echo " " . str_replace( - array('$script', '$args'), - array($argv[0], implode(' ', array_keys($this->arguments))), + array('$script', '$args'), # nolint + array($manager ." ". $this->module_name, implode(' ', array_keys($this->arguments))), $this->usage) . "\n"; ksort($this->options); @@ -137,15 +174,16 @@ class Module { $this->parseOptions(); if (isset($this->_options[$name])) return $this->_options[$name]; + elseif (isset($this->options[$name]) && $this->options[$name]->default) + return $this->options[$name]->default; else return $default; } function getArgument($name, $default=false) { $this->parseOptions(); - foreach (array_keys($this->arguments) as $idx=>$arg) - if ($arg == $name && isset($this->_args[$idx])) - return $this->_args[$idx]; + if (isset($this->_args[$name])) + return $this->_args[$name]; return $default; } @@ -160,6 +198,12 @@ class Module { foreach (array_keys($this->arguments) as $idx=>$name) if (!isset($this->_args[$idx])) $this->optionError($name . " is a required argument"); + else + $this->_args[$name] = &$this->_args[$idx]; + + foreach ($this->options as $name=>$opt) + if (!isset($this->_options[$name])) + $this->_options[$name] = $opt->default; if ($this->autohelp && $this->getOption('help')) { $this->showHelp(); @@ -173,10 +217,18 @@ class Module { die(); } - /* abstract */ function run() { + function _run($module_name) { + $this->module_name = $module_name; + $this->parseOptions(); + return $this->run($this->_args, $this->_options); + } + + /* abstract */ + function run($args, $options) { } - /* static */ function register($action, $class) { + /* static */ + function register($action, $class) { global $registered_modules; $registered_modules[$action] = new $class(); } diff --git a/setup/cli/modules/deploy.php b/setup/cli/modules/deploy.php new file mode 100644 index 0000000000000000000000000000000000000000..4d48689927d1cd6fec2d087421229e3520fa23b9 --- /dev/null +++ b/setup/cli/modules/deploy.php @@ -0,0 +1,69 @@ +<?php +require_once dirname(__file__) . "/class.module.php"; +require_once dirname(__file__) . "/unpack.php"; + +class Deployment extends Unpacker { + var $prologue = "Deploys osTicket into target install path"; + + var $epilog = + "Deployment is used from the continuous development model. If you + are following the upstream git repo, then you can use the deploy + script to deploy changes made by you or upstream development to your + installation target"; + + function __construct() { + $this->options['dry-run'] = array('-t','--dry-run', + 'action'=>'store_true', + 'help'=>'Don\'t actually deploy new code. Just show the files + that would be copied'); + # super(*args); + call_user_func_array(array('parent', '__construct'), func_get_args()); + } + + function find_root_folder() { + # Hop up to the root folder of this repo + $start = dirname(__file__); + for (;;) { + if (is_file($start . '/main.inc.php')) break; + $start .= '/..'; + } + return realpath($start); + } + + function run($args, $options) { + $this->destination = $args['install-path']; + if (!is_dir($this->destination)) + if (!@mkdir($this->destination, 0751, true)) + die("Destination path does not exist and cannot be created"); + $this->destination = realpath($this->destination).'/'; + + # Determine if this is an upgrade, and if so, where the include/ + # folder is currently located + $upgrade = file_exists("{$this->destination}/main.inc.php"); + + # Get the current value of the INCLUDE_DIR before overwriting + # main.inc.php + $include = ($upgrade) ? $this->get_include_dir() + : ($options['include'] ? $options['include'] + : "{$this->destination}/include"); + if (substr($include, -1) !== '/') + $include .= '/'; + + # Locate the upload folder + $root = $this->find_root_folder(); + + # Unpack everything but the include/ folder + $this->unpackage("$root/{,.}*", $this->destination, -1, + array("$root/setup", "$root/include", "$root/.git*", + "*.sw[a-z]","*.md", "*.txt")); + # Unpack the include folder + $this->unpackage("$root/include/{,.}*", $include, -1, + array("*/include/ost-config.php")); + if (!$options['dry-run'] && !$upgrade + && $include != "{$this->destination}/include") + $this->change_include_dir($include); + } +} + +Module::register('deploy', 'Deployment'); +?> diff --git a/setup/cli/modules/export.php b/setup/cli/modules/export.php new file mode 100644 index 0000000000000000000000000000000000000000..f74647726e0c80ba632de5e61639582f1b01d62e --- /dev/null +++ b/setup/cli/modules/export.php @@ -0,0 +1,49 @@ +<?php +/********************************************************************* + cli/export.php + + osTicket data exporter, used for migration and backup + + Jared Hancock <jared@osticket.com> + Copyright (c) 2006-2013 osTicket + http://www.osticket.com + + Released under the GNU General Public License WITHOUT ANY WARRANTY. + See LICENSE.TXT for details. + + vim: expandtab sw=4 ts=4 sts=4: +**********************************************************************/ +require_once dirname(__file__) . "/class.module.php"; + +define('OSTICKET_BACKUP_SIGNATURE', 'osTicket-Backup'); +define('OSTICKET_BACKUP_VERSION', 'A'); + +class Exporter extends Module { + var $prologue = + "Dumps the osTicket database in formats suitable for the importer"; + + var $options = array( + 'stream' => array('-o', '--output', 'default'=>'php://stdout', + 'help'=> "File or stream to receive the exported output. As a + default, zlib compressed output is sent to standard out."), + 'compress' => array('-z', '--compress', 'action'=>'store_true', + 'help'=> "Send zlib compress data to the output stream"), + ); + + function run($args, $options) { + require_once dirname(__file__) . '/../../../main.inc.php'; + require_once INCLUDE_DIR . 'class.export.php'; + + global $ost; + + $stream = $options['stream']; + if ($options['compress']) $stream = "compress.zlib://$stream"; + $stream = fopen($stream, 'w'); + + $x = new DatabaseExporter($stream); + $x->dump($this->stderr); + } +} + +Module::register('export', 'Exporter'); +?> diff --git a/setup/cli/modules/import.php b/setup/cli/modules/import.php new file mode 100644 index 0000000000000000000000000000000000000000..e0f23d57a1a11e8b8b8c24df592edc4b36cd992e --- /dev/null +++ b/setup/cli/modules/import.php @@ -0,0 +1,240 @@ +<?php +/********************************************************************* + cli/import.php + + osTicket data importer, used for migration and backup recovery + + Jared Hancock <jared@osticket.com> + Copyright (c) 2006-2013 osTicket + http://www.osticket.com + + Released under the GNU General Public License WITHOUT ANY WARRANTY. + See LICENSE.TXT for details. + + vim: expandtab sw=4 ts=4 sts=4: +**********************************************************************/ +require_once dirname(__file__) . "/class.module.php"; + +class Importer extends Module { + var $prologue = + "Imports data from a previous backup (using the exporter)"; + + var $options = array( + 'stream' => array('-i', '--input', 'default'=>'php://stdin', + 'metavar'=>'FILE', 'help'=> + "File or stream from which to read the export. As a default, + data is received from standard in."), + 'compress' => array('-z', '--compress', 'action'=>'store_true', + 'help'=>'Read zlib compressed data (use -z with the export + command)'), + 'tables' => array('-t', '--table', 'action'=>'append', + 'metavar'=>'TABLE', 'help'=> + "Table to be restored from the backup. Default is to restore all + tables. This option can be specified more than once"), + 'drop' => array('-D', '--drop', 'action'=>'store_true', 'help'=> + 'Issue DROP TABLE statements before the create statemente'), + ); + + var $epilog = + "The SQL of the import is written to standard outout"; + + var $stream; + var $header; + var $source_ost_info; + + function verify_header() { + list($header, $info) = $this->read_block(); + if (!$header || $header[0] != OSTICKET_BACKUP_SIGNATURE) { + $this->stderr->write('Header mismatch -- not an osTicket backup'); + return false; + } + else + $this->header = $header; + + if (!$info || $info['dbtype'] != 'mysql') { + $this->stderr->write('Only mysql imports are supported currently'); + return false; + } + $this->source_ost_info = $info; + return true; + } + + function read_block() { + $block = ''; + while (!feof($this->stream) && (($c = fgetc($this->stream)) != "\x1e")) + $block .= $c; + + if ($json = JsonDataParser::decode($block)) + return $json; + + if (strlen($block)) { + $this->stderr->write("Unable to read block from input"); + die(); + } + } + + function send_statement($stmt) { + if ($this->getOption('prime-time')) + db_query($stmt); + else { + $this->stdout->write($stmt); + $this->stdout->write(";\n"); + } + } + + function import_table() { + if (!($header = $this->read_block())) + return false; + + else if ($header[0] != 'table') { + $this->stderr->write('Unable to read table header'); + return false; + } + + // TODO: Consider included tables and excluded tables + + $this->stderr->write("Importing table: {$header[1]}\n"); + $this->create_table($header); + $this->create_indexes($header); + + while (($row=$this->read_block())) { + if (isset($row[0]) && ($row[0] == 'end-table')) { + $this->load_row(null, null, true); + return true; + } + $this->load_row($header, $row); + } + return false; + } + + function create_table($info) { + if ($this->getOption('drop')) + $this->send_statement('DROP TABLE IF EXISTS `'.TABLE_PREFIX.'`'); + $sql = 'CREATE TABLE `'.TABLE_PREFIX.$info[1].'` ('; + $pk = array(); + $fields = array(); + foreach ($info[2] as $col) { + $field = "`{$col['Field']}` {$col['Type']}"; + if ($col['Null'] == 'NO') + $field .= ' NOT NULL '; + if ($col['Default'] == 'CURRENT_TIMESTAMP') + $field .= ' DEFAULT CURRENT_TIMESTAMP'; + elseif ($col['Default'] !== null) + $field .= ' DEFAULT '.db_input($col['Default']); + $field .= ' '.$col['Extra']; + $fields[] = $field; + } + // Generate PRIMARY KEY + foreach ($info[3] as $idx) { + if ($idx['Key_name'] == 'PRIMARY') { + $col = '`'.$idx['Column_name'].'`'; + if ($idx['Collation'] != 'A') + $col .= ' DESC'; + $pk[(int)$idx['Seq_in_index']] = $col; + } + } + $sql .= implode(", ", $fields); + if ($pk) + $sql .= ', PRIMARY KEY ('.implode(',',$pk).')'; + $sql .= ') DEFAULT CHARSET=utf8'; + $queries[] = $sql; + $this->send_statement($sql); + } + + function create_indexes($header) { + $indexes = array(); + foreach ($header[3] as $idx) { + if ($idx['Key_name'] == 'PRIMARY') + continue; + if (!isset($indexes[$idx['Key_name']])) + $indexes[$idx['Key_name']] = array( + 'cols'=>array(), + // XXX: Drop table-prefix + 'table'=>substr($idx['Table'], + strlen($this->source_ost_info['table_prefix'])), + 'type'=>$idx['Index_type'], + 'unique'=>!$idx['Non_unique']); + $index = &$indexes[$idx['Key_name']]; + $col = '`'.$idx['Column_name'].'`'; + if ($idx['Collation'] != 'A') + $col .= ' DESC'; + $index[(int)$idx['Seq_in_index']] = $col; + $index['cols'][] = $col; + } + foreach ($indexes as $name=>$info) { + $cols = array(); + $this->send_statement('CREATE ' + .(($info['unique']) ? 'UNIQUE ' : '') + .'INDEX `'.$name + .'` USING '.$info['type'] + .' ON `'.TABLE_PREFIX.$info['table'].'` (' + .implode(',', $info['cols']) + .')'); + } + } + + function truncate_table($info) { + $this->send_statement('TRUNCATE TABLE '.TABLE_PREFIX.$info[1]); + $indexes = array(); + foreach ($info[3] as $idx) { + if ($idx['Key_name'] == 'PRIMARY') + continue; + $indexes[$idx['Key_name']] = + '`'.TABLE_PREFIX.$info[1].'`.`'.$idx['Key_name'].'`'; + } + foreach ($indexes as $T=>$fqn) + $this->send_statement('DROP INDEX IF EXISTS '.$fqn); + } + + function load_row($info, $row, $flush=false) { + static $header = null; + static $rows = array(); + static $length = 0; + + if ($info && $header === null) { + $header = "INSERT INTO `".TABLE_PREFIX.$info[1].'` ('; + $cols = array(); + foreach ($info[2] as $col) + $cols[] = "`{$col['Field']}`"; + $header .= implode(', ', $cols); + $header .= ") VALUES "; + } + if ($row) { + $values = array(); + foreach ($info[2] as $i=>$col) + $values[] = (is_numeric($row[$i])) + ? $row[$i] + : ($row[$i] ? '0x'.bin2hex($row[$i]) : "''"); + $values = "(" . implode(', ', $values) . ")"; + $length += strlen($values); + $rows[] = &$values; + } + if (($flush || $length > 16000) && $header) { + $this->send_statement($header . implode(',', $rows)); + $header = null; + $rows = array(); + $length = 0; + } + } + + function run($args, $options) { + require_once dirname(__file__) . '/../../../main.inc.php'; + require_once INCLUDE_DIR . 'class.json.php'; + + $stream = $options['stream']; + if ($options['compress']) $stream = "compress.zlib://$stream"; + if (!($this->stream = fopen($stream, 'rb'))) { + $this->stderr->write('Unable to open input stream'); + die(); + } + + if (!$this->verify_header()) + die('Unable to verify backup header'); + + while ($this->import_table()); + @fclose($this->stream); + } +} + +Module::register('import', 'Importer'); +?> diff --git a/setup/cli/modules/unpack.php b/setup/cli/modules/unpack.php index 58fc70e4f781b6199829b35ba39c2a5dfa3e6d9b..50156ea7e77d9ecc1627de92887ba4f6f25e5c79 100644 --- a/setup/cli/modules/unpack.php +++ b/setup/cli/modules/unpack.php @@ -21,6 +21,9 @@ class Unpacker extends Module { code in that folder. The folder will be automatically created if it doesn't already exist." ), + 'verbose' => array('-v','--verbose', 'default'=>false, 'nargs'=>0, + 'action'=>'store_true', 'help'=> + "Move verbose logging to stdout"), ); var $arguments = array( @@ -45,13 +48,14 @@ class Unpacker extends Module { # Read the main.inc.php script $main_inc_php = $this->destination . '/main.inc.php'; $lines = explode("\n", file_get_contents($main_inc_php)); - # Try and use ROOT_PATH + # Try and use ROOT_DIR if (strpos($include_path, $this->destination) === 0) - $include_path = "ROOT_PATH . '" . + $include_path = "ROOT_DIR . '" . str_replace($this->destination, '', $include_path) . "'"; else $include_path = "'$include_path'"; # Find the line that defines INCLUDE_DIR + $match = array(); foreach ($lines as &$line) { if (preg_match("/(\s*)define\s*\(\s*'INCLUDE_DIR'/", $line, $match)) { # Replace the definition with the new locatin @@ -78,23 +82,52 @@ class Unpacker extends Module { return false; } - function unpackage($folder, $destination, $recurse=true, $exclude=false) { + /** + * Copy from source to desination, perhaps recursing up to n folders. + * Exclusions are also permitted. If any files match an MD5 sum, they + * will be excluded from the copy operation. + * + * Parameters: + * folder - (string) source folder root + * destination - (string) destination folder root + * recurse - (int) recuse up to this many folders. Use 0 or false to + * disable recursion, and -1 to recurse infinite folders. + * exclude - (string | array<string>) patterns that will be matched + * using the PHP `fnmatch` function. If any file or folder matches, + * it will be excluded from the copy procedure. Omit or use false + * to disable exclusions + */ + function unpackage($folder, $destination, $recurse=0, $exclude=false) { + $dryrun = $this->getOption('dry-run', false); + $verbose = $this->getOption('verbose') || $dryrun; + if (substr($destination, -1) !== '/') + $destination .= '/'; foreach (glob($folder, GLOB_BRACE|GLOB_NOSORT) as $file) { if ($this->exclude($exclude, $file)) continue; if (is_file($file)) { - if (!is_dir($destination)) + if (!is_dir($destination) && !$dryrun) mkdir($destination, 0751, true); - copy($file, $destination . '/' . basename($file)); + $target = $destination . basename($file); + if (is_file($target) && (md5_file($target) == md5_file($file))) + continue; + if ($verbose) + $this->stdout->write($target."\n"); + if (!$dryrun) + copy($file, $target); } } if ($recurse) { - foreach (glob(dirname($folder).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir) { - if ($this->exclude($exclude, $dir)) + $folders = glob(dirname($folder).'/'.basename($folder), + GLOB_BRACE|GLOB_ONLYDIR|GLOB_NOSORT); + foreach ($folders as $dir) { + if (in_array(basename($dir), array('.','..'))) + continue; + elseif ($this->exclude($exclude, $dir)) continue; $this->unpackage( dirname($folder).'/'.basename($dir).'/'.basename($folder), - $destination.'/'.basename($dir), + $destination.basename($dir), $recurse - 1, $exclude); } } @@ -114,8 +147,8 @@ class Unpacker extends Module { return INCLUDE_DIR; } - function run() { - $this->destination = $this->getArgument('install-path'); + function run($args, $options) { + $this->destination = $args['install-path']; if (!is_dir($this->destination)) if (!mkdir($this->destination, 0751, true)) die("Destination path does not exist and cannot be created"); @@ -132,7 +165,7 @@ class Unpacker extends Module { # Get the current value of the INCLUDE_DIR before overwriting # main.inc.php $include = $this->get_include_dir(); - $this->unpackage("$upload/*", $this->destination, -1, "*include"); + $this->unpackage("$upload/{,.}*", $this->destination, -1, "*include"); if (!$upgrade) { if ($this->getOption('include')) { @@ -140,14 +173,14 @@ class Unpacker extends Module { if (!is_dir("$location/")) if (!mkdir("$location/", 0751, true)) die("Unable to create folder for include/ files\n"); - $this->unpackage("$upload/include/*", $location, -1); + $this->unpackage("$upload/include/{,.}*", $location, -1); $this->change_include_dir($location); } else - $this->unpackage("$upload/include/*", "{$this->destination}/include", -1); + $this->unpackage("$upload/include/{,.}*", "{$this->destination}/include", -1); } else { - $this->unpackage("$upload/include/*", $include, -1); + $this->unpackage("$upload/include/{,.}*", $include, -1); # Change the new main.inc.php to reflect the location of the # include/ directory $this->change_include_dir($include); diff --git a/setup/cli/package.php b/setup/cli/package.php index 67d20a32b5d8bbcc7f232c2f7392430a232e99da..45fd517704822e7ae3e4c8d471bdb30b14d1e5ea 100755 --- a/setup/cli/package.php +++ b/setup/cli/package.php @@ -84,13 +84,15 @@ mkdir($stage_path . '/upload'); # Load the root directory files package("*.php", 'upload/'); +package("web.config", 'upload/'); # Load the client interface foreach (array('assets','css','images','js') as $dir) package("$dir/*", "upload/$dir", -1, "*less"); -# Load API +# Load API and pages package('api/{,.}*', 'upload/api'); +package('pages/{,.}*', 'upload/pages'); # Load the knowledgebase package("kb/*.php", "upload/kb"); @@ -111,7 +113,7 @@ package("include/{,.}*", "upload/include", -1, array('*ost-config.php', '*.sw[a- package("setup/*.{php,txt}", "upload/setup", -1, array("*scripts","*test","*stage")); foreach (array('css','images','js') as $dir) package("setup/$dir/*", "upload/setup/$dir", -1); -package("setup/inc/sql/*.{sql,md5}", "upload/setup/inc/sql", -1); +package("setup/inc/streams/*.sql", "upload/setup/inc/streams", -1); # Load the license and documentation package("*.{txt,md}", ""); @@ -123,16 +125,17 @@ if(($mds = glob("$stage_path/*.md"))) { } # Make an archive of the stage folder -$version_info = preg_grep('/THIS_VERSION/', - explode("\n", file_get_contents("$root/main.inc.php"))); - -foreach ($version_info as $line) - eval($line); +$version = exec('git describe'); $pwd = getcwd(); chdir($stage_path); -shell_exec("tar cjf '$pwd/osTicket-".THIS_VERSION.".tar.bz2' *"); -shell_exec("zip -r '$pwd/osTicket-".THIS_VERSION.".zip' *"); + +// Replace THIS_VERSION in the stage/ folder + +shell_exec("grep -rl \"define('THIS_VERSION'\" * | xargs sed -ri -e \"s/( *).*THIS_VERSION.*/\\1define('THIS_VERSION', '$version');/\""); + +shell_exec("tar cjf '$pwd/osTicket-$version.tar.bz2' *"); +shell_exec("zip -r '$pwd/osTicket-$version.zip' *"); chdir($pwd); ?> diff --git a/setup/doc/api.md b/setup/doc/api.md index f40d7423c68730d2a20a2df58b36a6a1b2104a17..db9774af74848351f4c180abadeb66ef14af0576 100644 --- a/setup/doc/api.md +++ b/setup/doc/api.md @@ -17,6 +17,24 @@ API keys can be created and managed via the admin panel. Navigate to Manage special configuration is required to allow the API key to be used for the HTTP API. All API keys are valid for the HTTP API. +HTTP Access +----------- +Access to the HTTP API is restricted to valid API keys. An `X-API-Key` HTTP +header must be sent to indicate which API key is to be used with the +request. The API key must match the remote IP of the connected HTTP client. +The remote IP is checked as usual. If the osTicket server is sitting behind +a reverse proxy, the original IP of the client will be retrieved from the +`X-Forwarded-For` header, if provided by your proxy. + +Example: + + X-API-Key: BA00B76BAA30F62E1940B46CC1C3C73C + +Commandline Example with Curl: + + curl -d "{}" -H "X-API-Key: BA00B76BAA30F62E1940B46CC1C3C73C" + https://support.you.tld/api/tickets.json + Wrappers -------- diff --git a/setup/doc/signals.md b/setup/doc/signals.md new file mode 100644 index 0000000000000000000000000000000000000000..68c5e1513305ae8b615ece591c2fb8364099b9a8 --- /dev/null +++ b/setup/doc/signals.md @@ -0,0 +1,109 @@ +osTicket Signals API +==================== +osTicket uses a very simple publish and subscribe signal model to add +extensibility. To keep things simplistic between classes and to maintain +compatibility with PHP version 4, signals will not be explicitly defined or +registered. Instead, signals are connected to callbacks via a string signal +name. + +The system is proofed with a static inspection test which will ensure that +for every given Signal::connect() function call, somewhere else in the +codebase there exists a Signal::send() for the same-named signal. + +Publishing a signal +------------------- + $info = array('username'=>'blah'); + Signal::send('signal.name', $this, $info); + +All subscribers to the signal will be called in the order they connect()ed +to the signal. Subscribers do not have the opportunity to interrupt or +discontinue delivery of the signal to other subscribers. The $object +argument is required and should almost always be ($this). Its interpretation +is the object originating or sending the signal. It could also be +interpreted as the context of the signal. + +$data if sent should be a hash-array of data included with the signal event. +There is otherwise no definition for what should or could be included in the +$data array. The received data is received by reference and can be passed to +the callable by reference, if the callable is defined to receive it by +reference. Therefore, it is possible to propagate changes in the signal +handlers back to the originating context. + +Connecting to a signal +---------------------- + Signal::connect('signal.name', 'function', optional 'check_callable'); + +The subscribed function should receive two arguments and will have this +signature: + + function callback($object, $data); + +Where the $object argument is the object originating the signal, called the +context, and the $data is a hash-array of other information originating +from- and pertaining to the signal. + +The exact value of the $data argument is not defined. It is signal specific. +It should be a hash-array of data; however, no runtime checks are made to +ensure such an interface. + +Optionally, if $object is a class and is passed into the ::connect() method, +only instances of the named class or subclass will actually be connected to +the callable function. + +A predicate function, $check, can be used to filter calls to the signal +handler. The function will receive the signal data and should return true if +the signal handler should be called. + +Signals in osTicket +------------------- +**auth.login.succeeded** +Sent after a successful login is process for a user + +Context: +Object<StaffSession> - Staff object retrieved from the login credentials + +Parameters: +(none) + +**auth.login.failed** +Sent after an unsuccessful login is attempted by a user. + +Context: +null + +Arguments: + * **username**: *read-only* username submitted to the login form + * **passowrd**: *read-only* password submitted to the login form + +**auth.pwreset.email** +Sent just before an email is sent to the user with the password reset token + +Context: +Object<Staff> - Staff object who will receive the email + +Parameters: + * **email**: *read-only* email object used to send the email + * **vars**: (array) template variables used to render the password-reset + email template + +**auth.pwreset.login** +Sent just before processing the automatic login for the staff from the link +and token provided in the password-reset email. This signal is only sent if +the token presented is considered completely valid and the password for the +staff is forced to-be-changed. + +Context: +Object<Staff> - Staff being logged in from the reset token + +Parameters: + * **page**: Page / URL sent in the redirect to the user. In other words, + the next page the staff will see. + +**auth.pwchange** +Sent when the password for a user is changed + +Context: +Object<Staff> - Staff whose password is being changed + +Parameters: + * **password**: New password (clear-text) for the user diff --git a/setup/doc/streams.md b/setup/doc/streams.md new file mode 100644 index 0000000000000000000000000000000000000000..823e7ee5c3921f7c3b226161884dc66312b4b04c --- /dev/null +++ b/setup/doc/streams.md @@ -0,0 +1,114 @@ +osTicket Database Migration +=========================== + +Database Upgrade Streams +------------------------ +Database upgrade streams are registered in the +`INCLUDE_DIR/upgrader/streams/streams.cfg`. This file contains the names of +the upgrade streams in the order the streams should be applied in. The stock +osTicket install does not ship with a `streams.cfg`, so that updates to the +source code base will not destroy your `streams.cfg` file. If the file does +not exist, only the osTicket `core` stream will be updated. + +Streams folders +--------------- +Stream folders are created in the `INCLUDE_DIR/upgrader/streams` folder. The +name of the stream will be the name of the folder. For instance, the core +osTicket stream exists in the `core` folder, and so is called `core`. Each +stream folder should also have an accompanying hash file which gives the md5 +hash of the tip of the stream. How you generate the hashes is up to you. For +the core stream, we use the md5 hash of the install SQL file. Changes made +to the main install file result in changes to the md5 hash of the file. +Then, the update file placed in the upgrade stream will have the changes +between to two hashes. + +Upgrade Streams +--------------- +Upgrade patches are used to migrate the database from snapshot to snapshot. +The system will start upgrading by consulting the `schema_signature` +configuration setting in the `config` table in the namespace of your stream +name. It will look for an upgrade patch file that starts with the first +eight characters of the current signature. + +Each upgrade in a stream should set the `schema_signature` configuration +option in the `config` when completed. The plan is to make this automatic, +but currently, it is still a manual process. Whatever the hash of then last +executed patch is, it should be reflected in the config table at the +completion of the upgrade patch. + +The migration process will continue until the hash reflected in the +`schema_signature` setting is the same as the value given in the stream +signature file. If no patch files are given to migrate from the current +`schema_signature` value to the value listed in the md5 file, then the +migrater will fail and complain that the system is not upgradeable. + +Patch Files +----------- +Patch files should live in your stream folder and should have the name of + + 12345678-00abcdef.patch.sql + +and should contain only SQL text. Double-dash comments are only supported if +started at the beginning of a line. For instance, do not write then inline +as part of a long running SQL statement. The filename format is the first +eight chars of the starting and ending database `schema_signature` values. + +Your patch process can be separated into two parts if you like. A cleanup file can be used to cleanup database objects after the completion of the patch process. Cleanup files must have the name of + + 12345678-00abcdef.cleanup.sql + +Where the starting and ending hashes are listed with a hyphen in between. +The idea is that PHP code can be run between the two SQL patch files. +//Currently, support for this is hardcoded, but will hopefully be redesigned +to include a `patch.php` file at some point in the future.// + +If you want to use numeric serial numbers, make sure the first eight digits +change for every upgrade. For instance, use 00000001-00000002. Technically, +there is no current requirement for the hash file to be an actual md5 or even +have 32 hex chars in it. + +Patch files should contain a header with some common information about the +patch. The header should be formatted similar to + + /* osTicket database migration patch + * + * @version 0.0.0 + * @signature 0123456789abcdef0000000000000000 + * + * Details about the migration patch are listed here + */ + +Eventually the `@signature` line will be automatically inspected and forced +into the config table for the `schema_signature` setting in the namespace of +your stream at the completion of the patch process. Please add it to your +patches to keep them future-minded. The `@version` is a string that will be +shown to clients during the upgrade process indicating the version numbers +as each patch is applied. + +Customizing osTicket +==================== +osTicket now supports database customizations using a separation technique +called *streams*. Separating the database upgrade path into streams allows +the upstream osTicket database upgrades to be kept separate from your own, +custom upgrade streams. Streams are registered in `streams.cfg` located in +the `UPGRADE_DIR/streams`. + +Example streams.cfg +------------------- + # Write the names of the stream folders to be enabled in this file. + # The order is significant. The upgrade process will run updates for the + # respective streams in the order they are listed in this file. + + core # The upstream osTicket upgrade stream + + # Add custom upgrade streams here, which will be applied in the order + # listed in this file + +Database Customization Rules +--------------------------- +1. Leave the upstream osTicket tables unchanged. If your customization makes + changes to the main osTicket tables, you will likely get merge conflicts + when the *core* stream is updated. If you need to add columns to an + upstream table, add another table with the extra columns and link the + data to the upstream table using the primary key of the upstream table. + This will keep your data model separate from the upstream data model. diff --git a/setup/inc/class.installer.php b/setup/inc/class.installer.php index 21ac30f9b087475c2764b45b0c594deb094a664b..8a2b04a0d810c1ff7182f507cf47fd9044e9fb4a 100644 --- a/setup/inc/class.installer.php +++ b/setup/inc/class.installer.php @@ -13,6 +13,7 @@ vim: expandtab sw=4 ts=4 sts=4: **********************************************************************/ +require_once INCLUDE_DIR.'class.migrater.php'; require_once INCLUDE_DIR.'class.setup.php'; class Installer extends SetupWizard { @@ -44,7 +45,7 @@ class Installer extends SetupWizard { function install($vars) { $this->errors=$f=array(); - + $f['name'] = array('type'=>'string', 'required'=>1, 'error'=>'Name required'); $f['email'] = array('type'=>'email', 'required'=>1, 'error'=>'Valid email required'); $f['fname'] = array('type'=>'string', 'required'=>1, 'error'=>'First name required'); @@ -58,7 +59,7 @@ class Installer extends SetupWizard { $f['dbname'] = array('type'=>'string', 'required'=>1, 'error'=>'Database name required'); $f['dbuser'] = array('type'=>'string', 'required'=>1, 'error'=>'Username required'); $f['dbpass'] = array('type'=>'string', 'required'=>1, 'error'=>'password required'); - + if(!Validator::process($f,$vars,$this->errors) && !$this->errors['err']) $this->errors['err']='Missing or invalid data - correct the errors and try again.'; @@ -67,22 +68,22 @@ class Installer extends SetupWizard { //Staff's email can't be same as system emails. if($vars['admin_email'] && $vars['email'] && !strcasecmp($vars['admin_email'],$vars['email'])) $this->errors['admin_email']='Conflicts with system email above'; - //Admin's pass confirmation. + //Admin's pass confirmation. if(!$this->errors && strcasecmp($vars['passwd'],$vars['passwd2'])) $this->errors['passwd2']='passwords to not match!'; //Check table prefix underscore required at the end! if($vars['prefix'] && substr($vars['prefix'], -1)!='_') $this->errors['prefix']='Bad prefix. Must have underscore (_) at the end. e.g \'ost_\''; - //Make sure admin username is not very predictable. XXX: feels dirty but necessary + //Make sure admin username is not very predictable. XXX: feels dirty but necessary if(!$this->errors['username'] && in_array(strtolower($vars['username']),array('admin','admins','username','osticket'))) $this->errors['username']='Bad username'; //MYSQL: Connect to the DB and check the version & database (create database if it doesn't exist!) if(!$this->errors) { if(!db_connect($vars['dbhost'],$vars['dbuser'],$vars['dbpass'])) - $this->errors['db']='Unable to connect to MySQL server. Possibly invalid login info.'; - elseif(db_version()< $this->getMySQLVersion()) + $this->errors['db']='Unable to connect to MySQL server. '.db_connect_error(); + elseif(explode('.', db_version()) < explode('.', $this->getMySQLVersion())) $this->errors['db']=sprintf('osTicket requires MySQL %s or better!',$this->getMySQLVersion()); elseif(!db_select_database($vars['dbname']) && !db_create_database($vars['dbname'])) { $this->errors['dbname']='Database doesn\'t exist'; @@ -92,12 +93,12 @@ class Installer extends SetupWizard { } else { //Abort if we have another installation (or table) with same prefix. $sql = 'SELECT * FROM `'.$vars['prefix'].'config` LIMIT 1'; - if(mysql_query($sql)) { + if(db_query($sql, false)) { $this->errors['err'] = 'We have a problem - another installation with same table prefix exists!'; $this->errors['prefix'] = 'Prefix already in-use'; } else { //Try changing charset and collation of the DB - no bigie if we fail. - mysql_query('ALTER DATABASE '.$vars['dbname'].' DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci'); + db_query('ALTER DATABASE '.$vars['dbname'].' DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci', false); } } } @@ -109,46 +110,94 @@ class Installer extends SetupWizard { define('ADMIN_EMAIL',$vars['admin_email']); //Needed to report SQL errors during install. define('PREFIX',$vars['prefix']); //Table prefix - $schemaFile =INC_DIR.'sql/osTicket-mysql.sql'; //DB dump. - $debug = true; //XXX:Change it to true to show SQL errors. + $debug = true; // Change it to false to squelch SQL errors. //Last minute checks. - if(!file_exists($schemaFile)) - $this->errors['err']='Internal Error - please make sure your download is the latest (#1)'; - elseif(!($signature=trim(file_get_contents("$schemaFile.md5"))) || strcasecmp($signature, md5_file($schemaFile))) - $this->errors['err']='Unknown or invalid schema signature ('.$signature.' .. '.md5_file($schemaFile).')'; - elseif(!file_exists($this->getConfigFile()) || !($configFile=file_get_contents($this->getConfigFile()))) + if(!file_exists($this->getConfigFile()) || !($configFile=file_get_contents($this->getConfigFile()))) $this->errors['err']='Unable to read config file. Permission denied! (#2)'; elseif(!($fp = @fopen($this->getConfigFile(),'r+'))) $this->errors['err']='Unable to open config file for writing. Permission denied! (#3)'; - elseif(!$this->load_sql_file($schemaFile,$vars['prefix'], true, $debug)) - $this->errors['err']='Error parsing SQL schema! Get help from developers (#4)'; - + + else { + $streams = DatabaseMigrater::getUpgradeStreams(INCLUDE_DIR.'upgrader/streams/'); + foreach ($streams as $stream=>$signature) { + $schemaFile = INC_DIR."streams/$stream/install-mysql.sql"; + if (!file_exists($schemaFile) || !($fp2 = fopen($schemaFile, 'rb'))) + $this->errors['err'] = $stream + . ': Internal Error - please make sure your download is the latest (#1)'; + elseif ( + // TODO: Make the hash algo configurable in the streams + // configuration ( core : md5 ) + !($hash = md5(fread($fp2, filesize($schemaFile)))) + || strcasecmp($signature, $hash)) + $this->errors['err'] = $stream + .': Unknown or invalid schema signature (' + .$signature.' .. '.$hash.')'; + elseif (!$this->load_sql_file($schemaFile, $vars['prefix'], true, $debug)) + $this->errors['err'] = $stream + .': Error parsing SQL schema! Get help from developers (#4)'; + } + } + + $sql='SELECT `id` FROM '.PREFIX.'sla ORDER BY `id` LIMIT 1'; + $sla_id_1 = db_result(db_query($sql, false), 0); + + $sql='SELECT `dept_id` FROM '.PREFIX.'department ORDER BY `dept_id` LIMIT 1'; + $dept_id_1 = db_result(db_query($sql, false), 0); + + $sql='SELECT `tpl_id` FROM '.PREFIX.'email_template_group ORDER BY `tpl_id` LIMIT 1'; + $template_id_1 = db_result(db_query($sql, false), 0); + + $sql='SELECT `group_id` FROM '.PREFIX.'groups ORDER BY `group_id` LIMIT 1'; + $group_id_1 = db_result(db_query($sql, false), 0); + + $sql='SELECT `id` FROM '.PREFIX.'timezone WHERE offset=-5.0 LIMIT 1'; + $eastern_timezone = db_result(db_query($sql, false), 0); + if(!$this->errors) { //Create admin user. $sql='INSERT INTO '.PREFIX.'staff SET created=NOW() ' - .', isactive=1, isadmin=1, group_id=1, dept_id=1, timezone_id=8, max_page_size=25 ' - .', email='.db_input($_POST['admin_email']) + .", isactive=1, isadmin=1, group_id=$group_id_1, dept_id=$dept_id_1" + .", timezone_id=$eastern_timezone, max_page_size=25" + .', email='.db_input($vars['admin_email']) .', firstname='.db_input($vars['fname']) .', lastname='.db_input($vars['lname']) .', username='.db_input($vars['username']) .', passwd='.db_input(Passwd::hash($vars['passwd'])); - if(!mysql_query($sql) || !($uid=mysql_insert_id())) + if(!db_query($sql, false) || !($uid=db_insert_id())) $this->errors['err']='Unable to create admin user (#6)'; } if(!$this->errors) { + //Create default emails! + $email = $vars['email']; + list(,$domain)=explode('@',$vars['email']); + $sql='INSERT INTO '.PREFIX.'email (`name`,`email`,`created`,`updated`) VALUES ' + ." ('Support','$email',NOW(),NOW())" + .",('osTicket Alerts','alerts@$domain',NOW(),NOW())" + .",('','noreply@$domain',NOW(),NOW())"; + $support_email_id = db_query($sql, false) ? db_insert_id() : 0; + + + $sql='SELECT `email_id` FROM '.PREFIX."email WHERE `email`='alerts@$domain' LIMIT 1"; + $alert_email_id = db_result(db_query($sql, false), 0); + //Create config settings---default settings! //XXX: rename ostversion helpdesk_* ?? - $sql='INSERT INTO '.PREFIX.'config SET updated=NOW(), isonline=0 ' - .', default_email_id=1, alert_email_id=2, default_dept_id=1 ' - .', default_sla_id=1, default_timezone_id=8, default_template_id=1 ' - .', admin_email='.db_input($vars['admin_email']) - .', schema_signature='.db_input($signature) - .', helpdesk_url='.db_input(URL) - .', helpdesk_title='.db_input($vars['name']); - if(!mysql_query($sql) || !($cid=mysql_insert_id())) - $this->errors['err']='Unable to create config settings (#7)'; + // XXX: Some of this can go to the core install file + $defaults = array('isonline'=>'0', 'default_email_id'=>$support_email_id, + 'alert_email_id'=>$alert_email_id, 'default_dept_id'=>$dept_id_1, 'default_sla_id'=>$sla_id_1, + 'default_timezone_id'=>$eastern_timezone, 'default_template_id'=>$template_id_1, + 'admin_email'=>db_input($vars['admin_email']), + 'schema_signature'=>db_input($signature), + 'helpdesk_url'=>db_input(URL), + 'helpdesk_title'=>db_input($vars['name'])); + foreach ($defaults as $key=>$value) { + $sql='UPDATE '.PREFIX.'config SET updated=NOW(), value='.$value + .' WHERE namespace="core" AND `key`='.db_input($key); + if(!db_query($sql, false)) + $this->errors['err']='Unable to create config settings (#7)'; + } } if($this->errors) return false; //Abort on internal errors. @@ -162,7 +211,7 @@ class Installer extends SetupWizard { $configFile= str_replace('%CONFIG-DBUSER',$vars['dbuser'],$configFile); $configFile= str_replace('%CONFIG-DBPASS',$vars['dbpass'],$configFile); $configFile= str_replace('%CONFIG-PREFIX',$vars['prefix'],$configFile); - $configFile= str_replace('%CONFIG-SIRI',Misc::randcode(32),$configFile); + $configFile= str_replace('%CONFIG-SIRI',Misc::randCode(32),$configFile); if(!$fp || !ftruncate($fp,0) || !fwrite($fp,$configFile)) { $this->errors['err']='Unable to write to config file. Permission denied! (#5)'; return false; @@ -170,43 +219,41 @@ class Installer extends SetupWizard { @fclose($fp); /************* Make the system happy ***********************/ - //Create default emails! - $email = $vars['email']; - list(,$domain)=explode('@',$vars['email']); - $sql='INSERT INTO '.PREFIX.'email (`email_id`, `dept_id`, `name`,`email`,`created`,`updated`) VALUES ' - ." (1,1,'Support','$email',NOW(),NOW())" - .",(2,1,'osTicket Alerts','alerts@$domain',NOW(),NOW())" - .",(3,1,'','noreply@$domain',NOW(),NOW())"; - @mysql_query($sql); - + + $sql='UPDATE '.PREFIX."email SET dept_id=$dept_id_1"; + db_query($sql, false); + $sql='UPDATE '.PREFIX."department SET email_id=$support_email_id" + .", autoresp_email_id=$support_email_id"; + db_query($sql, false); + //Create a ticket to make the system warm and happy. $sql='INSERT INTO '.PREFIX.'ticket SET created=NOW(), status="open", source="Web" ' - .' ,priority_id=2, dept_id=1, topic_id=1 ' + ." ,priority_id=0, dept_id=$dept_id_1, topic_id=0 " .' ,ticketID='.db_input(Misc::randNumber(6)) .' ,email="support@osticket.com" ' .' ,name="osTicket Support" ' .' ,subject="osTicket Installed!"'; - if(mysql_query($sql) && ($tid=mysql_insert_id())) { + if(db_query($sql, false) && ($tid=db_insert_id())) { if(!($msg=file_get_contents(INC_DIR.'msg/installed.txt'))) $msg='Congratulations and Thank you for choosing osTicket!'; - + $sql='INSERT INTO '.PREFIX.'ticket_thread SET created=NOW()' .', source="Web" ' .', thread_type="M" ' .', ticket_id='.db_input($tid) .', title='.db_input('osTicket Installed') .', body='.db_input($msg); - @mysql_query($sql); + db_query($sql, false); } //TODO: create another personalized ticket and assign to admin?? - + //Log a message. $msg="Congratulations osTicket basic installation completed!\n\nThank you for choosing osTicket!"; $sql='INSERT INTO '.PREFIX.'syslog SET created=NOW(), updated=NOW(), log_type="Debug" ' .', title="osTicket installed!"' .', log='.db_input($msg) .', ip_address='.db_input($_SERVER['REMOTE_ADDR']); - @mysql_query($sql); + db_query($sql, false); return true; } diff --git a/setup/inc/header.inc.php b/setup/inc/header.inc.php index 4c7d16e0f217da5cce7e5db5ee779964c2b9dc2e..28aa849a1455ee160fac96f4b909e2c3fdf9704d 100644 --- a/setup/inc/header.inc.php +++ b/setup/inc/header.inc.php @@ -5,7 +5,7 @@ <title><?php echo $wizard['title']; ?></title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <link rel="stylesheet" href="css/wizard.css"> - <script type="text/javascript" src="js/jquery-1.6.2.min.js"></script> + <script type="text/javascript" src="../js/jquery-1.7.2.min.js"></script> <script type="text/javascript" src="js/tips.js"></script> <script type="text/javascript" src="js/setup.js"></script> </head> diff --git a/setup/inc/install-prereq.inc.php b/setup/inc/install-prereq.inc.php index 11746eda72bb2afe44cee62eb7f3ae99e4145a15..46f117e53d175d232df01588e2df9b902fd6052c 100644 --- a/setup/inc/install-prereq.inc.php +++ b/setup/inc/install-prereq.inc.php @@ -22,7 +22,6 @@ if(!defined('SETUPINC')) die('Kwaheri!'); <h3>Recommended:</h3> You can use osTicket without these, but you may not be able to use all features. <ul class="progress"> - <li class="<?php echo extension_loaded('mcrypt')?'yes':'no'; ?>">Mcrypt extension</li> <li class="<?php echo extension_loaded('gd')?'yes':'no'; ?>">Gdlib extension</li> <li class="<?php echo extension_loaded('imap')?'yes':'no'; ?>">PHP IMAP extension</li> </ul> diff --git a/setup/inc/msg/installed.txt b/setup/inc/msg/installed.txt index 58bbeb5a0c6238df52c83fa91eeb701cdd00dbd5..5b598afdaec399b23c751177967dfc689814b201 100644 --- a/setup/inc/msg/installed.txt +++ b/setup/inc/msg/installed.txt @@ -1,9 +1,11 @@ Thank you for choosing osTicket. -Please make sure you join the osTicket forums at http://osticket.com/forums to stay up to date on the latest news, security alerts and updates. The osTicket forums are also a great place to get assistance, guidance, tips, and help from other osTicket users. In addition to the forums, the osTicket wiki provides a useful collection of educational materials, documentation, and notes from the community. We welcome your contributions to the osTicket community. +Please make sure you join the osTicket forums (http://osticket.com/forums) and our mailing list (http://osticket.com/updates) to stay up to date on the latest news, security alerts and updates. The osTicket forums are also a great place to get assistance, guidance, tips, and help from other osTicket users. In addition to the forums, the osTicket wiki provides a useful collection of educational materials, documentation, and notes from the community. We welcome your contributions to the osTicket community. If you are looking for a greater level of support, we provide professional services and commercial support with guaranteed response times, and access to the core development team. We can also help customize osTicket or even add new features to the system to meet your unique needs. +If the idea of managing and upgrading this osTicket installation is daunting, you can try osTicket as a hosted service at http://www.supportsystem.com/ -- no installation required and we can import your data! With SupportSystem's turnkey infrastructure, you get osTicket at its best, leaving you free to focus on your customers without the burden of making sure the application is stable, maintained, and secure. + Cheers, - diff --git a/setup/inc/msg/upgraded.txt b/setup/inc/msg/upgraded.txt deleted file mode 100644 index d88a29733659f1e9696a47338b89f7b7ccec1b97..0000000000000000000000000000000000000000 --- a/setup/inc/msg/upgraded.txt +++ /dev/null @@ -1,10 +0,0 @@ - -osTicket upgraded successfully! Please refer to the Release Notes (http://osticket.com/wiki/Release_Notes) for more information about changes and new features. - -Be sure to join osTicket forum (http://osticket.com/forums) and our mailing list (http://osticket.com/support/subscribe.php) , if you haven't done so already, to stay up to date on announcements, security updates and alerts! Your contribution to osTicket community will be appreciated! - -The osTicket team is committed to providing support to all users through our free online resources and a full range of commercial support packages and services. For more information, or to discuss your needs, please contact us today at http://osticket.com/support/. Any feedback will be appreciated! - -- -osTicket Team -http://osticket.com/ diff --git a/setup/inc/sql/osTicket-mysql.sql.md5 b/setup/inc/sql/osTicket-mysql.sql.md5 deleted file mode 100644 index d14b842c7046800622e9423f6cb8f2bc24a0036b..0000000000000000000000000000000000000000 --- a/setup/inc/sql/osTicket-mysql.sql.md5 +++ /dev/null @@ -1 +0,0 @@ -d959a00e55c75e0c903b9e37324fd25d diff --git a/setup/inc/sql/osTicket-mysql.sql b/setup/inc/streams/core/install-mysql.sql similarity index 50% rename from setup/inc/sql/osTicket-mysql.sql rename to setup/inc/streams/core/install-mysql.sql index 3d14f86d5231b4c05986e5677d62adda544e9d56..9359cfae9eb562c3209e755df3c1a4d1456f1948 100644 --- a/setup/inc/sql/osTicket-mysql.sql +++ b/setup/inc/streams/core/install-mysql.sql @@ -13,7 +13,7 @@ CREATE TABLE `%TABLE_PREFIX%api_key` ( PRIMARY KEY (`id`), KEY `ipaddr` (`ipaddr`), UNIQUE KEY `apikey` (`apikey`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `%TABLE_PREFIX%faq`; CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%faq` ( @@ -29,16 +29,15 @@ CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%faq` ( PRIMARY KEY (`faq_id`), UNIQUE KEY `question` (`question`), KEY `category_id` (`category_id`), - KEY `ispublished` (`ispublished`), - FULLTEXT KEY `faq` (`question`,`answer`,`keywords`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; + KEY `ispublished` (`ispublished`) +) DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `%TABLE_PREFIX%faq_attachment`; CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%faq_attachment` ( `faq_id` int(10) unsigned NOT NULL, `file_id` int(10) unsigned NOT NULL, PRIMARY KEY (`faq_id`,`file_id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `%TABLE_PREFIX%faq_category`; CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%faq_category` ( @@ -51,119 +50,140 @@ CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%faq_category` ( `updated` date NOT NULL, PRIMARY KEY (`category_id`), KEY (`ispublic`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `%TABLE_PREFIX%faq_topic`; CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%faq_topic` ( `faq_id` int(10) unsigned NOT NULL, `topic_id` int(10) unsigned NOT NULL, PRIMARY KEY (`faq_id`,`topic_id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; + +DROP TABLE IF EXISTS `%TABLE_PREFIX%sla`; +CREATE TABLE `%TABLE_PREFIX%sla` ( + `id` int(11) unsigned NOT NULL auto_increment, + `isactive` tinyint(1) unsigned NOT NULL default '1', + `enable_priority_escalation` tinyint(1) unsigned NOT NULL default '1', + `disable_overdue_alerts` tinyint(1) unsigned NOT NULL default '0', + `grace_period` int(10) unsigned NOT NULL default '0', + `name` varchar(64) NOT NULL default '', + `notes` text, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`) +) DEFAULT CHARSET=utf8; + +INSERT INTO `%TABLE_PREFIX%sla` (`isactive`, `enable_priority_escalation`, + `disable_overdue_alerts`, `grace_period`, `name`, `notes`, `created`, `updated`) + VALUES (1, 1, 0, 48, 'Default SLA', NULL, NOW(), NOW()); DROP TABLE IF EXISTS `%TABLE_PREFIX%config`; CREATE TABLE `%TABLE_PREFIX%config` ( - `id` tinyint(1) unsigned NOT NULL auto_increment, - `isonline` tinyint(1) unsigned NOT NULL default '0', - `timezone_offset` float(3,1) NOT NULL default '0.0', - `enable_daylight_saving` tinyint(1) unsigned NOT NULL default '0', - `staff_ip_binding` tinyint(1) unsigned NOT NULL default '1', - `staff_max_logins` tinyint(3) unsigned NOT NULL default '4', - `staff_login_timeout` int(10) unsigned NOT NULL default '2', - `staff_session_timeout` int(10) unsigned NOT NULL default '30', - `passwd_reset_period` int(10) unsigned NOT NULL default '0', - `client_max_logins` tinyint(3) unsigned NOT NULL default '4', - `client_login_timeout` int(10) unsigned NOT NULL default '2', - `client_session_timeout` int(10) unsigned NOT NULL default '30', - `max_page_size` tinyint(3) unsigned NOT NULL default '25', - `max_open_tickets` tinyint(3) unsigned NOT NULL default '0', - `max_file_size` int(11) unsigned NOT NULL default '1048576', - `max_user_file_uploads` tinyint(3) unsigned NOT NULL, - `max_staff_file_uploads` tinyint(3) unsigned NOT NULL, - `autolock_minutes` tinyint(3) unsigned NOT NULL default '3', - `overdue_grace_period` int(10) unsigned NOT NULL default '0', - `alert_email_id` tinyint(4) unsigned NOT NULL default '0', - `default_email_id` tinyint(4) unsigned NOT NULL default '0', - `default_dept_id` tinyint(3) unsigned NOT NULL default '0', - `default_sla_id` int(10) unsigned NOT NULL default '0', - `default_priority_id` tinyint(2) unsigned NOT NULL default '2', - `default_template_id` tinyint(4) unsigned NOT NULL default '1', - `default_timezone_id` int(10) unsigned NOT NULL default '0', - `default_smtp_id` tinyint(4) unsigned NOT NULL default '0', - `allow_email_spoofing` tinyint(1) unsigned NOT NULL default '0', - `clickable_urls` tinyint(1) unsigned NOT NULL default '1', - `allow_priority_change` tinyint(1) unsigned NOT NULL default '0', - `use_email_priority` tinyint(1) unsigned NOT NULL default '0', - `enable_kb` tinyint(1) unsigned NOT NULL default '0', - `enable_premade` tinyint(1) unsigned NOT NULL default '1', - `enable_captcha` tinyint(1) unsigned NOT NULL default '0', - `enable_auto_cron` tinyint(1) unsigned NOT NULL default '0', - `enable_mail_polling` tinyint(1) unsigned NOT NULL default '0', - `send_sys_errors` tinyint(1) unsigned NOT NULL default '1', - `send_sql_errors` tinyint(1) unsigned NOT NULL default '1', - `send_mailparse_errors` tinyint(1) unsigned NOT NULL default '1', - `send_login_errors` tinyint(1) unsigned NOT NULL default '1', - `save_email_headers` tinyint(1) unsigned NOT NULL default '1', - `strip_quoted_reply` tinyint(1) unsigned NOT NULL default '1', - `log_ticket_activity` tinyint(1) unsigned NOT NULL default '1', - `ticket_autoresponder` tinyint(1) unsigned NOT NULL default '0', - `message_autoresponder` tinyint(1) unsigned NOT NULL default '0', - `ticket_notice_active` tinyint(1) unsigned NOT NULL default '0', - `ticket_alert_active` tinyint(1) unsigned NOT NULL default '0', - `ticket_alert_admin` tinyint(1) unsigned NOT NULL default '1', - `ticket_alert_dept_manager` tinyint(1) unsigned NOT NULL default '1', - `ticket_alert_dept_members` tinyint(1) unsigned NOT NULL default '0', - `message_alert_active` tinyint(1) unsigned NOT NULL default '0', - `message_alert_laststaff` tinyint(1) unsigned NOT NULL default '1', - `message_alert_assigned` tinyint(1) unsigned NOT NULL default '1', - `message_alert_dept_manager` tinyint(1) unsigned NOT NULL default '0', - `note_alert_active` tinyint(1) unsigned NOT NULL default '0', - `note_alert_laststaff` tinyint(1) unsigned NOT NULL default '1', - `note_alert_assigned` tinyint(1) unsigned NOT NULL default '1', - `note_alert_dept_manager` tinyint(1) unsigned NOT NULL default '0', - `transfer_alert_active` tinyint(1) unsigned NOT NULL default '0', - `transfer_alert_assigned` tinyint(1) unsigned NOT NULL default '0', - `transfer_alert_dept_manager` tinyint(1) unsigned NOT NULL default '1', - `transfer_alert_dept_members` tinyint(1) unsigned NOT NULL default '0', - `overdue_alert_active` tinyint(1) unsigned NOT NULL default '0', - `overdue_alert_assigned` tinyint(1) unsigned NOT NULL default '1', - `overdue_alert_dept_manager` tinyint(1) unsigned NOT NULL default '1', - `overdue_alert_dept_members` tinyint(1) unsigned NOT NULL default '0', - `assigned_alert_active` tinyint(1) unsigned NOT NULL default '1', - `assigned_alert_staff` tinyint(1) unsigned NOT NULL default '1', - `assigned_alert_team_lead` tinyint(1) unsigned NOT NULL default '0', - `assigned_alert_team_members` tinyint(1) unsigned NOT NULL default '0', - `auto_assign_reopened_tickets` tinyint(1) unsigned NOT NULL default '1', - `show_related_tickets` tinyint(1) unsigned NOT NULL default '1', - `show_assigned_tickets` tinyint(1) unsigned NOT NULL default '1', - `show_answered_tickets` tinyint(1) unsigned NOT NULL default '0', - `show_notes_inline` tinyint(1) unsigned NOT NULL default '1', - `hide_staff_name` tinyint(1) unsigned NOT NULL default '0', - `overlimit_notice_active` tinyint(1) unsigned NOT NULL default '0', - `email_attachments` tinyint(1) unsigned NOT NULL default '1', - `allow_attachments` tinyint(1) unsigned NOT NULL default '0', - `allow_email_attachments` tinyint(1) unsigned NOT NULL default '0', - `allow_online_attachments` tinyint(1) unsigned NOT NULL default '0', - `allow_online_attachments_onlogin` tinyint(1) unsigned NOT NULL default - '0', - `random_ticket_ids` tinyint(1) unsigned NOT NULL default '1', - `log_level` tinyint(1) unsigned NOT NULL default '2', - `log_graceperiod` int(10) unsigned NOT NULL default '12', - `upload_dir` varchar(255) NOT NULL default '', - `allowed_filetypes` varchar(255) NOT NULL default '.doc, .pdf', - `time_format` varchar(32) NOT NULL default ' h:i A', - `date_format` varchar(32) NOT NULL default 'm/d/Y', - `datetime_format` varchar(60) NOT NULL default 'm/d/Y g:i a', - `daydatetime_format` varchar(60) NOT NULL default 'D, M j Y g:ia', - `reply_separator` varchar(60) NOT NULL default '-- do not edit --', - `admin_email` varchar(125) NOT NULL default '', - `helpdesk_title` varchar(255) NOT NULL default - 'osTicket Support Ticket System', - `helpdesk_url` varchar(255) NOT NULL default '', - `schema_signature` char(32) NOT NULL default '', - `updated` timestamp NOT NULL default CURRENT_TIMESTAMP, + `id` int(11) unsigned NOT NULL auto_increment, + `namespace` varchar(64) NOT NULL, + `key` varchar(64) NOT NULL, + `value` text NOT NULL, + `updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), - KEY `isoffline` (`isonline`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; + UNIQUE KEY (`namespace`, `key`) +) DEFAULT CHARSET=utf8; + +INSERT INTO `%TABLE_PREFIX%config` (`namespace`, `key`, `value`) VALUES + ('core', 'isonline', '0'), + ('core', 'enable_daylight_saving', '0'), + ('core', 'staff_ip_binding', '0'), + ('core', 'staff_max_logins', '4'), + ('core', 'staff_login_timeout', '2'), + ('core', 'staff_session_timeout', '30'), + ('core', 'passwd_reset_period', '0'), + ('core', 'client_max_logins', '4'), + ('core', 'client_login_timeout', '2'), + ('core', 'client_session_timeout', '30'), + ('core', 'max_page_size', '25'), + ('core', 'max_open_tickets', '0'), + ('core', 'max_file_size', '1048576'), + ('core', 'max_user_file_uploads', ''), + ('core', 'max_staff_file_uploads', ''), + ('core', 'autolock_minutes', '3'), + ('core', 'overdue_grace_period', '0'), + ('core', 'alert_email_id', '0'), + ('core', 'default_email_id', '0'), + ('core', 'default_dept_id', '0'), + ('core', 'default_sla_id', '0'), + ('core', 'default_priority_id', '2'), + ('core', 'default_template_id', '1'), + ('core', 'default_timezone_id', '0'), + ('core', 'default_smtp_id', '0'), + ('core', 'allow_email_spoofing', '0'), + ('core', 'clickable_urls', '1'), + ('core', 'allow_priority_change', '0'), + ('core', 'use_email_priority', '0'), + ('core', 'enable_kb', '0'), + ('core', 'enable_premade', '1'), + ('core', 'enable_captcha', '0'), + ('core', 'enable_auto_cron', '0'), + ('core', 'enable_mail_polling', '0'), + ('core', 'send_sys_errors', '1'), + ('core', 'send_sql_errors', '1'), + ('core', 'send_mailparse_errors', '1'), + ('core', 'send_login_errors', '1'), + ('core', 'save_email_headers', '1'), + ('core', 'strip_quoted_reply', '1'), + ('core', 'log_ticket_activity', '1'), + ('core', 'ticket_autoresponder', '0'), + ('core', 'message_autoresponder', '0'), + ('core', 'ticket_notice_active', '0'), + ('core', 'ticket_alert_active', '0'), + ('core', 'ticket_alert_admin', '1'), + ('core', 'ticket_alert_dept_manager', '1'), + ('core', 'ticket_alert_dept_members', '0'), + ('core', 'message_alert_active', '0'), + ('core', 'message_alert_laststaff', '1'), + ('core', 'message_alert_assigned', '1'), + ('core', 'message_alert_dept_manager', '0'), + ('core', 'note_alert_active', '0'), + ('core', 'note_alert_laststaff', '1'), + ('core', 'note_alert_assigned', '1'), + ('core', 'note_alert_dept_manager', '0'), + ('core', 'transfer_alert_active', '0'), + ('core', 'transfer_alert_assigned', '0'), + ('core', 'transfer_alert_dept_manager', '1'), + ('core', 'transfer_alert_dept_members', '0'), + ('core', 'overdue_alert_active', '0'), + ('core', 'overdue_alert_assigned', '1'), + ('core', 'overdue_alert_dept_manager', '1'), + ('core', 'overdue_alert_dept_members', '0'), + ('core', 'assigned_alert_active', '1'), + ('core', 'assigned_alert_staff', '1'), + ('core', 'assigned_alert_team_lead', '0'), + ('core', 'assigned_alert_team_members', '0'), + ('core', 'auto_assign_reopened_tickets', '1'), + ('core', 'show_related_tickets', '1'), + ('core', 'show_assigned_tickets', '1'), + ('core', 'show_answered_tickets', '0'), + ('core', 'show_notes_inline', '1'), + ('core', 'hide_staff_name', '0'), + ('core', 'overlimit_notice_active', '0'), + ('core', 'email_attachments', '1'), + ('core', 'allow_attachments', '0'), + ('core', 'allow_email_attachments', '0'), + ('core', 'allow_online_attachments', '0'), + ('core', 'allow_online_attachments_onlogin', '0'), + ('core', 'random_ticket_ids', '1'), + ('core', 'log_level', '2'), + ('core', 'log_graceperiod', '12'), + ('core', 'upload_dir', ''), + ('core', 'allowed_filetypes', '.doc, .pdf'), + ('core', 'time_format', ' h:i A'), + ('core', 'date_format', 'm/d/Y'), + ('core', 'datetime_format', 'm/d/Y g:i a'), + ('core', 'daydatetime_format', 'D, M j Y g:ia'), + ('core', 'reply_separator', '-- do not edit --'), + ('core', 'admin_email', ''), + ('core', 'helpdesk_title', 'osTicket Support Ticket System'), + ('core', 'helpdesk_url', ''), + ('core', 'schema_signature', ''); DROP TABLE IF EXISTS `%TABLE_PREFIX%department`; CREATE TABLE `%TABLE_PREFIX%department` ( @@ -186,11 +206,11 @@ CREATE TABLE `%TABLE_PREFIX%department` ( KEY `manager_id` (`manager_id`), KEY `autoresp_email_id` (`autoresp_email_id`), KEY `tpl_id` (`tpl_id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; -INSERT INTO `%TABLE_PREFIX%department` (`dept_id`, `tpl_id`, `sla_id`, `email_id`, `autoresp_email_id`, `manager_id`, `dept_name`, `dept_signature`, `ispublic`, `ticket_auto_response`, `message_auto_response`) VALUES - (1, 0, 0, 1, 1, 0, 'Support', 'Support Dept', 1, 1, 1), - (2, 0, 1, 1, 1, 0, 'Billing', 'Billing Dept', 1, 1, 1); +INSERT INTO `%TABLE_PREFIX%department` (`sla_id`, `dept_name`, `dept_signature`, `ispublic`, `ticket_auto_response`, `message_auto_response`) VALUES + (0, 'Support', 'Support Dept', 1, 1, 1), + ((SELECT `id` FROM `%TABLE_PREFIX%sla` ORDER BY `id` LIMIT 1), 'Billing', 'Billing Dept', 1, 1, 1); DROP TABLE IF EXISTS `%TABLE_PREFIX%email`; CREATE TABLE `%TABLE_PREFIX%email` ( @@ -201,7 +221,7 @@ CREATE TABLE `%TABLE_PREFIX%email` ( `email` varchar(255) NOT NULL default '', `name` varchar(255) NOT NULL default '', `userid` varchar(255) NOT NULL, - `userpass` varchar(125) NOT NULL, + `userpass` varchar(255) collate ascii_general_ci NOT NULL, `mail_active` tinyint(1) NOT NULL default '0', `mail_host` varchar(255) NOT NULL, `mail_protocol` enum('POP','IMAP') NOT NULL default 'POP', @@ -227,7 +247,7 @@ CREATE TABLE `%TABLE_PREFIX%email` ( UNIQUE KEY `email` (`email`), KEY `priority_id` (`priority_id`), KEY `dept_id` (`dept_id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `%TABLE_PREFIX%filter`; CREATE TABLE `%TABLE_PREFIX%filter` ( @@ -254,12 +274,12 @@ CREATE TABLE `%TABLE_PREFIX%filter` ( PRIMARY KEY (`id`), KEY `target` (`target`), KEY `email_id` (`email_id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; INSERT INTO `%TABLE_PREFIX%filter` ( - `id`,`isactive`,`execorder`,`reject_ticket`,`name`,`notes`,`created`) - VALUES (1, 1, 99, 1, 'SYSTEM BAN LIST', 'Internal list for email banning. Do not remove', NOW()); +`isactive`,`execorder`,`reject_ticket`,`name`,`notes`,`created`) +VALUES (1, 99, 1, 'SYSTEM BAN LIST', 'Internal list for email banning. Do not remove', NOW()); DROP TABLE IF EXISTS `%TABLE_PREFIX%filter_rule`; CREATE TABLE `%TABLE_PREFIX%filter_rule` ( @@ -274,80 +294,99 @@ CREATE TABLE `%TABLE_PREFIX%filter_rule` ( `updated` datetime NOT NULL, PRIMARY KEY (`id`), KEY `filter_id` (`filter_id`), - UNIQUE `filter` (`filter_id`, `what`, `how`, `val`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; + UNIQUE `filter` (`filter_id`, `what`, `how`, `val`) +) DEFAULT CHARSET=utf8; INSERT INTO `%TABLE_PREFIX%filter_rule` ( - `id`, `filter_id`, `isactive`, `what`,`how`,`val`,`created`) - VALUES (1, 1, 1, 'email', 'equal', 'test@example.com',NOW()); + `filter_id`, `isactive`, `what`,`how`,`val`,`created`) + VALUES (LAST_INSERT_ID(), 1, 'email', 'equal', 'test@example.com',NOW()); -DROP TABLE IF EXISTS `%TABLE_PREFIX%email_template`; -CREATE TABLE `%TABLE_PREFIX%email_template` ( +DROP TABLE IF EXISTS `%TABLE_PREFIX%email_template_group`; +CREATE TABLE `%TABLE_PREFIX%email_template_group` ( `tpl_id` int(11) NOT NULL auto_increment, - `cfg_id` int(10) unsigned NOT NULL default '0', `isactive` tinyint(1) unsigned NOT NULL default '0', `name` varchar(32) NOT NULL default '', `notes` text, - `ticket_autoresp_subj` varchar(255) NOT NULL default '', - `ticket_autoresp_body` text NOT NULL, - `ticket_autoreply_subj` varchar(255) NOT NULL default '', - `ticket_autoreply_body` text NOT NULL, - `ticket_notice_subj` varchar(255) NOT NULL, - `ticket_notice_body` text NOT NULL, - `ticket_alert_subj` varchar(255) NOT NULL default '', - `ticket_alert_body` text NOT NULL, - `message_autoresp_subj` varchar(255) NOT NULL default '', - `message_autoresp_body` text NOT NULL, - `message_alert_subj` varchar(255) NOT NULL default '', - `message_alert_body` text NOT NULL, - `note_alert_subj` varchar(255) NOT NULL, - `note_alert_body` text NOT NULL, - `assigned_alert_subj` varchar(255) NOT NULL default '', - `assigned_alert_body` text NOT NULL, - `transfer_alert_subj` varchar(255) NOT NULL default '', - `transfer_alert_body` text NOT NULL, - `ticket_overdue_subj` varchar(255) NOT NULL default '', - `ticket_overdue_body` text NOT NULL, - `ticket_overlimit_subj` varchar(255) NOT NULL default '', - `ticket_overlimit_body` text NOT NULL, - `ticket_reply_subj` varchar(255) NOT NULL default '', - `ticket_reply_body` text NOT NULL, + `created` datetime NOT NULL, + `updated` timestamp NOT NULL, + PRIMARY KEY (`tpl_id`) +) DEFAULT CHARSET=utf8; + +INSERT INTO `%TABLE_PREFIX%email_template_group` SET + `isactive` = 1, `name` = 'osTicket Default Template', + `notes` = 'Default osTicket templates', `created` = NOW(), `updated` = NOW(); + +DROP TABLE IF EXISTS `%TABLE_PREFIX%email_template`; +CREATE TABLE `%TABLE_PREFIX%email_template` ( + `id` int(11) UNSIGNED NOT NULL auto_increment, + `tpl_id` int(11) UNSIGNED NOT NULL, + `code_name` varchar(32) NOT NULL, + `subject` varchar(255) NOT NULL default '', + `body` text NOT NULL, `created` datetime NOT NULL, `updated` datetime NOT NULL, - PRIMARY KEY (`tpl_id`), - KEY `cfg_id` (`cfg_id`), - FULLTEXT KEY `message_subj` (`ticket_reply_subj`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; + PRIMARY KEY (`id`), + UNIQUE KEY `template_lookup` (`tpl_id`, `code_name`) +) DEFAULT CHARSET=utf8; -- TODO: Dump revised copy before release!!! -INSERT INTO `%TABLE_PREFIX%email_template` (`tpl_id`, `cfg_id`, `isactive`, `name`, `notes`, `ticket_autoresp_subj`, `ticket_autoresp_body`, `ticket_autoreply_subj`, `ticket_autoreply_body`, `ticket_notice_subj`, `ticket_notice_body`, `ticket_alert_subj`, `ticket_alert_body`, `message_autoresp_subj`, `message_autoresp_body`, `message_alert_subj`, `message_alert_body`, `note_alert_subj`, `note_alert_body`, `assigned_alert_subj`, `assigned_alert_body`, `transfer_alert_subj`, `transfer_alert_body`, `ticket_overdue_subj`, `ticket_overdue_body`, `ticket_overlimit_subj`, `ticket_overlimit_body`, `ticket_reply_subj`, `ticket_reply_body`, `created`, `updated`) VALUES -(1, 1, 1, 'osTicket Default Template', 'Default osTicket templates', 'Support Ticket Opened [#%{ticket.number}]', '%{ticket.name},\r\n\r\nA request for support has been created and assigned ticket #%{ticket.number}. A representative will follow-up with you as soon as possible.\r\n\r\nYou can view this ticket''s progress online here: %{ticket.client_link}.\r\n\r\nIf you wish to send additional comments or information regarding this issue, please don''t open a new ticket. Simply login using the link above and update the ticket.\r\n\r\n%{signature}', 'Support Ticket Opened [#%{ticket.number}]', '%{ticket.name},\r\n\r\nA request for support has been created and assigned ticket #%{ticket.number} with the following auto-reply:\r\n\r\n%{response}\r\n\r\n\r\nWe hope this response has sufficiently answered your questions. If not, please do not open another ticket. If need be, representative will follow-up with you as soon as possible.\r\n\r\nYou can view this ticket''s progress online here: %{ticket.client_link}.', '[#%{ticket.number}] %{ticket.subject}', '%{ticket.name},\r\n\r\nOur customer care team has created a ticket, #%{ticket.number} on your behalf, with the following message.\r\n\r\n%{message}\r\n\r\nIf you wish to provide additional comments or information regarding this issue, please don''t open a new ticket. You can update or view this ticket''s progress online here: %{ticket.client_link}.\r\n\r\n%{signature}', 'New Ticket Alert', '%{recipient},\r\n\r\nNew ticket #%{ticket.number} created.\r\n\r\n-----------------------\r\nName: %{ticket.name}\r\nEmail: %{ticket.email}\r\nDept: %{ticket.dept.name}\r\n\r\n%{message}\r\n-----------------------\r\n\r\nTo view/respond to the ticket, please login to the support ticket system.\r\n\r\n%{ticket.staff_link}\r\n\r\n- Your friendly Customer Support System - powered by osTicket.', '[#%{ticket.number}] Message Added', '%{ticket.name},\r\n\r\nYour reply to support request #%{ticket.number} has been noted.\r\n\r\nYou can view this support request progress online here: %{ticket.client_link}.\r\n\r\n%{signature}', 'New Message Alert', '%{recipient},\r\n\r\nNew message appended to ticket #%{ticket.number}\r\n\r\n----------------------\r\nName: %{ticket.name}\r\nEmail: %{ticket.email}\r\nDept: %{ticket.dept.name}\r\n\r\n%{message}\r\n----------------------\r\n\r\nTo view/respond to the ticket, please login to the support ticket system.\r\n\r\n%{ticket.staff_link}\r\n\r\n- Your friendly Customer Support System - powered by osTicket.', 'New Internal Note Alert', '%{recipient},\r\n\r\nInternal note appended to ticket #%{ticket.number}\r\n\r\n----------------------\r\n* %{note.title} *\r\n\r\n%{note.message}\r\n----------------------\r\n\r\nTo view/respond to the ticket, please login to the support ticket system.\r\n\r\n%{ticket.staff_link}\r\n\r\n- Your friendly Customer Support System - powered by osTicket.', 'Ticket #%{ticket.number} Assigned to you', '%{assignee},\r\n\r\nTicket #%{ticket.number} has been assigned to you by %{assigner}\r\n\r\n----------------------\r\n\r\n%{comments}\r\n\r\n----------------------\r\n\r\nTo view complete details, simply login to the support system.\r\n\r\n%{ticket.staff_link}\r\n\r\n- Your friendly Support Ticket System - powered by osTicket.', 'Ticket Transfer #%{ticket.number} - %{ticket.dept.name}', '%{recipient},\r\n\r\nTicket #%{ticket.number} has been transferred to %{ticket.dept.name} department by %{staff.name}\r\n\r\n----------------------\r\n\r\n%{comments}\r\n\r\n----------------------\r\n\r\nTo view/respond to the ticket, please login to the support ticket system.\r\n\r\n%{ticket.staff_link}\r\n\r\n- Your friendly Customer Support System - powered by osTicket.', 'Stale Ticket Alert', '%{recipient},\r\n\r\nA ticket, #%{ticket.number} assigned to you or in your department is seriously overdue.\r\n\r\n%{ticket.staff_link}\r\n\r\nWe should all work hard to guarantee that all tickets are being addressed in a timely manner.\r\n\r\n- Your friendly (although with limited patience) Support Ticket System - powered by osTicket.', 'Open Tickets Limit Reached', '%{ticket.name}\r\n\r\nYou have reached the maximum number of open tickets allowed.\r\n\r\nTo be able to open another ticket, one of your pending tickets must be closed. To update or add comments to an open ticket simply login using the link below.\r\n\r\n%{url}/tickets.php?e=%{ticket.email}\r\n\r\nThank you.\r\n\r\nSupport Ticket System', '[#%{ticket.number}] %{ticket.subject}', '%{ticket.name},\r\n\r\nA customer support staff member has replied to your support request, #%{ticket.number} with the following response:\r\n\r\n%{response}\r\n\r\nWe hope this response has sufficiently answered your questions. If not, please do not send another email. Instead, reply to this email or login to your account for a complete archive of all your support requests and responses.\r\n\r\n%{ticket.client_link}\r\n\r\n%{signature}', NOW(), NOW()); +INSERT INTO `%TABLE_PREFIX%email_template` (`code_name`, `subject`, `body`) + VALUES ( + 'ticket.autoresp', 'Support Ticket Opened [#%{ticket.number}]', '%{ticket.name}, \r\n\r\nA request for support has been created and assigned ticket #%{ticket.number}. A representative will follow-up with you as soon as possible.\r\n\r\nYou can view this ticket''s progress online here: %{ticket.client_link}.\r\n\r\nIf you wish to send additional comments or information regarding this issue, please don''t open a new ticket. Simply login using the link above and update the ticket.\r\n\r\n%{signature}' + ), ( + 'ticket.autoreply', 'Support Ticket Opened [#%{ticket.number}]', '%{ticket.name}, \r\n\r\nA request for support has been created and assigned ticket #%{ticket.number} with the following auto-reply:\r\n\r\n%{response}\r\n\r\n\r\nWe hope this response has sufficiently answered your questions. If not, please do not open another ticket. If need be, representative will follow-up with you as soon as possible.\r\n\r\nYou can view this ticket''s progress online here: %{ticket.client_link}.' + ), ( + 'ticket.notice', '[#%{ticket.number}] %{ticket.subject}', '%{ticket.name}, \r\n\r\nOur customer care team has created a ticket, #%{ticket.number} on your behalf, with the following message.\r\n\r\n%{message}\r\n\r\nIf you wish to provide additional comments or information regarding this issue, please don''t open a new ticket. You can update or view this ticket''s progress online here: %{ticket.client_link}.\r\n\r\n%{signature}' + ), ( + 'ticket.alert', 'New Ticket Alert', '%{recipient}, \r\n\r\nNew ticket #%{ticket.number} created.\r\n\r\n-----------------------\r\nName: %{ticket.name}\r\nEmail: %{ticket.email}\r\nDept: %{ticket.dept.name}\r\n\r\n%{message}\r\n-----------------------\r\n\r\nTo view/respond to the ticket, please login to the support ticket system.\r\n\r\n%{ticket.staff_link}\r\n\r\n- Your friendly Customer Support System - powered by osTicket.' + ), ( + 'message.autoresp', '[#%{ticket.number}] Message Added', '%{ticket.name}, \r\n\r\nYour reply to support request #%{ticket.number} has been noted.\r\n\r\nYou can view this support request progress online here: %{ticket.client_link}.\r\n\r\n%{signature}' + ), ( + 'message.alert', 'New Message Alert', '%{recipient}, \r\n\r\nNew message appended to ticket #%{ticket.number}\r\n\r\n----------------------\r\nName: %{ticket.name}\r\nEmail: %{ticket.email}\r\nDept: %{ticket.dept.name}\r\n\r\n%{message}\r\n----------------------\r\n\r\nTo view/respond to the ticket, please login to the support ticket system.\r\n\r\n%{ticket.staff_link}\r\n\r\n- Your friendly Customer Support System - powered by osTicket.' + ), ( + 'note.alert', 'New Internal Note Alert', '%{recipient}, \r\n\r\nInternal note appended to ticket #%{ticket.number}\r\n\r\n----------------------\r\n* %{note.title} *\r\n\r\n%{note.message}\r\n----------------------\r\n\r\nTo view/respond to the ticket, please login to the support ticket system.\r\n\r\n%{ticket.staff_link}\r\n\r\n- Your friendly Customer Support System - powered by osTicket.' + ), ( + 'assigned.alert', 'Ticket #%{ticket.number} Assigned to you', '%{assignee}, \r\n\r\nTicket #%{ticket.number} has been assigned to you by %{assigner}\r\n\r\n----------------------\r\n\r\n%{comments}\r\n\r\n----------------------\r\n\r\nTo view complete details, simply login to the support system.\r\n\r\n%{ticket.staff_link}\r\n\r\n- Your friendly Support Ticket System - powered by osTicket.' + ), ( + 'transfer.alert', 'Ticket Transfer #%{ticket.number} - %{ticket.dept.name}', '%{recipient}, \r\n\r\nTicket #%{ticket.number} has been transferred to %{ticket.dept.name} department by %{staff.name}\r\n\r\n----------------------\r\n\r\n%{comments}\r\n\r\n----------------------\r\n\r\nTo view/respond to the ticket, please login to the support ticket system.\r\n\r\n%{ticket.staff_link}\r\n\r\n- Your friendly Customer Support System - powered by osTicket.' + ), ( + 'ticket.overdue', 'Stale Ticket Alert', '%{recipient}, \r\n\r\nA ticket, #%{ticket.number} assigned to you or in your department is seriously overdue.\r\n\r\n%{ticket.staff_link}\r\n\r\nWe should all work hard to guarantee that all tickets are being addressed in a timely manner.\r\n\r\n- Your friendly (although with limited patience) Support Ticket System - powered by osTicket.' + ), ( + 'ticket.overlimit', 'Open Tickets Limit Reached', '%{ticket.name}\r\n\r\nYou have reached the maximum number of open tickets allowed.\r\n\r\nTo be able to open another ticket, one of your pending tickets must be closed. To update or add comments to an open ticket simply login using the link below.\r\n\r\n%{url}/tickets.php?e=%{ticket.email}\r\n\r\nThank you.\r\n\r\nSupport Ticket System' + ), ( + 'ticket.reply', '[#%{ticket.number}] %{ticket.subject}', '%{ticket.name}, \r\n\r\nA customer support staff member has replied to your support request, #%{ticket.number} with the following response:\r\n\r\n%{response}\r\n\r\nWe hope this response has sufficiently answered your questions. If not, please do not send another email. Instead, reply to this email or login to your account for a complete archive of all your support requests and responses.\r\n\r\n%{ticket.client_link}\r\n\r\n%{signature}' + ); +UPDATE `%TABLE_PREFIX%email_template` SET `created`=NOW(), `updated`=NOW(), + `tpl_id` = (SELECT `tpl_id` FROM `%TABLE_PREFIX%email_template_group`); DROP TABLE IF EXISTS `%TABLE_PREFIX%file`; CREATE TABLE `%TABLE_PREFIX%file` ( `id` int(11) NOT NULL auto_increment, + `ft` CHAR( 1 ) NOT NULL DEFAULT 'T', `type` varchar(255) NOT NULL default '', `size` varchar(25) NOT NULL default '', `hash` varchar(125) NOT NULL, `name` varchar(255) NOT NULL default '', `created` datetime NOT NULL, PRIMARY KEY (`id`), + KEY `ft` (`ft`), KEY `hash` (`hash`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; -INSERT INTO `%TABLE_PREFIX%file` (`id`, `type`, `size`, `hash`, `name`, `created`) VALUES -(1, 'text/plain', '25', '670c6cc1d1dfc97fad20e5470251b255', 'osTicket.txt', NOW()); + +INSERT INTO `%TABLE_PREFIX%file` (`type`, `size`, `hash`, `name`, `created`) VALUES + ('text/plain', '25', '670c6cc1d1dfc97fad20e5470251b255', 'osTicket.txt', NOW()); DROP TABLE IF EXISTS `%TABLE_PREFIX%file_chunk`; CREATE TABLE `%TABLE_PREFIX%file_chunk` ( - `file_id` int(11) NOT NULL, - `chunk_id` int(11) NOT NULL, - `filedata` longblob NOT NULL, - PRIMARY KEY (`file_id`, `chunk_id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; + `file_id` int(11) NOT NULL, + `chunk_id` int(11) NOT NULL, + `filedata` longblob NOT NULL, + PRIMARY KEY (`file_id`, `chunk_id`) +) DEFAULT CHARSET=utf8; INSERT INTO `%TABLE_PREFIX%file_chunk` (`file_id`, `chunk_id`, `filedata`) -VALUES (1, 0, 0x43616e6e6564206174746163686d656e747320726f636b210a); + VALUES (LAST_INSERT_ID(), 0, 0x43616e6e6564206174746163686d656e747320726f636b210a); DROP TABLE IF EXISTS `%TABLE_PREFIX%groups`; CREATE TABLE `%TABLE_PREFIX%groups` ( @@ -370,12 +409,12 @@ CREATE TABLE `%TABLE_PREFIX%groups` ( `updated` datetime NOT NULL, PRIMARY KEY (`group_id`), KEY `group_active` (`group_enabled`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; -INSERT INTO `%TABLE_PREFIX%groups` (`group_id`, `group_enabled`, `group_name`, `can_create_tickets`, `can_edit_tickets`, `can_delete_tickets`, `can_close_tickets`, `can_assign_tickets`, `can_transfer_tickets`, `can_ban_emails`, `can_manage_premade`, `can_manage_faq`, `notes`, `created`, `updated`) VALUES - (1, 1, 'Admins', 1, 1, 1, 1, 1, 1, 1, 1, 1, 'overlords', NOW(), NOW()), - (2, 1, 'Managers', 1, 1, 1, 1, 1, 1, 1, 1, 1, '', NOW(), NOW()), - (3, 1, 'Staff', 1, 1, 0, 1, 1, 1, 0, 0, 0, '', NOW(), NOW()); +INSERT INTO `%TABLE_PREFIX%groups` (`group_enabled`, `group_name`, `can_create_tickets`, `can_edit_tickets`, `can_delete_tickets`, `can_close_tickets`, `can_assign_tickets`, `can_transfer_tickets`, `can_ban_emails`, `can_manage_premade`, `can_manage_faq`, `notes`, `created`, `updated`) VALUES + (1, 'Admins', 1, 1, 1, 1, 1, 1, 1, 1, 1, 'overlords', NOW(), NOW()), + (1, 'Managers', 1, 1, 1, 1, 1, 1, 1, 1, 1, '', NOW(), NOW()), + (1, 'Staff', 1, 1, 0, 1, 1, 1, 0, 0, 0, '', NOW(), NOW()); DROP TABLE IF EXISTS `%TABLE_PREFIX%group_dept_access`; CREATE TABLE `%TABLE_PREFIX%group_dept_access` ( @@ -383,10 +422,11 @@ CREATE TABLE `%TABLE_PREFIX%group_dept_access` ( `dept_id` int(10) unsigned NOT NULL default '0', UNIQUE KEY `group_dept` (`group_id`,`dept_id`), KEY `dept_id` (`dept_id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; -INSERT INTO `%TABLE_PREFIX%group_dept_access` (`group_id`, `dept_id`) VALUES - (1, 1), (1, 2), (2, 1), (2, 2), (3, 1), (3, 2); +INSERT INTO `%TABLE_PREFIX%group_dept_access` (`group_id`, `dept_id`) + SELECT `%TABLE_PREFIX%groups`.`group_id`, `%TABLE_PREFIX%department`.`dept_id` + FROM `%TABLE_PREFIX%groups`, `%TABLE_PREFIX%department`; DROP TABLE IF EXISTS `%TABLE_PREFIX%help_topic`; CREATE TABLE `%TABLE_PREFIX%help_topic` ( @@ -400,6 +440,7 @@ CREATE TABLE `%TABLE_PREFIX%help_topic` ( `staff_id` int(10) unsigned NOT NULL default '0', `team_id` int(10) unsigned NOT NULL default '0', `sla_id` int(10) unsigned NOT NULL default '0', + `page_id` int(10) unsigned NOT NULL default '0', `topic` varchar(32) NOT NULL default '', `notes` text, `created` datetime NOT NULL, @@ -410,12 +451,13 @@ CREATE TABLE `%TABLE_PREFIX%help_topic` ( KEY `priority_id` (`priority_id`), KEY `dept_id` (`dept_id`), KEY `staff_id` (`staff_id`,`team_id`), - KEY `sla_id` (`sla_id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; + KEY `sla_id` (`sla_id`), + KEY `page_id` (`page_id`) +) DEFAULT CHARSET=utf8; -INSERT INTO `%TABLE_PREFIX%help_topic` (`topic_id`, `isactive`, `ispublic`, `noautoresp`, `priority_id`, `dept_id`, `staff_id`, `team_id`, `sla_id`, `topic`, `notes`) VALUES - (1, 1, 1, 0, 2, 1, 0, 0, 1, 'Support', NULL), - (2, 1, 1, 0, 3, 1, 0, 0, 0, 'Billing', NULL); +INSERT INTO `%TABLE_PREFIX%help_topic` (`isactive`, `ispublic`, `noautoresp`, `dept_id`, `sla_id`, `topic`, `notes`) VALUES + (1, 1, 0, (SELECT `dept_id` FROM `%TABLE_PREFIX%department` ORDER BY `dept_id` LIMIT 1), (SELECT `id` FROM `%TABLE_PREFIX%sla` ORDER BY `id` LIMIT 1), 'Support', NULL), + (1, 1, 0, (SELECT `dept_id` FROM `%TABLE_PREFIX%department` ORDER BY `dept_id` LIMIT 1), 0, 'Billing', NULL); DROP TABLE IF EXISTS `%TABLE_PREFIX%canned_response`; CREATE TABLE `%TABLE_PREFIX%canned_response` ( @@ -430,27 +472,27 @@ CREATE TABLE `%TABLE_PREFIX%canned_response` ( PRIMARY KEY (`canned_id`), UNIQUE KEY `title` (`title`), KEY `dept_id` (`dept_id`), - KEY `active` (`isenabled`), - FULLTEXT KEY `resp` (`title`,`response`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; + KEY `active` (`isenabled`) +) DEFAULT CHARSET=utf8; -INSERT INTO `%TABLE_PREFIX%canned_response` (`canned_id`, `dept_id`, `isenabled`, `title`, `response`) VALUES - (1, 0, 1, 'What is osTicket (sample)?', '\r\nosTicket is a widely-used open source support ticket system, an attractive alternative to higher-cost and complex customer support systems - simple, lightweight, reliable, open source, web-based and easy to setup and use.'), - (2, 0, 1, 'Sample (with variables)', '\r\n%{ticket.name},\r\n\r\nYour ticket #%{ticket.number} created on %{ticket.create_date} is in %{ticket.dept.name} department.\r\n\r\n'); +INSERT INTO `%TABLE_PREFIX%canned_response` (`isenabled`, `title`, `response`) VALUES + (1, 'What is osTicket (sample)?', '\r\nosTicket is a widely-used open source support ticket system, an attractive alternative to higher-cost and complex customer support systems - simple, lightweight, reliable, open source, web-based and easy to setup and use.'), + (1, 'Sample (with variables)', '\r\n%{ticket.name},\r\n\r\nYour ticket #%{ticket.number} created on %{ticket.create_date} is in %{ticket.dept.name} department.\r\n\r\n'); DROP TABLE IF EXISTS `%TABLE_PREFIX%canned_attachment`; CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%canned_attachment` ( `canned_id` int(10) unsigned NOT NULL, `file_id` int(10) unsigned NOT NULL, PRIMARY KEY (`canned_id`,`file_id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; -INSERT INTO `%TABLE_PREFIX%canned_attachment` (`canned_id`, `file_id`) VALUES (1,1); +INSERT INTO `%TABLE_PREFIX%canned_attachment` (`canned_id`, `file_id`) + VALUES (LAST_INSERT_ID(), (SELECT `id` FROM `%TABLE_PREFIX%file` ORDER BY `id` LIMIT 1)); DROP TABLE IF EXISTS `%TABLE_PREFIX%session`; CREATE TABLE `%TABLE_PREFIX%session` ( - `session_id` varchar(256) collate utf8_unicode_ci NOT NULL default '', - `session_data` longtext collate utf8_unicode_ci, + `session_id` varchar(255) collate ascii_general_ci NOT NULL default '', + `session_data` blob, `session_expire` datetime default NULL, `session_updated` datetime default NULL, `user_id` int(10) unsigned NOT NULL default '0' COMMENT 'osTicket staff ID', @@ -459,26 +501,7 @@ CREATE TABLE `%TABLE_PREFIX%session` ( PRIMARY KEY (`session_id`), KEY `updated` (`session_updated`), KEY `user_id` (`user_id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; - -DROP TABLE IF EXISTS `%TABLE_PREFIX%sla`; -CREATE TABLE `%TABLE_PREFIX%sla` ( - `id` int(11) unsigned NOT NULL auto_increment, - `isactive` tinyint(1) unsigned NOT NULL default '1', - `enable_priority_escalation` tinyint(1) unsigned NOT NULL default '1', - `disable_overdue_alerts` tinyint(1) unsigned NOT NULL default '0', - `grace_period` int(10) unsigned NOT NULL default '0', - `name` varchar(64) NOT NULL default '', - `notes` text, - `created` datetime NOT NULL, - `updated` datetime NOT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `name` (`name`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; - -INSERT INTO `%TABLE_PREFIX%sla` (`isactive`, `enable_priority_escalation`, - `disable_overdue_alerts`, `grace_period`, `name`, `notes`, `created`, `updated`) - VALUES (1, 1, 0, 48, 'Default SLA', NULL, NOW(), NOW()); +) DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; DROP TABLE IF EXISTS `%TABLE_PREFIX%staff`; CREATE TABLE `%TABLE_PREFIX%staff` ( @@ -517,7 +540,7 @@ CREATE TABLE `%TABLE_PREFIX%staff` ( KEY `dept_id` (`dept_id`), KEY `issuperuser` (`isadmin`), KEY `group_id` (`group_id`,`staff_id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `%TABLE_PREFIX%syslog`; CREATE TABLE `%TABLE_PREFIX%syslog` ( @@ -531,7 +554,7 @@ CREATE TABLE `%TABLE_PREFIX%syslog` ( `updated` datetime NOT NULL, PRIMARY KEY (`log_id`), KEY `log_type` (`log_type`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `%TABLE_PREFIX%team`; CREATE TABLE `%TABLE_PREFIX%team` ( @@ -547,10 +570,10 @@ CREATE TABLE `%TABLE_PREFIX%team` ( UNIQUE KEY `name` (`name`), KEY `isnabled` (`isenabled`), KEY `lead_id` (`lead_id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; -INSERT INTO `%TABLE_PREFIX%team` (`lead_id`, `isenabled`, `noalerts`, `name`, `notes`, `created`, `updated`) - VALUES (0, 1, 0, 'Level I Support', '', NOW(), NOW()); +INSERT INTO `%TABLE_PREFIX%team` (`isenabled`, `noalerts`, `name`, `notes`, `created`, `updated`) + VALUES (1, 0, 'Level I Support', '', NOW(), NOW()); DROP TABLE IF EXISTS `%TABLE_PREFIX%team_member`; CREATE TABLE `%TABLE_PREFIX%team_member` ( @@ -558,15 +581,15 @@ CREATE TABLE `%TABLE_PREFIX%team_member` ( `staff_id` int(10) unsigned NOT NULL, `updated` datetime NOT NULL, PRIMARY KEY (`team_id`,`staff_id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `%TABLE_PREFIX%ticket`; CREATE TABLE `%TABLE_PREFIX%ticket` ( `ticket_id` int(11) unsigned NOT NULL auto_increment, `ticketID` int(11) unsigned NOT NULL default '0', - `dept_id` int(10) unsigned NOT NULL default '1', + `dept_id` int(10) unsigned NOT NULL default '0', `sla_id` int(10) unsigned NOT NULL default '0', - `priority_id` int(10) unsigned NOT NULL default '2', + `priority_id` int(10) unsigned NOT NULL default '0', `topic_id` int(10) unsigned NOT NULL default '0', `staff_id` int(10) unsigned NOT NULL default '0', `team_id` int(10) unsigned NOT NULL default '0', @@ -577,8 +600,7 @@ CREATE TABLE `%TABLE_PREFIX%ticket` ( `phone_ext` varchar(8) default NULL, `ip_address` varchar(64) NOT NULL default '', `status` enum('open','closed') NOT NULL default 'open', - `source` enum('Web','Email','Phone','API','Other') NOT NULL default -'Other', + `source` enum('Web','Email','Phone','API','Other') NOT NULL default 'Other', `isoverdue` tinyint(1) unsigned NOT NULL default '0', `isanswered` tinyint(1) unsigned NOT NULL default '0', `duedate` datetime default NULL, @@ -600,7 +622,7 @@ CREATE TABLE `%TABLE_PREFIX%ticket` ( KEY `duedate` (`duedate`), KEY `topic_id` (`topic_id`), KEY `sla_id` (`sla_id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `%TABLE_PREFIX%ticket_attachment`; CREATE TABLE `%TABLE_PREFIX%ticket_attachment` ( @@ -615,7 +637,7 @@ CREATE TABLE `%TABLE_PREFIX%ticket_attachment` ( KEY `ref_type` (`ref_type`), KEY `ref_id` (`ref_id`), KEY `file_id` (`file_id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `%TABLE_PREFIX%ticket_lock`; CREATE TABLE `%TABLE_PREFIX%ticket_lock` ( @@ -627,7 +649,7 @@ CREATE TABLE `%TABLE_PREFIX%ticket_lock` ( PRIMARY KEY (`lock_id`), UNIQUE KEY `ticket_id` (`ticket_id`), KEY `staff_id` (`staff_id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `%TABLE_PREFIX%ticket_email_info`; CREATE TABLE `%TABLE_PREFIX%ticket_email_info` ( @@ -635,7 +657,7 @@ CREATE TABLE `%TABLE_PREFIX%ticket_email_info` ( `email_mid` varchar(255) NOT NULL, `headers` text, KEY `message_id` (`email_mid`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `%TABLE_PREFIX%ticket_event`; CREATE TABLE `%TABLE_PREFIX%ticket_event` ( @@ -650,7 +672,7 @@ CREATE TABLE `%TABLE_PREFIX%ticket_event` ( `timestamp` datetime NOT NULL, KEY `ticket_state` (`ticket_id`, `state`, `timestamp`), KEY `ticket_stats` (`timestamp`, `state`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `%TABLE_PREFIX%ticket_priority`; CREATE TABLE `%TABLE_PREFIX%ticket_priority` ( @@ -664,13 +686,13 @@ CREATE TABLE `%TABLE_PREFIX%ticket_priority` ( UNIQUE KEY `priority` (`priority`), KEY `priority_urgency` (`priority_urgency`), KEY `ispublic` (`ispublic`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; -INSERT INTO `%TABLE_PREFIX%ticket_priority` (`priority_id`, `priority`, `priority_desc`, `priority_color`, `priority_urgency`, `ispublic`) VALUES - (1, 'low', 'Low', '#DDFFDD', 4, 1), - (2, 'normal', 'Normal', '#FFFFF0', 3, 1), - (3, 'high', 'High', '#FEE7E7', 2, 1), - (4, 'emergency', 'Emergency', '#FEE7E7', 1, 0); +INSERT INTO `%TABLE_PREFIX%ticket_priority` (`priority`, `priority_desc`, `priority_color`, `priority_urgency`, `ispublic`) VALUES + ('low', 'Low', '#DDFFDD', 4, 1), + ('normal', 'Normal', '#FFFFF0', 3, 1), + ('high', 'High', '#FEE7E7', 2, 1), + ('emergency', 'Emergency', '#FEE7E7', 1, 0); DROP TABLE IF EXISTS `%TABLE_PREFIX%ticket_thread`; CREATE TABLE `%TABLE_PREFIX%ticket_thread` ( @@ -689,9 +711,8 @@ CREATE TABLE `%TABLE_PREFIX%ticket_thread` ( PRIMARY KEY (`id`), KEY `ticket_id` (`ticket_id`), KEY `staff_id` (`staff_id`), - KEY `pid` (`pid`), - FULLTEXT KEY `body` (`body`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; + KEY `pid` (`pid`) +) DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `%TABLE_PREFIX%timezone`; CREATE TABLE `%TABLE_PREFIX%timezone` ( @@ -699,36 +720,61 @@ CREATE TABLE `%TABLE_PREFIX%timezone` ( `offset` float(3,1) NOT NULL default '0.0', `timezone` varchar(255) NOT NULL default '', PRIMARY KEY (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; - -INSERT INTO `%TABLE_PREFIX%timezone` (`id`, `offset`, `timezone`) VALUES -(1, -12.0, 'Eniwetok, Kwajalein'), -(2, -11.0, 'Midway Island, Samoa'), -(3, -10.0, 'Hawaii'), -(4, -9.0, 'Alaska'), -(5, -8.0, 'Pacific Time (US & Canada)'), -(6, -7.0, 'Mountain Time (US & Canada)'), -(7, -6.0, 'Central Time (US & Canada), Mexico City'), -(8, -5.0, 'Eastern Time (US & Canada), Bogota, Lima'), -(9, -4.0, 'Atlantic Time (Canada), Caracas, La Paz'), -(10, -3.5, 'Newfoundland'), -(11, -3.0, 'Brazil, Buenos Aires, Georgetown'), -(12, -2.0, 'Mid-Atlantic'), -(13, -1.0, 'Azores, Cape Verde Islands'), -(14, 0.0, 'Western Europe Time, London, Lisbon, Casablanca'), -(15, 1.0, 'Brussels, Copenhagen, Madrid, Paris'), -(16, 2.0, 'Kaliningrad, South Africa'), -(17, 3.0, 'Baghdad, Riyadh, Moscow, St. Petersburg'), -(18, 3.5, 'Tehran'), -(19, 4.0, 'Abu Dhabi, Muscat, Baku, Tbilisi'), -(20, 4.5, 'Kabul'), -(21, 5.0, 'Ekaterinburg, Islamabad, Karachi, Tashkent'), -(22, 5.5, 'Bombay, Calcutta, Madras, New Delhi'), -(23, 6.0, 'Almaty, Dhaka, Colombo'), -(24, 7.0, 'Bangkok, Hanoi, Jakarta'), -(25, 8.0, 'Beijing, Perth, Singapore, Hong Kong'), -(26, 9.0, 'Tokyo, Seoul, Osaka, Sapporo, Yakutsk'), -(27, 9.5, 'Adelaide, Darwin'), -(28, 10.0, 'Eastern Australia, Guam, Vladivostok'), -(29, 11.0, 'Magadan, Solomon Islands, New Caledonia'), -(30, 12.0, 'Auckland, Wellington, Fiji, Kamchatka'); +) DEFAULT CHARSET=utf8; + +INSERT INTO `%TABLE_PREFIX%timezone` (`offset`, `timezone`) VALUES + (-12.0, 'Eniwetok, Kwajalein'), + (-11.0, 'Midway Island, Samoa'), + (-10.0, 'Hawaii'), + (-9.0, 'Alaska'), + (-8.0, 'Pacific Time (US & Canada)'), + (-7.0, 'Mountain Time (US & Canada)'), + (-6.0, 'Central Time (US & Canada), Mexico City'), + (-5.0, 'Eastern Time (US & Canada), Bogota, Lima'), + (-4.0, 'Atlantic Time (Canada), Caracas, La Paz'), + (-3.5, 'Newfoundland'), + (-3.0, 'Brazil, Buenos Aires, Georgetown'), + (-2.0, 'Mid-Atlantic'), + (-1.0, 'Azores, Cape Verde Islands'), + (0.0, 'Western Europe Time, London, Lisbon, Casablanca'), + (1.0, 'Brussels, Copenhagen, Madrid, Paris'), + (2.0, 'Kaliningrad, South Africa'), + (3.0, 'Baghdad, Riyadh, Moscow, St. Petersburg'), + (3.5, 'Tehran'), + (4.0, 'Abu Dhabi, Muscat, Baku, Tbilisi'), + (4.5, 'Kabul'), + (5.0, 'Ekaterinburg, Islamabad, Karachi, Tashkent'), + (5.5, 'Bombay, Calcutta, Madras, New Delhi'), + (6.0, 'Almaty, Dhaka, Colombo'), + (7.0, 'Bangkok, Hanoi, Jakarta'), + (8.0, 'Beijing, Perth, Singapore, Hong Kong'), + (9.0, 'Tokyo, Seoul, Osaka, Sapporo, Yakutsk'), + (9.5, 'Adelaide, Darwin'), + (10.0, 'Eastern Australia, Guam, Vladivostok'), + (11.0, 'Magadan, Solomon Islands, New Caledonia'), + (12.0, 'Auckland, Wellington, Fiji, Kamchatka'); + +-- pages +CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%page` ( + `id` int(10) unsigned NOT NULL auto_increment, + `isactive` tinyint(1) unsigned NOT NULL default '0', + `type` enum('landing','offline','thank-you','other') NOT NULL default 'other', + `name` varchar(255) NOT NULL, + `body` text NOT NULL, + `notes` text, + `created` datetime NOT NULL, + `updated` datetime NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`) +) DEFAULT CHARSET=utf8; + + +INSERT INTO `%TABLE_PREFIX%page` (`id`, `isactive`, `type`, `name`, `body`, `notes`, `created`, `updated`) VALUES +('', 1, 'offline', 'Offline', '<div>\r\n<h1><span style="font-size: medium">Support Ticket System Offline</span></h1>\r\n<p>Thank you for your interest in contacting us.</p>\r\n<p>Our helpdesk is offline at the moment, please check back at a later time.</p>\r\n</div>', 'Default offline page', NOW(), NOW()), +('', 1, 'thank-you', 'Thank you', '<div>%{ticket.name},<br />\r\n \r\n<p>\r\nThank you for contacting us.</p><p> A support ticket request #%{ticket.number} has been created and a representative will be getting back to you shortly if necessary.</p>\r\n \r\n<p>Support Team </p>\r\n</div>', 'Default "thank you" page displayed after the end-user creates a web ticket.', NOW(), NOW()), +('', 1, 'landing', 'Landing', '<h1>Welcome to the Support Center</h1>\r\n<p>In order to streamline support requests and better serve you, we utilize a support ticket system. Every support request is assigned a unique ticket number which you can use to track the progress and responses online. For your reference we provide complete archives and history of all your support requests. A valid email address is required to submit a ticket.\r\n</p>\r\n', 'Introduction text on the landing page.', NOW(), NOW()); + +INSERT INTO `%TABLE_PREFIX%config` (`key`, `value`, `namespace`) VALUES + ('landing_page_id', (SELECT `id` FROM `%TABLE_PREFIX%page` WHERE `type` = 'landing'), 'core'), + ('offline_page_id', (SELECT `id` FROM `%TABLE_PREFIX%page` WHERE `type` = 'offline'), 'core'), + ('thank-you_page_id', (SELECT `id` FROM `%TABLE_PREFIX%page` WHERE `type` = 'thank-you'), 'core'); diff --git a/setup/install.php b/setup/install.php index 497d37817faf22754c6af26468a56873190ca702..2bca44418e655637620993439f34ca1e0a0329f7 100644 --- a/setup/install.php +++ b/setup/install.php @@ -25,7 +25,7 @@ define('OSTICKET_CONFIGFILE','../include/ost-config.php'); //XXX: Make sure the $installer = new Installer(OSTICKET_CONFIGFILE); //Installer instance. $wizard=array(); $wizard['title']='osTicket Installer'; -$wizard['tagline']='Installing osTicket v'.$installer->getVersionVerbose(); +$wizard['tagline']='Installing osTicket '.$installer->getVersionVerbose(); $wizard['logo']='logo.png'; $wizard['menu']=array('Installation Guide'=>'http://osticket.com/wiki/Installation', 'Get Professional Help'=>'http://osticket.com/support'); @@ -85,17 +85,17 @@ switch(strtolower($_SESSION['ost_installer']['s'])) { case 'install': if(!$installer->config_exists()) { $inc='file-missing.inc.php'; - } elseif(!($cFile=file_get_contents($installer->getConfigFile())) + } elseif(!($cFile=file_get_contents($installer->getConfigFile())) || preg_match("/define\('OSTINSTALLED',TRUE\)\;/i",$cFile)) { //osTicket already installed or empty config file? $inc='file-unclean.inc.php'; } elseif(!$installer->config_writable()) { //writable config file?? clearstatcache(); $inc='file-perm.inc.php'; } else { //Everything checked out show install form. - $inc='install.inc.php'; + $inc='install.inc.php'; } break; - case 'subscribe': //TODO: Prep for v1.7 RC1 + case 'subscribe': //TODO: Prep for v1.7 RC1 $inc='subscribe.inc.php'; break; case 'done': @@ -105,10 +105,10 @@ switch(strtolower($_SESSION['ost_installer']['s'])) { break; default: //Fail IF any of the old config files exists. - if(file_exists(INCLUDE_DIR.'settings.php') + if(file_exists(INCLUDE_DIR.'settings.php') || file_exists(ROOT_DIR.'ostconfig.php') - || (file_exists(OSTICKET_CONFIGFILE) - && preg_match("/define\('OSTINSTALLED',TRUE\)\;/i", + || (file_exists(OSTICKET_CONFIGFILE) + && preg_match("/define\('OSTINSTALLED',TRUE\)\;/i", file_get_contents(OSTICKET_CONFIGFILE))) ) $inc='file-unclean.inc.php'; diff --git a/setup/js/jquery-1.6.2.min.js b/setup/js/jquery-1.6.2.min.js deleted file mode 100644 index 48590ecb96a74f5987d125e7fbc5a26c1392543a..0000000000000000000000000000000000000000 --- a/setup/js/jquery-1.6.2.min.js +++ /dev/null @@ -1,18 +0,0 @@ -/*! - * jQuery JavaScript Library v1.6.2 - * http://jquery.com/ - * - * Copyright 2011, John Resig - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * Includes Sizzle.js - * http://sizzlejs.com/ - * Copyright 2011, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * - * Date: Thu Jun 30 14:16:56 2011 -0400 - */ -(function(a,b){function cv(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cs(a){if(!cg[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ch||(ch=c.createElement("iframe"),ch.frameBorder=ch.width=ch.height=0),b.appendChild(ch);if(!ci||!ch.createElement)ci=(ch.contentWindow||ch.contentDocument).document,ci.write((c.compatMode==="CSS1Compat"?"<!doctype html>":"")+"<html><body>"),ci.close();d=ci.createElement(a),ci.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ch)}cg[a]=e}return cg[a]}function cr(a,b){var c={};f.each(cm.concat.apply([],cm.slice(0,b)),function(){c[this]=a});return c}function cq(){cn=b}function cp(){setTimeout(cq,0);return cn=f.now()}function cf(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ce(){try{return new a.XMLHttpRequest}catch(b){}}function b$(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function bZ(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function bY(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bC.test(a)?d(a,e):bY(a+"["+(typeof e=="object"||f.isArray(e)?b:"")+"]",e,c,d)});else if(!c&&b!=null&&typeof b=="object")for(var e in b)bY(a+"["+e+"]",b[e],c,d);else d(a,b)}function bX(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bR,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=bX(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=bX(a,c,d,e,"*",g));return l}function bW(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bN),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bA(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=b==="width"?bv:bw;if(d>0){c!=="border"&&f.each(e,function(){c||(d-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?d+=parseFloat(f.css(a,c+this))||0:d-=parseFloat(f.css(a,"border"+this+"Width"))||0});return d+"px"}d=bx(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0,c&&f.each(e,function(){d+=parseFloat(f.css(a,"padding"+this))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+this+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+this))||0)});return d+"px"}function bm(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(be,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bl(a){f.nodeName(a,"input")?bk(a):"getElementsByTagName"in a&&f.grep(a.getElementsByTagName("input"),bk)}function bk(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bj(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bi(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bh(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;i<j;i++)f.event.add(b,h+(g[h][i].namespace?".":"")+g[h][i].namespace,g[h][i],g[h][i].data)}}}}function bg(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function W(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(R.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function V(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function N(a,b){return(a&&a!=="*"?a+".":"")+b.replace(z,"`").replace(A,"&")}function M(a){var b,c,d,e,g,h,i,j,k,l,m,n,o,p=[],q=[],r=f._data(this,"events");if(!(a.liveFired===this||!r||!r.live||a.target.disabled||a.button&&a.type==="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var s=r.live.slice(0);for(i=0;i<s.length;i++)g=s[i],g.origType.replace(x,"")===a.type?q.push(g.selector):s.splice(i--,1);e=f(a.target).closest(q,a.currentTarget);for(j=0,k=e.length;j<k;j++){m=e[j];for(i=0;i<s.length;i++){g=s[i];if(m.selector===g.selector&&(!n||n.test(g.namespace))&&!m.elem.disabled){h=m.elem,d=null;if(g.preType==="mouseenter"||g.preType==="mouseleave")a.type=g.preType,d=f(a.relatedTarget).closest(g.selector)[0],d&&f.contains(h,d)&&(d=h);(!d||d!==h)&&p.push({elem:h,handleObj:g,level:m.level})}}}for(j=0,k=p.length;j<k;j++){e=p[j];if(c&&e.level>c)break;a.currentTarget=e.elem,a.data=e.handleObj.data,a.handleObj=e.handleObj,o=e.handleObj.origHandler.apply(e.elem,arguments);if(o===!1||a.isPropagationStopped()){c=e.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function K(a,c,d){var e=f.extend({},d[0]);e.type=a,e.originalEvent={},e.liveFired=b,f.event.handle.call(c,e),e.isDefaultPrevented()&&d[0].preventDefault()}function E(){return!0}function D(){return!1}function m(a,c,d){var e=c+"defer",g=c+"queue",h=c+"mark",i=f.data(a,e,b,!0);i&&(d==="queue"||!f.data(a,g,b,!0))&&(d==="mark"||!f.data(a,h,b,!0))&&setTimeout(function(){!f.data(a,g,b,!0)&&!f.data(a,h,b,!0)&&(f.removeData(a,e,!0),i.resolve())},0)}function l(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function k(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(j,"$1-$2").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNaN(d)?i.test(d)?f.parseJSON(d):d:parseFloat(d)}catch(g){}f.data(a,c,d)}else d=b}return d}var c=a.document,d=a.navigator,e=a.location,f=function(){function J(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(J,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=/-([a-z])/ig,x=function(a,b){return b.toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.6.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;A.resolveWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!A){A=e._Deferred();if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNaN:function(a){return a==null||!m.test(a)||isNaN(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1;var c;for(c in a);return c===b||D.call(a,c)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(b,c,d){a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b)),d=c.documentElement,(!d||!d.nodeName||d.nodeName==="parsererror")&&e.error("Invalid XML: "+b);return c},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:G?function(a){return a==null?"":G.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?E.call(c,a):e.merge(c,a)}return c},inArray:function(a,b){if(H)return H.call(b,a);for(var c=0,d=b.length;c<d;c++)if(b[c]===a)return c;return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=F.call(arguments,2),g=function(){return a.apply(c,f.concat(F.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h){var i=a.length;if(typeof c=="object"){for(var j in c)e.access(a,j,c[j],f,g,d);return a}if(d!==b){f=!h&&f&&e.isFunction(d);for(var k=0;k<i;k++)g(a[k],c,f?d.call(a[k],k,g(a[k],c)):d,h);return a}return i?g(a[0],c):b},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=s.exec(a)||t.exec(a)||u.exec(a)||a.indexOf("compatible")<0&&v.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){I["[object "+b+"]"]=b.toLowerCase()}),z=e.uaMatch(y),z.browser&&(e.browser[z.browser]=!0,e.browser.version=z.version),e.browser.webkit&&(e.browser.safari=!0),j.test(" ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?B=function(){c.removeEventListener("DOMContentLoaded",B,!1),e.ready()}:c.attachEvent&&(B=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",B),e.ready())});return e}(),g="done fail isResolved isRejected promise then always pipe".split(" "),h=[].slice;f.extend({_Deferred:function(){var a=[],b,c,d,e={done:function(){if(!d){var c=arguments,g,h,i,j,k;b&&(k=b,b=0);for(g=0,h=c.length;g<h;g++)i=c[g],j=f.type(i),j==="array"?e.done.apply(e,i):j==="function"&&a.push(i);k&&e.resolveWith(k[0],k[1])}return this},resolveWith:function(e,f){if(!d&&!b&&!c){f=f||[],c=1;try{while(a[0])a.shift().apply(e,f)}finally{b=[e,f],c=0}}return this},resolve:function(){e.resolveWith(this,arguments);return this},isResolved:function(){return!!c||!!b},cancel:function(){d=1,a=[];return this}};return e},Deferred:function(a){var b=f._Deferred(),c=f._Deferred(),d;f.extend(b,{then:function(a,c){b.done(a).fail(c);return this},always:function(){return b.done.apply(b,arguments).fail.apply(this,arguments)},fail:c.done,rejectWith:c.resolveWith,reject:c.resolve,isRejected:c.isResolved,pipe:function(a,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[c,"reject"]},function(a,c){var e=c[0],g=c[1],h;f.isFunction(e)?b[a](function(){h=e.apply(this,arguments),h&&f.isFunction(h.promise)?h.promise().then(d.resolve,d.reject):d[g](h)}):b[a](d[g])})}).promise()},promise:function(a){if(a==null){if(d)return d;d=a={}}var c=g.length;while(c--)a[g[c]]=b[g[c]];return a}}),b.done(c.cancel).fail(b.cancel),delete b.cancel,a&&a.call(b,b);return b},when:function(a){function i(a){return function(c){b[a]=arguments.length>1?h.call(arguments,0):c,--e||g.resolveWith(g,h.call(b,0))}}var b=arguments,c=0,d=b.length,e=d,g=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred();if(d>1){for(;c<d;c++)b[c]&&f.isFunction(b[c].promise)?b[c].promise().then(i(c),g.reject):--e;e||g.resolveWith(g,b)}else g!==a&&g.resolveWith(g,d?[a]:[]);return g.promise()}}),f.support=function(){var a=c.createElement("div"),b=c.documentElement,d,e,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u;a.setAttribute("className","t"),a.innerHTML=" <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",d=a.getElementsByTagName("*"),e=a.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=a.getElementsByTagName("input")[0],k={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55$/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:a.className!=="t",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,k.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,k.optDisabled=!h.disabled;try{delete a.test}catch(v){k.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function(){k.noCloneEvent=!1}),a.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),k.radioValue=i.value==="t",i.setAttribute("checked","checked"),a.appendChild(i),l=c.createDocumentFragment(),l.appendChild(a.firstChild),k.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",m=c.getElementsByTagName("body")[0],o=c.createElement(m?"div":"body"),p={visibility:"hidden",width:0,height:0,border:0,margin:0},m&&f.extend(p,{position:"absolute",left:-1e3,top:-1e3});for(t in p)o.style[t]=p[t];o.appendChild(a),n=m||b,n.insertBefore(o,n.firstChild),k.appendChecked=i.checked,k.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,k.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="<div style='width:4px;'></div>",k.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>",q=a.getElementsByTagName("td"),u=q[0].offsetHeight===0,q[0].style.display="",q[1].style.display="none",k.reliableHiddenOffsets=u&&q[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",a.appendChild(j),k.reliableMarginRight=(parseInt((c.defaultView.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0),o.innerHTML="",n.removeChild(o);if(a.attachEvent)for(t in{submit:1,change:1,focusin:1})s="on"+t,u=s in a,u||(a.setAttribute(s,"return;"),u=typeof a[s]=="function"),k[t+"Bubbles"]=u;o=l=g=h=m=j=a=i=null;return k}(),f.boxModel=f.support.boxModel;var i=/^(?:\{.*\}|\[.*\])$/,j=/([a-z])([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!l(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g=f.expando,h=typeof c=="string",i,j=a.nodeType,k=j?f.cache:a,l=j?a[f.expando]:a[f.expando]&&f.expando;if((!l||e&&l&&!k[l][g])&&h&&d===b)return;l||(j?a[f.expando]=l=++f.uuid:l=f.expando),k[l]||(k[l]={},j||(k[l].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?k[l][g]=f.extend(k[l][g],c):k[l]=f.extend(k[l],c);i=k[l],e&&(i[g]||(i[g]={}),i=i[g]),d!==b&&(i[f.camelCase(c)]=d);if(c==="events"&&!i[c])return i[g]&&i[g].events;return h?i[f.camelCase(c)]||i[c]:i}},removeData:function(b,c,d){if(!!f.acceptData(b)){var e=f.expando,g=b.nodeType,h=g?f.cache:b,i=g?b[f.expando]:f.expando;if(!h[i])return;if(c){var j=d?h[i][e]:h[i];if(j){delete j[c];if(!l(j))return}}if(d){delete h[i][e];if(!l(h[i]))return}var k=h[i][e];f.support.deleteExpando||h!=a?delete h[i]:h[i]=null,k?(h[i]={},g||(h[i].toJSON=f.noop),h[i][e]=k):g&&(f.support.deleteExpando?delete b[f.expando]:b.removeAttribute?b.removeAttribute(f.expando):b[f.expando]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d=null;if(typeof a=="undefined"){if(this.length){d=f.data(this[0]);if(this[0].nodeType===1){var e=this[0].attributes,g;for(var h=0,i=e.length;h<i;h++)g=e[h].name,g.indexOf("data-")===0&&(g=f.camelCase(g.substring(5)),k(this[0],g,d[g]))}}return d}if(typeof a=="object")return this.each(function(){f.data(this,a)});var j=a.split(".");j[1]=j[1]?"."+j[1]:"";if(c===b){d=this.triggerHandler("getData"+j[1]+"!",[j[0]]),d===b&&this.length&&(d=f.data(this[0],a),d=k(this[0],a,d));return d===b&&j[1]?this.data(j[0]):d}return this.each(function(){var b=f(this),d=[j[0],c];b.triggerHandler("setData"+j[1]+"!",d),f.data(this,a,c),b.triggerHandler("changeData"+j[1]+"!",d)})},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,c){a&&(c=(c||"fx")+"mark",f.data(a,c,(f.data(a,c,b,!0)||0)+1,!0))},_unmark:function(a,c,d){a!==!0&&(d=c,c=a,a=!1);if(c){d=d||"fx";var e=d+"mark",g=a?0:(f.data(c,e,b,!0)||1)-1;g?f.data(c,e,g,!0):(f.removeData(c,e,!0),m(c,d,"mark"))}},queue:function(a,c,d){if(a){c=(c||"fx")+"queue";var e=f.data(a,c,b,!0);d&&(!e||f.isArray(d)?e=f.data(a,c,f.makeArray(d),!0):e.push(d));return e||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e;d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),d.call(a,function(){f.dequeue(a,b)})),c.length||(f.removeData(a,b+"queue",!0),m(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){typeof a!="string"&&(c=a,a="fx");if(c===b)return f.queue(this[0],a);return this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(){var c=this;setTimeout(function(){f.dequeue(c,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f._Deferred(),!0))h++,l.done(m);m();return d.promise()}});var n=/[\n\t\r]/g,o=/\s+/,p=/\r/g,q=/^(?:button|input)$/i,r=/^(?:button|input|object|select|textarea)$/i,s=/^a(?:rea)?$/i,t=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,u=/\:|^on/,v,w;f.fn.extend({attr:function(a,b){return f.access(this,a,b,!0,f.attr)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,a,b,!0,f.prop)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(o);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{g=" "+e.className+" ";for(h=0,i=b.length;h<i;h++)~g.indexOf(" "+b[h]+" ")||(g+=b[h]+" ");e.className=f.trim(g)}}}return this},removeClass:function(a){var c,d,e,g,h,i,j;if(f.isFunction(a))return this.each(function(b){f(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(o);for(d=0,e=this.length;d<e;d++){g=this[d];if(g.nodeType===1&&g.className)if(a){h=(" "+g.className+" ").replace(n," ");for(i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){f(this).toggleClass(a.call(this,c,this.className,b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(o);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ";for(var c=0,d=this.length;c<d;c++)if((" "+this[c].className+" ").replace(n," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e=this[0];if(!arguments.length){if(e){c=f.valHooks[e.nodeName.toLowerCase()]||f.valHooks[e.type];if(c&&"get"in c&&(d=c.get(e,"value"))!==b)return d;d=e.value;return typeof d=="string"?d.replace(p,""):d==null?"":d}return b}var g=f.isFunction(a);return this.each(function(d){var e=f(this),h;if(this.nodeType===1){g?h=a.call(this,d,e.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c=a.selectedIndex,d=[],e=a.options,g=a.type==="select-one";if(c<0)return null;for(var h=g?c:0,i=g?c+1:e.length;h<i;h++){var j=e[h];if(j.selected&&(f.support.optDisabled?!j.disabled:j.getAttribute("disabled")===null)&&(!j.parentNode.disabled||!f.nodeName(j.parentNode,"optgroup"))){b=f(j).val();if(g)return b;d.push(b)}}if(g&&!d.length&&e.length)return f(e[c]).val();return d},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attrFix:{tabindex:"tabIndex"},attr:function(a,c,d,e){var g=a.nodeType;if(!a||g===3||g===8||g===2)return b;if(e&&c in f.attrFn)return f(a)[c](d);if(!("getAttribute"in a))return f.prop(a,c,d);var h,i,j=g!==1||!f.isXMLDoc(a);j&&(c=f.attrFix[c]||c,i=f.attrHooks[c],i||(t.test(c)?i=w:v&&c!=="className"&&(f.nodeName(a,"form")||u.test(c))&&(i=v)));if(d!==b){if(d===null){f.removeAttr(a,c);return b}if(i&&"set"in i&&j&&(h=i.set(a,d,c))!==b)return h;a.setAttribute(c,""+d);return d}if(i&&"get"in i&&j&&(h=i.get(a,c))!==null)return h;h=a.getAttribute(c);return h===null?b:h},removeAttr:function(a,b){var c;a.nodeType===1&&(b=f.attrFix[b]||b,f.support.getSetAttribute?a.removeAttribute(b):(f.attr(a,b,""),a.removeAttributeNode(a.getAttributeNode(b))),t.test(b)&&(c=f.propFix[b]||b)in a&&(a[c]=!1))},attrHooks:{type:{set:function(a,b){if(q.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},tabIndex:{get:function(a){var c=a.getAttributeNode("tabIndex");return c&&c.specified?parseInt(c.value,10):r.test(a.nodeName)||s.test(a.nodeName)&&a.href?0:b}},value:{get:function(a,b){if(v&&f.nodeName(a,"button"))return v.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(v&&f.nodeName(a,"button"))return v.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e=a.nodeType;if(!a||e===3||e===8||e===2)return b;var g,h,i=e!==1||!f.isXMLDoc(a);i&&(c=f.propFix[c]||c,h=f.propHooks[c]);return d!==b?h&&"set"in h&&(g=h.set(a,d,c))!==b?g:a[c]=d:h&&"get"in h&&(g=h.get(a,c))!==b?g:a[c]},propHooks:{}}),w={get:function(a,c){return f.prop(a,c)?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},f.support.getSetAttribute||(f.attrFix=f.propFix,v=f.attrHooks.name=f.attrHooks.title=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&d.nodeValue!==""?d.nodeValue:b},set:function(a,b,c){var d=a.getAttributeNode(c);if(d){d.nodeValue=b;return b}}},f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})})),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}})),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var x=/\.(.*)$/,y=/^(?:textarea|input|select)$/i,z=/\./g,A=/ /g,B=/[^\w\s.|`]/g,C=function(a){return a.replace(B,"\\$&")};f.event={add:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){if(d===!1)d=D;else if(!d)return;var g,h;d.handler&&(g=d,d=g.handler),d.guid||(d.guid=f.guid++);var i=f._data(a);if(!i)return;var j=i.events,k=i.handle;j||(i.events=j={}),k||(i.handle=k=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.handle.apply(k.elem,arguments):b}),k.elem=a,c=c.split(" ");var l,m=0,n;while(l=c[m++]){h=g?f.extend({},g):{handler:d,data:e},l.indexOf(".")>-1?(n=l.split("."),l=n.shift(),h.namespace=n.slice(0).sort().join(".")):(n=[],h.namespace=""),h.type=l,h.guid||(h.guid=d.guid);var o=j[l],p=f.event.special[l]||{};if(!o){o=j[l]=[];if(!p.setup||p.setup.call(a,e,n,k)===!1)a.addEventListener?a.addEventListener(l,k,!1):a.attachEvent&&a.attachEvent("on"+l,k)}p.add&&(p.add.call(a,h),h.handler.guid||(h.handler.guid=d.guid)),o.push(h),f.event.global[l]=!0}a=null}},global:{},remove:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){d===!1&&(d=D);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=f.hasData(a)&&f._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(d=c.handler,c=c.type);if(!c||typeof c=="string"&&c.charAt(0)==="."){c=c||"";for(h in t)f.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+f.map(m.slice(0).sort(),C).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!d){for(j=0;j<p.length;j++){q=p[j];if(l||n.test(q.namespace))f.event.remove(a,r,q.handler,j),p.splice(j--,1)}continue}o=f.event.special[h]||{};for(j=e||0;j<p.length;j++){q=p[j];if(d.guid===q.guid){if(l||n.test(q.namespace))e==null&&p.splice(j--,1),o.remove&&o.remove.call(a,q);if(e!=null)break}}if(p.length===0||e!=null&&p.length===1)(!o.teardown||o.teardown.call(a,m)===!1)&&f.removeEvent(a,h,s.handle),g=null,delete t[h]}if(f.isEmptyObject(t)){var u=s.handle;u&&(u.elem=null),delete s.events,delete s.handle,f.isEmptyObject(s)&&f.removeData(a,b,!0)}}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){var h=c.type||c,i=[],j;h.indexOf("!")>=0&&(h=h.slice(0,-1),j=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i. -shift(),i.sort());if(!!e&&!f.event.customEvent[h]||!!f.event.global[h]){c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.exclusive=j,c.namespace=i.join("."),c.namespace_re=new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)");if(g||!e)c.preventDefault(),c.stopPropagation();if(!e){f.each(f.cache,function(){var a=f.expando,b=this[a];b&&b.events&&b.events[h]&&f.event.trigger(c,d,b.handle.elem)});return}if(e.nodeType===3||e.nodeType===8)return;c.result=b,c.target=e,d=d!=null?f.makeArray(d):[],d.unshift(c);var k=e,l=h.indexOf(":")<0?"on"+h:"";do{var m=f._data(k,"handle");c.currentTarget=k,m&&m.apply(k,d),l&&f.acceptData(k)&&k[l]&&k[l].apply(k,d)===!1&&(c.result=!1,c.preventDefault()),k=k.parentNode||k.ownerDocument||k===c.target.ownerDocument&&a}while(k&&!c.isPropagationStopped());if(!c.isDefaultPrevented()){var n,o=f.event.special[h]||{};if((!o._default||o._default.call(e.ownerDocument,c)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)){try{l&&e[h]&&(n=e[l],n&&(e[l]=null),f.event.triggered=h,e[h]())}catch(p){}n&&(e[l]=n),f.event.triggered=b}}return c.result}},handle:function(c){c=f.event.fix(c||a.event);var d=((f._data(this,"events")||{})[c.type]||[]).slice(0),e=!c.exclusive&&!c.namespace,g=Array.prototype.slice.call(arguments,0);g[0]=c,c.currentTarget=this;for(var h=0,i=d.length;h<i;h++){var j=d[h];if(e||c.namespace_re.test(j.namespace)){c.handler=j.handler,c.data=j.data,c.handleObj=j;var k=j.handler.apply(this,g);k!==b&&(c.result=k,k===!1&&(c.preventDefault(),c.stopPropagation()));if(c.isImmediatePropagationStopped())break}}return c.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(a){if(a[f.expando])return a;var d=a;a=f.Event(d);for(var e=this.props.length,g;e;)g=this.props[--e],a[g]=d[g];a.target||(a.target=a.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),!a.relatedTarget&&a.fromElement&&(a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement);if(a.pageX==null&&a.clientX!=null){var h=a.target.ownerDocument||c,i=h.documentElement,j=h.body;a.pageX=a.clientX+(i&&i.scrollLeft||j&&j.scrollLeft||0)-(i&&i.clientLeft||j&&j.clientLeft||0),a.pageY=a.clientY+(i&&i.scrollTop||j&&j.scrollTop||0)-(i&&i.clientTop||j&&j.clientTop||0)}a.which==null&&(a.charCode!=null||a.keyCode!=null)&&(a.which=a.charCode!=null?a.charCode:a.keyCode),!a.metaKey&&a.ctrlKey&&(a.metaKey=a.ctrlKey),!a.which&&a.button!==b&&(a.which=a.button&1?1:a.button&2?3:a.button&4?2:0);return a},guid:1e8,proxy:f.proxy,special:{ready:{setup:f.bindReady,teardown:f.noop},live:{add:function(a){f.event.add(this,N(a.origType,a.selector),f.extend({},a,{handler:M,guid:a.handler.guid}))},remove:function(a){f.event.remove(this,N(a.origType,a.selector),a)}},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}}},f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!this.preventDefault)return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?E:D):this.type=a,b&&f.extend(this,b),this.timeStamp=f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=E;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=E;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=E,this.stopPropagation()},isDefaultPrevented:D,isPropagationStopped:D,isImmediatePropagationStopped:D};var F=function(a){var b=a.relatedTarget,c=!1,d=a.type;a.type=a.data,b!==this&&(b&&(c=f.contains(this,b)),c||(f.event.handle.apply(this,arguments),a.type=d))},G=function(a){a.type=a.data,f.event.handle.apply(this,arguments)};f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={setup:function(c){f.event.add(this,b,c&&c.selector?G:F,a)},teardown:function(a){f.event.remove(this,b,a&&a.selector?G:F)}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(a,b){if(!f.nodeName(this,"form"))f.event.add(this,"click.specialSubmit",function(a){var b=a.target,c=b.type;(c==="submit"||c==="image")&&f(b).closest("form").length&&K("submit",this,arguments)}),f.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,c=b.type;(c==="text"||c==="password")&&f(b).closest("form").length&&a.keyCode===13&&K("submit",this,arguments)});else return!1},teardown:function(a){f.event.remove(this,".specialSubmit")}});if(!f.support.changeBubbles){var H,I=function(a){var b=a.type,c=a.value;b==="radio"||b==="checkbox"?c=a.checked:b==="select-multiple"?c=a.selectedIndex>-1?f.map(a.options,function(a){return a.selected}).join("-"):"":f.nodeName(a,"select")&&(c=a.selectedIndex);return c},J=function(c){var d=c.target,e,g;if(!!y.test(d.nodeName)&&!d.readOnly){e=f._data(d,"_change_data"),g=I(d),(c.type!=="focusout"||d.type!=="radio")&&f._data(d,"_change_data",g);if(e===b||g===e)return;if(e!=null||g)c.type="change",c.liveFired=b,f.event.trigger(c,arguments[1],d)}};f.event.special.change={filters:{focusout:J,beforedeactivate:J,click:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(c==="radio"||c==="checkbox"||f.nodeName(b,"select"))&&J.call(this,a)},keydown:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(a.keyCode===13&&!f.nodeName(b,"textarea")||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&J.call(this,a)},beforeactivate:function(a){var b=a.target;f._data(b,"_change_data",I(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in H)f.event.add(this,c+".specialChange",H[c]);return y.test(this.nodeName)},teardown:function(a){f.event.remove(this,".specialChange");return y.test(this.nodeName)}},H=f.event.special.change.filters,H.focus=H.beforeactivate}f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){function e(a){var c=f.event.fix(a);c.type=b,c.originalEvent={},f.event.trigger(c,null,c.target),c.isDefaultPrevented()&&a.preventDefault()}var d=0;f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.each(["bind","one"],function(a,c){f.fn[c]=function(a,d,e){var g;if(typeof a=="object"){for(var h in a)this[c](h,d,a[h],e);return this}if(arguments.length===2||d===!1)e=d,d=b;c==="one"?(g=function(a){f(this).unbind(a,g);return e.apply(this,arguments)},g.guid=e.guid||f.guid++):g=e;if(a==="unload"&&c!=="one")this.one(a,d,e);else for(var i=0,j=this.length;i<j;i++)f.event.add(this[i],a,g,d);return this}}),f.fn.extend({unbind:function(a,b){if(typeof a=="object"&&!a.preventDefault)for(var c in a)this.unbind(c,a[c]);else for(var d=0,e=this.length;d<e;d++)f.event.remove(this[d],a,b);return this},delegate:function(a,b,c,d){return this.live(b,c,d,a)},undelegate:function(a,b,c){return arguments.length===0?this.unbind("live"):this.die(b,null,c,a)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f.data(this,"lastToggle"+a.guid)||0)%d;f.data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var L={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};f.each(["live","die"],function(a,c){f.fn[c]=function(a,d,e,g){var h,i=0,j,k,l,m=g||this.selector,n=g?this:f(this.context);if(typeof a=="object"&&!a.preventDefault){for(var o in a)n[c](o,d,a[o],m);return this}if(c==="die"&&!a&&g&&g.charAt(0)==="."){n.unbind(g);return this}if(d===!1||f.isFunction(d))e=d||D,d=b;a=(a||"").split(" ");while((h=a[i++])!=null){j=x.exec(h),k="",j&&(k=j[0],h=h.replace(x,""));if(h==="hover"){a.push("mouseenter"+k,"mouseleave"+k);continue}l=h,L[h]?(a.push(L[h]+k),h=h+k):h=(L[h]||h)+k;if(c==="live")for(var p=0,q=n.length;p<q;p++)f.event.add(n[p],"live."+N(h,m),{data:d,selector:m,handler:e,origType:h,origHandler:e,preType:l});else n.unbind("live."+N(h,m),e)}return this}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.bind(b,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0)}),function(){function u(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}if(i.nodeType===1){f||(i.sizcache=c,i.sizset=g);if(typeof b!="string"){if(i===b){j=!0;break}}else if(k.filter(b,[i]).length>0){j=i;break}}i=i[a]}d[g]=j}}}function t(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}i.nodeType===1&&!f&&(i.sizcache=c,i.sizset=g);if(i.nodeName.toLowerCase()===b){j=i;break}i=i[a]}d[g]=j}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d=0,e=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,f,g){f=f||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return f;var i,j,n,o,q,r,s,t,u=!0,w=k.isXML(d),x=[],y=b;do{a.exec(""),i=a.exec(y);if(i){y=i[3],x.push(i[1]);if(i[2]){o=i[3];break}}}while(i);if(x.length>1&&m.exec(b))if(x.length===2&&l.relative[x[0]])j=v(x[0]+x[1],d);else{j=l.relative[x[0]]?[d]:k(x.shift(),d);while(x.length)b=x.shift(),l.relative[b]&&(b+=x.shift()),j=v(b,j)}else{!g&&x.length>1&&d.nodeType===9&&!w&&l.match.ID.test(x[0])&&!l.match.ID.test(x[x.length-1])&&(q=k.find(x.shift(),d,w),d=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:p(g)}:k.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),j=q.expr?k.filter(q.expr,q.set):q.set,x.length>0?n=p(j):u=!1;while(x.length)r=x.pop(),s=r,l.relative[r]?s=x.pop():r="",s==null&&(s=d),l.relative[r](n,s,w)}else n=x=[]}n||(n=j),n||k.error(r||b);if(e.call(n)==="[object Array]")if(!u)f.push.apply(f,n);else if(d&&d.nodeType===1)for(t=0;n[t]!=null;t++)n[t]&&(n[t]===!0||n[t].nodeType===1&&k.contains(d,n[t]))&&f.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&f.push(j[t]);else p(n,f);o&&(k(o,h,f,g),k.uniqueSort(f));return f};k.uniqueSort=function(a){if(r){g=h,a.sort(r);if(g)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},k.matches=function(a,b){return k(a,null,null,b)},k.matchesSelector=function(a,b){return k(b,null,null,[a]).length>0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e<f;e++){var g,h=l.order[e];if(g=l.leftMatch[h].exec(a)){var j=g[1];g.splice(1,1);if(j.substr(j.length-1)!=="\\"){g[1]=(g[1]||"").replace(i,""),d=l.find[h](g,b,c);if(d!=null){a=a.replace(l.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},k.filter=function(a,c,d,e){var f,g,h=a,i=[],j=c,m=c&&c[0]&&k.isXML(c[0]);while(a&&c.length){for(var n in l.filter)if((f=l.leftMatch[n].exec(a))!=null&&f[2]){var o,p,q=l.filter[n],r=f[1];g=!1,f.splice(1,1);if(r.substr(r.length-1)==="\\")continue;j===i&&(i=[]);if(l.preFilter[n]){f=l.preFilter[n](f,j,d,i,e,m);if(!f)g=o=!0;else if(f===!0)continue}if(f)for(var s=0;(p=j[s])!=null;s++)if(p){o=q(p,f,s,j);var t=e^!!o;d&&o!=null?t?g=!0:j[s]=!1:t&&(i.push(p),g=!0)}if(o!==b){d||(j=i),a=a.replace(l.match[n],"");if(!g)return[];break}}if(a===h)if(g==null)k.error(a);else break;h=a}return j},k.error=function(a){throw"Syntax error, unrecognized expression: "+a};var l=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!j.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&k.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&k.filter(b,a,!0)}},"":function(a,b,c){var e,f=d++,g=u;typeof b=="string"&&!j.test(b)&&(b=b.toLowerCase(),e=b,g=t),g("parentNode",b,f,a,e,c)},"~":function(a,b,c){var e,f=d++,g=u;typeof b=="string"&&!j.test(b)&&(b=b.toLowerCase(),e=b,g=t),g("previousSibling",b,f,a,e,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(i,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(i,"")},TAG:function(a,b){return a[1].replace(i,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||k.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&k.error(a[0]);a[0]=d++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(i,"");!f&&l.attrMap[g]&&(a[1]=l.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(i,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=k(b[3],null,null,c);else{var g=k.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(l.match.POS.test(b[0])||l.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!k(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=l.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||k.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}k.error(e)},CHILD:function(a,b){var c=b[1],d=a;switch(c){case"only":case"first":while(d=d.previousSibling)if(d.nodeType===1)return!1;if(c==="first")return!0;d=a;case"last":while(d=d.nextSibling)if(d.nodeType===1)return!1;return!0;case"nth":var e=b[2],f=b[3];if(e===1&&f===0)return!0;var g=b[0],h=a.parentNode;if(h&&(h.sizcache!==g||!a.nodeIndex)){var i=0;for(d=h.firstChild;d;d=d.nextSibling)d.nodeType===1&&(d.nodeIndex=++i);h.sizcache=g}var j=a.nodeIndex-f;return e===0?j===0:j%e===0&&j/e>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=l.attrHandle[c]?l.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=l.setFilters[e];if(f)return f(a,c,b,d)}}},m=l.match.POS,n=function(a,b){return"\\"+(b-0+1)};for(var o in l.match)l.match[o]=new RegExp(l.match[o].source+/(?![^\[]*\])(?![^\(]*\))/.source),l.leftMatch[o]=new RegExp(/(^(?:.|\r|\n)*?)/.source+l.match[o].source.replace(/\\(\d+)/g,n));var p=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(q){p=function(a,b){var c=0,d=b||[];if(e.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var f=a.length;c<f;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var r,s;c.documentElement.compareDocumentPosition?r=function(a,b){if(a===b){g=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(r=function(a,b){if(a===b){g=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],h=a.parentNode,i=b.parentNode,j=h;if(h===i)return s(a,b);if(!h)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return s(e[k],f[k]);return k===c?s(a,f[k],-1):s(e[k],b,1)},s=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),k.getText=function(a){var b="",c;for(var d=0;a[d];d++)c=a[d],c.nodeType===3||c.nodeType===4?b+=c.nodeValue:c.nodeType!==8&&(b+=k.getText(c.childNodes));return b},function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(l.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},l.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(l.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(l.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=k,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){k=function(b,e,f,g){e=e||c;if(!g&&!k.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return p(e.getElementsByTagName(b),f);if(h[2]&&l.find.CLASS&&e.getElementsByClassName)return p(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return p([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return p([],f);if(i.id===h[3])return p([i],f)}try{return p(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e,n=e.getAttribute("id"),o=n||d,q=e.parentNode,r=/^\s*[+~]/.test(b);n?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),r&&q&&(e=e.parentNode);try{if(!r||q)return p(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(s){}finally{n||m.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)k[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(e||!l.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return k(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;l.order.splice(1,0,"CLASS"),l.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?k.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?k.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:k.contains=function(){return!1},k.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var v=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=l.match.PSEUDO.exec(a))e+=c[0],a=a.replace(l.match.PSEUDO,"");a=l.relative[a]?a+"*":a;for(var g=0,h=f.length;g<h;g++)k(a,f[g],d);return k.filter(e,d)};f.find=k,f.expr=k.selectors,f.expr[":"]=f.expr.filters,f.unique=k.uniqueSort,f.text=k.getText,f.isXMLDoc=k.isXML,f.contains=k.contains}();var O=/Until$/,P=/^(?:parents|prevUntil|prevAll)/,Q=/,/,R=/^.[^:#\[\.,]*$/,S=Array.prototype.slice,T=f.expr.match.POS,U={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(W(this,a,!1),"not",a)},filter:function(a){return this.pushStack(W(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(d=0,e=a.length;d<e;d++)i=a[d],j[i]||(j[i]=T.test(i)?f(i,b||this.context):i);while(g&&g.ownerDocument&&g!==b){for(i in j)h=j[i],(h.jquery?h.index(g)>-1:f(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=T.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(l?l.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a||typeof a=="string")return f.inArray(this[0],a?f(a):this.parent().children());return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(V(c[0])||V(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c),g=S.call(arguments);O.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!U[a]?f.unique(e):e,(this.length>1||Q.test(d))&&P.test(a)&&(e=e.reverse());return this.pushStack(e,a,g.join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var X=/ jQuery\d+="(?:\d+|null)"/g,Y=/^\s+/,Z=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,$=/<([\w:]+)/,_=/<tbody/i,ba=/<|&#?\w+;/,bb=/<(?:script|object|embed|option|style)/i,bc=/checked\s*(?:[^=]|=\s*.checked.)/i,bd=/\/(java|ecma)script/i,be=/^\s*<!(?:\[CDATA\[|\-\-)/,bf={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};bf.optgroup=bf.option,bf.tbody=bf.tfoot=bf.colgroup=bf.caption=bf.thead,bf.th=bf.td,f.support.htmlSerialize||(bf._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){f(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(X,""):null;if(typeof a=="string"&&!bb.test(a)&&(f.support.leadingWhitespace||!Y.test(a))&&!bf[($.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Z,"<$1></$2>");try{for(var c=0,d=this.length;c<d;c++)this[c].nodeType===1&&(f.cleanData(this[c].getElementsByTagName("*")),this[c].innerHTML=a)}catch(e){this.empty().append(a)}}else f.isFunction(a)?this.each(function(b){var c=f(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bc.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bg(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,bm)}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i;b&&b[0]&&(i=b[0].ownerDocument||b[0]),i.createDocumentFragment||(i=c),a.length===1&&typeof a[0]=="string"&&a[0].length<512&&i===c&&a[0].charAt(0)==="<"&&!bb.test(a[0])&&(f.support.checkClone||!bc.test(a[0]))&&(g=!0,h=f.fragments[a[0]],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[a[0]]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j -)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d=a.cloneNode(!0),e,g,h;if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bi(a,d),e=bj(a),g=bj(d);for(h=0;e[h];++h)bi(e[h],g[h])}if(b){bh(a,d);if(c){e=bj(a),g=bj(d);for(h=0;e[h];++h)bh(e[h],g[h])}}e=g=null;return d},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!ba.test(k))k=b.createTextNode(k);else{k=k.replace(Z,"<$1></$2>");var l=($.exec(k)||["",""])[1].toLowerCase(),m=bf[l]||bf._default,n=m[0],o=b.createElement("div");o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=_.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]==="<table>"&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&Y.test(k)&&o.insertBefore(b.createTextNode(Y.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i<r;i++)bl(k[i]);else bl(k);k.nodeType?h.push(k):h=f.merge(h,k)}if(d){g=function(a){return!a.type||bd.test(a.type)};for(j=0;h[j];j++)if(e&&f.nodeName(h[j],"script")&&(!h[j].type||h[j].type.toLowerCase()==="text/javascript"))e.push(h[j].parentNode?h[j].parentNode.removeChild(h[j]):h[j]);else{if(h[j].nodeType===1){var s=f.grep(h[j].getElementsByTagName("script"),g);h.splice.apply(h,[j+1,0].concat(s))}d.appendChild(h[j])}}return h},cleanData:function(a){var b,c,d=f.cache,e=f.expando,g=f.event.special,h=f.support.deleteExpando;for(var i=0,j;(j=a[i])!=null;i++){if(j.nodeName&&f.noData[j.nodeName.toLowerCase()])continue;c=j[f.expando];if(c){b=d[c]&&d[c][e];if(b&&b.events){for(var k in b.events)g[k]?f.event.remove(j,k):f.removeEvent(j,k,b.handle);b.handle&&(b.handle.elem=null)}h?delete j[f.expando]:j.removeAttribute&&j.removeAttribute(f.expando),delete d[c]}}}});var bn=/alpha\([^)]*\)/i,bo=/opacity=([^)]*)/,bp=/([A-Z]|^ms)/g,bq=/^-?\d+(?:px)?$/i,br=/^-?\d/,bs=/^[+\-]=/,bt=/[^+\-\.\de]+/g,bu={position:"absolute",visibility:"hidden",display:"block"},bv=["Left","Right"],bw=["Top","Bottom"],bx,by,bz;f.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return f.access(this,a,c,!0,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)})},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bx(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d;if(h==="number"&&isNaN(d)||d==null)return;h==="string"&&bs.test(d)&&(d=+d.replace(bt,"")+parseFloat(f.css(a,c)),h="number"),h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(bx)return bx(a,c)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]}}),f.curCSS=f.css,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){var e;if(c){if(a.offsetWidth!==0)return bA(a,b,d);f.swap(a,bu,function(){e=bA(a,b,d)});return e}},set:function(a,b){if(!bq.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bo.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle;c.zoom=1;var e=f.isNaN(b)?"":"alpha(opacity="+b*100+")",g=d&&d.filter||c.filter||"";c.filter=bn.test(g)?g.replace(bn,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bx(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(by=function(a,c){var d,e,g;c=c.replace(bp,"-$1").toLowerCase();if(!(e=a.ownerDocument.defaultView))return b;if(g=e.getComputedStyle(a,null))d=g.getPropertyValue(c),d===""&&!f.contains(a.ownerDocument.documentElement,a)&&(d=f.style(a,c));return d}),c.documentElement.currentStyle&&(bz=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bq.test(d)&&br.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bx=by||bz,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bB=/%20/g,bC=/\[\]$/,bD=/\r?\n/g,bE=/#.*$/,bF=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bG=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bH=/^(?:about|app|app\-storage|.+\-extension|file|widget):$/,bI=/^(?:GET|HEAD)$/,bJ=/^\/\//,bK=/\?/,bL=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bM=/^(?:select|textarea)/i,bN=/\s+/,bO=/([?&])_=[^&]*/,bP=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bQ=f.fn.load,bR={},bS={},bT,bU;try{bT=e.href}catch(bV){bT=c.createElement("a"),bT.href="",bT=bT.href}bU=bP.exec(bT.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bQ)return bQ.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bL,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bM.test(this.nodeName)||bG.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bD,"\r\n")}}):{name:b.name,value:c.replace(bD,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.bind(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?f.extend(!0,a,f.ajaxSettings,b):(b=a,a=f.extend(!0,f.ajaxSettings,b));for(var c in{context:1,url:1})c in b?a[c]=b[c]:c in f.ajaxSettings&&(a[c]=f.ajaxSettings[c]);return a},ajaxSettings:{url:bT,isLocal:bH.test(bU[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":"*/*"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML}},ajaxPrefilter:bW(bR),ajaxTransport:bW(bS),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a?4:0;var o,r,u,w=l?bZ(d,v,l):b,x,y;if(a>=200&&a<300||a===304){if(d.ifModified){if(x=v.getResponseHeader("Last-Modified"))f.lastModified[k]=x;if(y=v.getResponseHeader("Etag"))f.etag[k]=y}if(a===304)c="notmodified",o=!0;else try{r=b$(d,w),c="success",o=!0}catch(z){c="parsererror",u=z}}else{u=c;if(!c||a)c="error",a<0&&(a=0)}v.status=a,v.statusText=c,o?h.resolveWith(e,[r,c,v]):h.rejectWith(e,[v,c,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.resolveWith(e,[v,c]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f._Deferred(),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bF.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.done,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bE,"").replace(bJ,bU[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bN),d.crossDomain==null&&(r=bP.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bU[1]&&r[2]==bU[2]&&(r[3]||(r[1]==="http:"?80:443))==(bU[3]||(bU[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bX(bR,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bI.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bK.test(d.url)?"&":"?")+d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bO,"$1_="+x);d.url=y+(y===d.url?(bK.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", */*; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bX(bS,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){status<2?w(-1,z):f.error(z)}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)bY(g,a[g],c,e);return d.join("&").replace(bB,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var b_=f.now(),ca=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+b_++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ca.test(b.url)||e&&ca.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ca,l),b.url===j&&(e&&(k=k.replace(ca,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cb=a.ActiveXObject?function(){for(var a in cd)cd[a](0,1)}:!1,cc=0,cd;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ce()||cf()}:ce,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cb&&delete cd[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cc,cb&&(cd||(cd={},f(a).unload(cb)),cd[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cg={},ch,ci,cj=/^(?:toggle|show|hide)$/,ck=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cl,cm=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cn,co=a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cr("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),e===""&&f.css(d,"display")==="none"&&f._data(d,"olddisplay",cs(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(cr("hide",3),a,b,c);for(var d=0,e=this.length;d<e;d++)if(this[d].style){var g=f.css(this[d],"display");g!=="none"&&!f._data(this[d],"olddisplay")&&f._data(this[d],"olddisplay",g)}for(d=0;d<e;d++)this[d].style&&(this[d].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(cr("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return this[e.queue===!1?"each":"queue"](function(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]),h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(f.support.inlineBlockNeedsLayout?(j=cs(this.nodeName),j==="inline"?this.style.display="inline-block":(this.style.display="inline",this.style.zoom=1)):this.style.display="inline-block"))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)k=new f.fx(this,b,i),h=a[i],cj.test(h)?k[h==="toggle"?d?"show":"hide":h]():(l=ck.exec(h),m=k.cur(),l?(n=parseFloat(l[2]),o=l[3]||(f.cssNumber[i]?"":"px"),o!=="px"&&(f.style(this,i,(n||1)+o),m=(n||1)/k.cur()*m,f.style(this,i,m+o)),l[1]&&(n=(l[1]==="-="?-1:1)*n+m),k.custom(m,n,o)):k.custom(m,h,""));return!0})},stop:function(a,b){a&&this.queue([]),this.each(function(){var a=f.timers,c=a.length;b||f._unmark(!0,this);while(c--)a[c].elem===this&&(b&&a[c](!0),a.splice(c,1))}),b||this.dequeue();return this}}),f.each({slideDown:cr("show",1),slideUp:cr("hide",1),slideToggle:cr("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default,d.old=d.complete,d.complete=function(a){f.isFunction(d.old)&&d.old.call(this),d.queue!==!1?f.dequeue(this):a!==!1&&f._unmark(this)};return d},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,b,c){function h(a){return d.step(a)}var d=this,e=f.fx,g;this.startTime=cn||cp(),this.start=a,this.end=b,this.unit=c||this.unit||(f.cssNumber[this.prop]?"":"px"),this.now=this.start,this.pos=this.state=0,h.elem=this.elem,h()&&f.timers.push(h)&&!cl&&(co?(cl=!0,g=function(){cl&&(co(g),e.tick())},co(g)):cl=setInterval(e.tick,e.interval))},show:function(){this.options.orig[this.prop]=f.style(this.elem,this.prop),this.options.show=!0,this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b=cn||cp(),c=!0,d=this.elem,e=this.options,g,h;if(a||b>=e.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),e.animatedProperties[this.prop]=!0;for(g in e.animatedProperties)e.animatedProperties[g]!==!0&&(c=!1);if(c){e.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){d.style["overflow"+b]=e.overflow[a]}),e.hide&&f(d).hide();if(e.hide||e.show)for(var i in e.animatedProperties)f.style(d,i,e.orig[i]);e.complete.call(d)}return!1}e.duration==Infinity?this.now=b:(h=b-this.startTime,this.state=h/e.duration,this.pos=f.easing[e.animatedProperties[this.prop]](this.state,h,0,1,e.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){for(var a=f.timers,b=0;b<a.length;++b)a[b]()||a.splice(b--,1);a.length||f.fx.stop()},interval:13,stop:function(){clearInterval(cl),cl=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit:a.elem[a.prop]=a.now}}}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var ct=/^t(?:able|d|h)$/i,cu=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?f.fn.offset=function(a){var b=this[0],c;if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);try{c=b.getBoundingClientRect()}catch(d){}var e=b.ownerDocument,g=e.documentElement;if(!c||!f.contains(g,b))return c?{top:c.top,left:c.left}:{top:0,left:0};var h=e.body,i=cv(e),j=g.clientTop||h.clientTop||0,k=g.clientLeft||h.clientLeft||0,l=i.pageYOffset||f.support.boxModel&&g.scrollTop||h.scrollTop,m=i.pageXOffset||f.support.boxModel&&g.scrollLeft||h.scrollLeft,n=c.top+l-j,o=c.left+m-k;return{top:n,left:o}}:f.fn.offset=function(a){var b=this[0];if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);f.offset.initialize();var c,d=b.offsetParent,e=b,g=b.ownerDocument,h=g.documentElement,i=g.body,j=g.defaultView,k=j?j.getComputedStyle(b,null):b.currentStyle,l=b.offsetTop,m=b.offsetLeft;while((b=b.parentNode)&&b!==i&&b!==h){if(f.offset.supportsFixedPosition&&k.position==="fixed")break;c=j?j.getComputedStyle(b,null):b.currentStyle,l-=b.scrollTop,m-=b.scrollLeft,b===d&&(l+=b.offsetTop,m+=b.offsetLeft,f.offset.doesNotAddBorder&&(!f.offset.doesAddBorderForTableAndCells||!ct.test(b.nodeName))&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),e=d,d=b.offsetParent),f.offset.subtractsBorderForOverflowNotVisible&&c.overflow!=="visible"&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),k=c}if(k.position==="relative"||k.position==="static")l+=i.offsetTop,m+=i.offsetLeft;f.offset.supportsFixedPosition&&k.position==="fixed"&&(l+=Math.max(h.scrollTop,i.scrollTop),m+=Math.max(h.scrollLeft,i.scrollLeft));return{top:l,left:m}},f.offset={initialize:function(){var a=c.body,b=c.createElement("div"),d,e,g,h,i=parseFloat(f.css(a,"marginTop"))||0,j="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";f.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),d=b.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,this.doesNotAddBorder=e.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,e.style.position="fixed",e.style.top="20px",this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),f.offset.initialize=f.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.offset.initialize(),f.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cu.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cu.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cv(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cv(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a&&a.style?parseFloat(f.css(a,d,"padding")):null},f.fn["outer"+c]=function(a){var b=this[0];return b&&b.style?parseFloat(f.css(b,d,a?"margin":"border")):null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c];return e.document.compatMode==="CSS1Compat"&&g||e.document.body["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var h=f.css(e,d),i=parseFloat(h);return f.isNaN(i)?h:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f})(window); \ No newline at end of file diff --git a/setup/scripts/rcron.php b/setup/scripts/rcron.php index 53c2da007f91ab84007afcc20409b32a8da822ed..a15a152ec910104d157faa2b9ab6971a1b7361bd 100755 --- a/setup/scripts/rcron.php +++ b/setup/scripts/rcron.php @@ -16,12 +16,12 @@ **********************************************************************/ # Configuration: Enter the url and key. That is it. -# url => URL to api/task/cron e.g http://yourdomain.com/support/api/task/cron +# url => URL to api/task/cron e.g http://yourdomain.com/support/api/tasks/cron # key => API's Key (see admin panel on how to generate a key) # $config = array( - 'url'=>'http://yourdomain.com/support/api/task/cron', + 'url'=>'http://yourdomain.com/support/api/tasks/cron', 'key'=>'API KEY HERE' ); @@ -32,8 +32,8 @@ function_exists('curl_version') or die('CURL support required'); set_time_limit(30); #curl post -$ch = curl_init(); -curl_setopt($ch, CURLOPT_URL, $config['url']); +$ch = curl_init(); +curl_setopt($ch, CURLOPT_URL, $config['url']); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, ''); curl_setopt($ch, CURLOPT_USERAGENT, 'osTicket API Client v1.7'); @@ -41,7 +41,7 @@ curl_setopt($ch, CURLOPT_HEADER, TRUE); curl_setopt($ch, CURLOPT_HTTPHEADER, array( 'Expect:', 'X-API-Key: '.$config['key'])); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, FALSE); curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); -$result=curl_exec($ch); +$result=curl_exec($ch); curl_close($ch); if(preg_match('/HTTP\/.* ([0-9]+) .*/', $result, $status) && $status[1] == 200) diff --git a/setup/setup.inc.php b/setup/setup.inc.php index 52b7563a51f9b545c5d58da094da4fd65a09a8ef..ab75b7219ddc69b482ef56fb6a7c2de998fc438c 100644 --- a/setup/setup.inc.php +++ b/setup/setup.inc.php @@ -15,7 +15,7 @@ **********************************************************************/ #This version - changed on every release -define('THIS_VERSION', '1.7.0'); +define('THIS_VERSION', '1.7.0+'); #inits - error reporting. $error_reporting = E_ALL & ~E_NOTICE; @@ -51,10 +51,10 @@ define('SETUPINC',true); define('URL',rtrim('http'.(($_SERVER['HTTPS']=='on')?'s':'').'://'.$_SERVER['HTTP_HOST'].dirname($_SERVER['PHP_SELF']),'setup')); #define paths -define('INC_DIR','./inc/'); //local include dir! +define('INC_DIR',dirname(__file__).'/inc/'); //local include dir! if(!defined('INCLUDE_DIR')): -define('ROOT_PATH','../'); -define('ROOT_DIR','../'); +define('ROOT_PATH',dirname(__file__).'/../'); +define('ROOT_DIR',dirname(__file__).'/../'); define('INCLUDE_DIR',ROOT_DIR.'include/'); define('PEAR_DIR',INCLUDE_DIR.'pear/'); ini_set('include_path', './'.PATH_SEPARATOR.INC_DIR.PATH_SEPARATOR.INCLUDE_DIR.PATH_SEPARATOR.PEAR_DIR); @@ -66,5 +66,10 @@ require_once(INCLUDE_DIR.'class.validator.php'); require_once(INCLUDE_DIR.'class.passwd.php'); require_once(INCLUDE_DIR.'class.format.php'); require_once(INCLUDE_DIR.'class.misc.php'); -require_once(INCLUDE_DIR.'mysql.php'); + +if (extension_loaded('mysqli')) + require_once INCLUDE_DIR.'mysqli.php'; +else + require(INCLUDE_DIR.'mysql.php'); + ?> diff --git a/setup/test/lint.php b/setup/test/lint.php deleted file mode 100644 index 4887856182e23d3acfed19168a92dbc744f4e9e8..0000000000000000000000000000000000000000 --- a/setup/test/lint.php +++ /dev/null @@ -1,140 +0,0 @@ -#!/usr/bin/env php -<?php -if (php_sapi_name() != 'cli') exit(); - -function get_osticket_root_path() { - # Hop up to the root folder - $start = dirname(__file__); - for (;;) { - if (file_exists($start . '/main.inc.php')) break; - $start .= '/..'; - } - return realpath($start); -} - -$root = get_osticket_root_path(); - -# Check PHP syntax across all php files -function glob_recursive($pattern, $flags = 0) { - $files = glob($pattern, $flags); - foreach (glob(dirname($pattern).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir) { - $files = array_merge($files, - glob_recursive($dir.'/'.basename($pattern), $flags)); - } - return $files; -} -echo "PHP Syntax Errors: "; -ob_start(); -$scripts=glob_recursive("$root/*.php"); -$exit=0; -$syntax_errors=""; -foreach ($scripts as $s) { - system("php -l $s", $exit); - $line = ob_get_contents(); - ob_clean(); - if ($exit !== 0) - $syntax_errors .= $line; -} -ob_end_clean(); - -if (strlen($syntax_errors)) { - $syntax_errors=str_replace("$root/", '', $syntax_errors); - echo "FAIL\n"; - echo "-------------------------------------------------------\n"; - echo "$syntax_errors"; - exit(); -} else { - echo "pass\n"; -} - -function line_number_for_offset($filename, $offset) { - $lines = file($filename); - $bytes = $line = 0; - while ($bytes < $offset) { - $bytes += strlen(array_shift($lines)); - $line += 1; - } - return $line; -} -echo "Short open tags: "; -$fails = array(); -foreach ($scripts as $s) { - $matches = array(); - if (preg_match_all('/<\?\s*(?!php|xml).*$/m', file_get_contents($s), $matches, - PREG_OFFSET_CAPTURE) > 0) { - foreach ($matches[0] as $match) - $fails[] = array( - str_replace($root.'/', '', $s), - $match[0], - line_number_for_offset($s, $match[1])); - } -} -if (count($fails)) { - echo "FAIL\n"; - echo "-------------------------------------------------------\n"; - foreach ($fails as $f) - echo sprintf("In %s, line %d: %s\n", $f[0], $f[2], - str_replace("\n", " ", $f[1])); - echo "\n"; -} else { - echo "pass\n"; -} - -# Run phplint across all php files -echo "Access to unitialized variables: "; -ob_start(); -# XXX: This won't run well on Windoze -system("$root/setup/test/lib/phplint.tcl ".implode(" ", $scripts)); -$lint_errors = ob_get_clean(); - -if (strlen($lint_errors)) { - $lint_errors=str_replace("$root/", '', $lint_errors); - echo "FAIL\n"; - echo "-------------------------------------------------------\n"; - echo "$lint_errors"; -} else { - echo "\n"; -} - -function find_function_calls($scripts) { - $calls=array(); - foreach ($scripts as $s) { - $lines = explode("\n", file_get_contents($s)); - $lineno=0; - foreach (explode("\n", file_get_contents($s)) as $line) { - $lineno++; $matches=array(); - preg_match_all('/-[>]([a-zA-Z0-9]*)\(/', $line, $matches, - PREG_SET_ORDER); - foreach ($matches as $m) { - $calls[] = array($s, $lineno, $line, $m[1]); - } - } - } - return $calls; -} - -$php_script_content=''; -foreach ($scripts as $s) { - $php_script_content .= file_get_contents($s); -} -echo "Access to undefined object methods: "; -ob_start(); -foreach (find_function_calls($scripts) as $call) { - list($file, $no, $line, $func) = $call; - if (!preg_match('/^\s*(\/\*[^*]*\*\/)?'."\s*function\s+&?\s*$func\\(/m", - $php_script_content)) { - print "$func: Definitely undefined, from $file:$no\n"; - } -} -$undef_func_errors = ob_get_clean(); - -if (strlen($undef_func_errors)) { - $undef_func_errors=str_replace("$root/", '', $undef_func_errors); - echo "FAIL\n"; - echo "-------------------------------------------------------\n"; - echo "$undef_func_errors"; - exit(); -} else { - echo "\n"; -} -?> diff --git a/setup/test/run-tests.php b/setup/test/run-tests.php new file mode 100644 index 0000000000000000000000000000000000000000..0f42dd7d024f04bf03d87a8b5611a3b658c6953e --- /dev/null +++ b/setup/test/run-tests.php @@ -0,0 +1,67 @@ +#!/usr/bin/env php +<?php +if (php_sapi_name() != 'cli') exit(); + +require_once "tests/class.test.php"; + +function get_osticket_root_path() { + # Hop up to the root folder + $start = dirname(__file__); + for (;;) { + if (file_exists($start . '/main.inc.php')) break; + $start .= '/..'; + } + return realpath($start); +} +$root = get_osticket_root_path(); + +# Check PHP syntax across all php files +function glob_recursive($pattern, $flags = 0) { + $files = glob($pattern, $flags); + foreach (glob(dirname($pattern).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir) { + $files = array_merge($files, + glob_recursive($dir.'/'.basename($pattern), $flags)); + } + return $files; +} + +$fails = array(); + +function show_fails() { + global $fails, $root; + if ($fails) { + echo count($fails) . " FAIL(s)\n"; + echo "-------------------------------------------------------\n"; + foreach ($fails as $f) { + list($test, $script, $line, $message) = $f; + $script = str_replace($root.'/', '', $script); + print("$test: $message @ $script:$line\n"); + } + } +} +if (function_exists('pcntl_signal')) { + declare(ticks=1); + function show_fails_on_ctrlc() { + while (@ob_end_flush()); + print("\n"); + show_fails(); + exit(); + } + pcntl_signal(SIGINT, 'show_fails_on_ctrlc'); +} + +foreach (glob_recursive(dirname(__file__)."/tests/test.*.php") as $t) { + if (strpos($t,"class.") !== false) + continue; + $class = (include $t); + if (!is_string($class)) + continue; + $test = new $class(); + echo "Running: " . $test->name . "\n"; + $test->run(); + $fails = array_merge($fails, $test->fails); + echo " ok\n"; +} +show_fails(); + +?> diff --git a/setup/test/tests/class.test.php b/setup/test/tests/class.test.php new file mode 100644 index 0000000000000000000000000000000000000000..80a07b87b31acf34126712a132139c5c55c2f3ac --- /dev/null +++ b/setup/test/tests/class.test.php @@ -0,0 +1,103 @@ +<?php + +class Test { + var $fails = array(); + var $name = ""; + + var $third_party_paths = array( + '/include/JSON.php', + '/include/htmLawed.php', + '/include/PasswordHash.php', + '/include/pear/', + '/include/Spyc.php', + ); + + function Test() { + call_user_func_array(array($this, '__construct'), func_get_args()); + } + function __construct() { + assert_options(ASSERT_CALLBACK, array($this, 'fail')); + error_reporting(E_ALL & ~E_WARNING); + } + + function setup() { + } + + function teardown() { + } + + /*static*/ + function getAllScripts($excludes=true) { + $root = get_osticket_root_path(); + $scripts = array(); + foreach (glob_recursive("$root/*.php") as $s) { + $found = false; + if ($excludes) { + foreach ($this->third_party_paths as $p) { + if (strpos($s, $p) !== false) { + $found = true; + break; + } + } + } + if (!$found) + $scripts[] = $s; + } + return $scripts; + } + + function fail($script, $line, $message) { + $this->fails[] = array(get_class($this), $script, $line, $message); + fputs(STDOUT, 'F'); + } + + function pass() { + fputs(STDOUT, "."); + } + + function warn($message) { + $this->fails[] = array(get_class($this), '', '', 'WARNING: '.$message); + fputs(STDOUT, 'w'); + } + + function assert($expr, $message) { + if ($expr) + $this->pass(); + else + $this->fail('', '', $message); + } + + function assertEqual($a, $b, $message=false) { + if (!$message) + $message = "Assertion: {$a} != {$b}"; + return $this->assert($a == $b, $message); + } + + function assertNotEqual($a, $b, $message=false) { + if (!$message) + $message = "Assertion: {$a} == {$b}"; + return $this->assert($a != $b, $message); + } + + function run() { + $rc = new ReflectionClass(get_class($this)); + foreach ($rc->getMethods() as $m) { + if (stripos($m->name, 'test') === 0) { + $this->setup(); + call_user_func(array($this, $m->name)); + $this->teardown(); + } + } + } + + function line_number_for_offset($filename, $offset) { + $lines = file($filename); + $bytes = $line = 0; + while ($bytes < $offset) { + $bytes += strlen(array_shift($lines)); + $line += 1; + } + return $line; + } +} +?> diff --git a/setup/test/lib/phplint.tcl b/setup/test/tests/lib/phplint.tcl similarity index 100% rename from setup/test/lib/phplint.tcl rename to setup/test/tests/lib/phplint.tcl diff --git a/setup/test/tests/test.crypto.php b/setup/test/tests/test.crypto.php new file mode 100644 index 0000000000000000000000000000000000000000..e7274118e08fcd160a1171157aaa6f6260456969 --- /dev/null +++ b/setup/test/tests/test.crypto.php @@ -0,0 +1,93 @@ +<?php +require_once "class.test.php"; +define('INCLUDE_DIR', realpath(dirname(__file__).'/../../../include').'/'); +define('PEAR_DIR', INCLUDE_DIR.'/pear/'); +require_once INCLUDE_DIR."class.crypto.php"; + +class TestCrypto extends Test { + var $name = "Crypto library tests"; + + var $test_data = 'supercalifragilisticexpialidocious'; # notrans + var $master = 'master'; # notrans + + var $passwords = array( + 'english-password', + 'CaseSensitive Password', + '«ταБЬℓσ»', + 'Ù©(-̮̮̃-̃)Û¶ Ù©(â—̮̮̃•̃)Û¶ Ù©(Í¡à¹Ì¯Í¡à¹)Û¶ Ù©(-̮̮̃•̃).', + 'å‘åŒè®²è¯´å®…电的手机告的世全所回广讲说跟', + ); + + function testSimple() { + $tests = array_merge(array($this->test_data), $this->passwords); + foreach ($tests as $subject) { + $enc = Crypto::encrypt($subject, $this->master, 'simple'); + $dec = Crypto::decrypt($enc, $this->master, 'simple'); + $this->assertEqual($dec, $subject, + "{$subject}: Encryption failed closed loop"); + $this->assertNotEqual($enc, $subject, + 'Data was not encrypted'); + $this->assertNotEqual($enc, false, 'Encryption failed'); + $this->assertNotEqual($dec, false, 'Decryption failed'); + + $dec = Crypto::decrypt($enc, $this->master, 'wrong'); + $this->assertNotEqual($dec, $this->test_data, 'Subkeys are broken'); + } + } + + function _testLibrary($c, $tests) { + $name = get_class($c); + foreach ($tests as $id => $subject) { + $dec = $c->decrypt(base64_decode($subject)); + $this->assertEqual($dec, $this->test_data, "$name: decryption incorrect"); + $this->assertNotEqual($dec, false, "$name: decryption FAILED"); + } + $enc = $c->encrypt($this->test_data); + $this->assertNotEqual($enc, $this->test_data, + "$name: encryption cheaped out"); + $this->assertNotEqual($enc, false, "$name: encryption failed"); + + $c->setKeys($this->master, 'wrong'); + $dec = $c->decrypt(base64_decode($subject)); + $this->assertEqual($dec, false, "$name: Subkeys are broken"); + + } + + function testMcrypt() { + $tests = array( + 1 => 'JDEkIEDoeaSiOUEGE5KQ3UmJpQ5+pUaX91HyLMG58GmNU9pZXAdiXXJsfl+7TSDlLczGD98UCD6tLuDIwI9XJLEwew==', + ); + if (!CryptoMcrypt::exists()) + return $this->warn('Not testing mcrypt encryption'); + + $c = new CryptoMcrypt(0); + $c->setKeys($this->master, 'simple'); + $this->_testLibrary($c, $tests); + } + + function testOpenSSL() { + $tests = array( + 1 => 'JDEkRiLtWBgRN68kJjp4jsM6xKJY+XFYwMeaQIHJXKW8v3fEZzs3gCq3hKevgvAjvdgwx5ZUYLFPsFehLtkzAw8IlQ==', + ); + if (!CryptoOpenSSL::exists()) + return $this->warn('Not testing openssl encryption'); + + $c = new CryptoOpenSSL(0); + $c->setKeys($this->master, 'simple'); + $this->_testLibrary($c, $tests); + } + + function testPHPSecLib() { + $tests = array( + 1 => 'JDEkvH/es2Drdsmc8pU2UBnBxhiPavtvst2Sl9jOYVXTRjHsgPmv8+8mIgwnA1nQ6EI2AoTq2gMZtoBoqK3Mzpw8IQ==', + ); + if (!CryptoPHPSecLib::exists()) + return $this->warn('Not testing PHPSecLib encryption'); + + $c = new CryptoPHPSecLib(0); + $c->setKeys($this->master, 'simple'); + $this->_testLibrary($c, $tests); + } +} +return 'TestCrypto'; +?> diff --git a/setup/test/tests/test.extra-whitespace.php b/setup/test/tests/test.extra-whitespace.php new file mode 100644 index 0000000000000000000000000000000000000000..96d31ac8425e40a38e66ff90f7a59e9717697bb9 --- /dev/null +++ b/setup/test/tests/test.extra-whitespace.php @@ -0,0 +1,26 @@ +<?php +require_once "class.test.php"; + +class ExtraWhitespace extends Test { + var $name = "PHP Leading and Trailing Whitespace"; + + function testFindWhitespace() { + foreach ($this->getAllScripts() as $s) { + $matches = array(); + if (preg_match_all('/^\s+<\?php|\?>\n\s+$/s', + file_get_contents($s), $matches, + PREG_OFFSET_CAPTURE) > 0) { + foreach ($matches[0] as $match) + $this->fail( + $s, $this->line_number_for_offset($s, $match[1]), + (strpos('?>', $match[0]) !== false) + ? 'Leading whitespace' + : 'Trailing whitespace'); + } + else $this->pass(); + } + } +} +return 'ExtraWhitespace'; + +?> diff --git a/setup/test/tests/test.shortopentags.php b/setup/test/tests/test.shortopentags.php new file mode 100644 index 0000000000000000000000000000000000000000..571fc08e15e1c3af5c0c60216eeda7d494a6cfa6 --- /dev/null +++ b/setup/test/tests/test.shortopentags.php @@ -0,0 +1,24 @@ +<?php +require_once "class.test.php"; + +class ShortOpenTag extends Test { + var $name = "PHP Short Open Checks"; + + function testFindShortOpens() { + foreach ($this->getAllScripts() as $s) { + $matches = array(); + if (preg_match_all('/<\?\s*(?!php|xml).*$/m', + file_get_contents($s), $matches, + PREG_OFFSET_CAPTURE) > 0) { + foreach ($matches[0] as $match) + $this->fail( + $s, + $this->line_number_for_offset($s, $match[1]), + $match[0]); + } + else $this->pass(); + } + } +} +return 'ShortOpenTag'; +?> diff --git a/setup/test/tests/test.signals.php b/setup/test/tests/test.signals.php new file mode 100644 index 0000000000000000000000000000000000000000..7ce888383ab0aee781450f3f908d8c4a36d96ae1 --- /dev/null +++ b/setup/test/tests/test.signals.php @@ -0,0 +1,47 @@ +<?php +require_once "class.test.php"; + +class SignalsTest extends Test { + var $name = "Signals checks"; + + /** + * Ensures that each signal subscribed to has a sender somewhere else + */ + function testFindSignalPublisher() { + $scripts = $this->getAllScripts(); + $matches = $published_signals = array(); + foreach ($scripts as $s) + if (preg_match_all("/^ *Signal::send\('([^']+)'/m", + file_get_contents($s), $matches, PREG_SET_ORDER)) + foreach ($matches as $match) + $published_signals[] = $match[1]; + foreach ($scripts as $s) { + if (preg_match_all("/^ *Signal::connect\('([^']+)'/m", + file_get_contents($s), $matches, + PREG_OFFSET_CAPTURE|PREG_SET_ORDER) > 0) { + foreach ($matches as $match) { + $match = $match[1]; + if (!in_array($match[0], $published_signals)) + $this->fail( + $s, self::line_number_for_offset($s, $match[1]), + "Signal '{$match[0]}' is never sent"); + else + $this->pass(); + } + } + } + } + + function line_number_for_offset($filename, $offset) { + $lines = file($filename); + $bytes = $line = 0; + while ($bytes < $offset) { + $bytes += strlen(array_shift($lines)); + $line += 1; + } + return $line; + } +} + +return 'SignalsTest'; +?> diff --git a/setup/test/tests/test.syntax.php b/setup/test/tests/test.syntax.php new file mode 100644 index 0000000000000000000000000000000000000000..774ed964e466a20ce63b352fe7792d68769513d1 --- /dev/null +++ b/setup/test/tests/test.syntax.php @@ -0,0 +1,23 @@ +<?php +require_once "class.test.php"; + +class SyntaxTest extends Test { + var $name = "PHP Syntax Checks"; + + function testCompileErrors() { + $exit = 0; + foreach ($this->getAllScripts() as $s) { + ob_start(); + system("php -l $s", $exit); + $line = ob_get_contents(); + ob_end_clean(); + if ($exit != 0) + $this->fail($s, 0, $line); + else + $this->pass(); + } + } +} + +return 'SyntaxTest'; +?> diff --git a/setup/test/tests/test.undefinedmethods.php b/setup/test/tests/test.undefinedmethods.php new file mode 100644 index 0000000000000000000000000000000000000000..4d22d1aafed62fcfe6d1b68dc985a03db6020343 --- /dev/null +++ b/setup/test/tests/test.undefinedmethods.php @@ -0,0 +1,46 @@ +<?php +require_once "class.test.php"; + +class UndefinedMethods extends Test { + var $name = "Access to undefined object methods"; + + function testFindShortOpen() { + $scripts = $this->getAllScripts(false); + $function_defs = array(); + foreach ($scripts as $s) { + $matches = array(); + preg_match_all('/^\s*(?:\/\*[^*]*\*\/)?\s*' + .'(?:(?:private|public|protected|static|abstract)\s+)*' + .'function\s+&?\s*([^(\s]+)\s*\(/m', + file_get_contents($s), $matches); + $function_defs = array_merge($function_defs, $matches[1]); + } + foreach (find_function_calls($scripts) as $call) { + list($file, $no, $line, $func) = $call; + if (!in_array($func, $function_defs)) + $this->fail($file, $no, "$func: Definitely undefined"); + else + $this->pass(); + } + } +} + +function find_function_calls($scripts) { + $calls=array(); + foreach ($scripts as $s) { + $lines = explode("\n", file_get_contents($s)); + $lineno=0; + foreach ($lines as $line) { + $lineno++; $matches=array(); + preg_match_all('/(?:-[>]|::)([a-zA-Z0-9_]+)\(.*/', $line, $matches, + PREG_SET_ORDER); + foreach ($matches as $m) + if (strpos($m[0], 'nolint') === false) + $calls[] = array($s, $lineno, $line, $m[1]); + } + } + return $calls; +} + +return 'UndefinedMethods'; +?> diff --git a/setup/test/tests/test.unitialized.php b/setup/test/tests/test.unitialized.php new file mode 100644 index 0000000000000000000000000000000000000000..09bd0509e155072ccb6044429248965c2e318b36 --- /dev/null +++ b/setup/test/tests/test.unitialized.php @@ -0,0 +1,25 @@ +<?php +require_once "class.test.php"; + +class UnitializedVars extends Test { + var $name = "Access to unitialized variables"; + + 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]}'"); + } + } +} + +return 'UnitializedVars'; +?> diff --git a/web.config b/web.config new file mode 100644 index 0000000000000000000000000000000000000000..fd61b6a95f6776289e5b1fd85660f05ceec5174f --- /dev/null +++ b/web.config @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8"?> +<configuration> + + <configSections> + <sectionGroup name="system.webServer"> + <sectionGroup name="rewrite"> + <section name="rewriteMaps" overrideModeDefault="Allow" /> + <section name="rules" overrideModeDefault="Allow" /> + </sectionGroup> + </sectionGroup> + </configSections> + + <system.webServer> + <directoryBrowse enabled="false" /> + <rewrite> + <rules> + <rule name="HTTP api" stopProcessing="true"> + <match url="^(.*/)?api/(.*)$" ignoreCase="true"/> + <conditions> + <add input="{REQUEST_FILENAME}" matchType="IsFile" + ignoreCase="false" negate="true" /> + <add input="{REQUEST_FILENAME}" matchType="IsDirectory" + ignoreCase="false" negate="true" /> + </conditions> + <action type="Rewrite" url="{R:1}api/http.php/{R:2}"/> + </rule> + <rule name="Site pages" stopProcessing="true"> + <match url="^(.*/)?pages/(.*)$" ignoreCase="true"/> + <conditions> + <add input="{REQUEST_FILENAME}" matchType="IsFile" + ignoreCase="false" negate="true" /> + <add input="{REQUEST_FILENAME}" matchType="IsDirectory" + ignoreCase="false" negate="true" /> + </conditions> + <action type="Rewrite" url="{R:1}pages/index.php/{R:2}"/> + </rule> + </rules> + </rewrite> + <defaultDocument> + <files> + <remove value="index.php" /> + <add value="index.php" /> + </files> + </defaultDocument> + </system.webServer> + +</configuration>