diff --git a/.gitignore b/.gitignore index 5fdb093..9c444df 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .project .buildpath .settings/ +.externalToolBuilders/ .komodoproject # Exclude common OS-files diff --git a/Bootstrapper.php b/Bootstrapper.php index 5ded46e..f90e2da 100644 --- a/Bootstrapper.php +++ b/Bootstrapper.php @@ -1,199 +1,190 @@ . - */ - - /** - * Provides basic bootstrapper functionality. - * - * @author Daniël van de Giessen - * @package DBSR - */ - class Bootstrapper { - /** - * Private variable used to prevent initialization function from running multiple times. - * - * @var boolean - */ - private static $is_initialized = FALSE; - - /** - * Helper function for converting errors to exceptions. - * - * @param integer $errno The type of the error. - * @param string $errstr The error message. - * @param string $errfile The filename where the error occured. - * @param integer $errline The line number where the error occured. - * @throws ErrorException With the given error, unless the error_reporting value does not include given error number. - */ - public static function exception_error_handler($errno, $errstr, $errfile, $errline ) { - if(($errno & error_reporting()) != 0) { - throw new ErrorException($errstr, 0, $errno, $errfile, $errline); - } - } - - /** - * Helper function for autoloading classes. - * - * @param string $class_name The name of the class to be loaded. - * @return boolean TRUE when the class was loaded successfully, FALSE otherwise. - */ - public static function autoloader($class_name) { - // Check if class already exists - if(class_exists($class_name)) return TRUE; - - // For each include path - $include_paths = explode(PATH_SEPARATOR, get_include_path()); - foreach($include_paths as $include_path) { - // Skip empty items - if(empty($include_path) || !is_dir($include_path)) continue; - - // Clean up the include path - $include_path = rtrim($include_path, '\\/' . DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; - - // For each extension - foreach(array('.php', '.php5', '.inc.php', '.inc.php5', '.inc') as $extension) { - // Check for filename in subdirectories - $count = substr_count($class_name, '_'); - for($i = 0; $i <= $count; $i++) { - // Replace $i'th first underscores by directory separators - $filename = $include_path . preg_replace('/_/', DIRECTORY_SEPARATOR, $class_name, $i) . $extension; - if(is_readable($filename)) { - include_once $filename; - if(class_exists($class_name)) return TRUE; - } - } - } - } - - // Appearantly, the class couldn't be loaded - return FALSE; - } - - /** - * Helper function for magic quote reversal: runs stripslashes recursively on an array. - * - * @see stripslashes() - * - * @param mixed $value The value to strip slashes from. - * @return mixed The value with slashes stripped off. - */ - protected static function stripslashes_recursive($value) { - return is_array($value) ? array_map(array(get_class(), 'stripslashes_recursive'), $value) : (is_string($value) ? stripslashes($value) : $value); - } - - /** - * Initializes basic PHP stuff like error handling, include paths, magic quote reversal, internal character encoding, timezones. - */ - public static function initialize() { - // Check initialization status - if(self::$is_initialized) return; - - // Set up error handling - set_error_handler(array(get_class(), 'exception_error_handler')); - - // Define DEBUG constant - if(!defined('DEBUG')) { - define('DEBUG', FALSE); - } - - // Set error reporting level - error_reporting(DEBUG ? E_ALL : 0); - - // Set up include path - set_include_path(get_include_path() . PATH_SEPARATOR . realpath(dirname(__FILE__))); - - // Set up autoloader - spl_autoload_register(array(get_class(), 'autoloader')); - - // Get rid of magic quotes - if(function_exists('get_magic_quotes_gpc') && @get_magic_quotes_gpc()) { - $_POST = self::stripslashes_recursive($_POST); - $_GET = self::stripslashes_recursive($_GET); - $_COOKIE = self::stripslashes_recursive($_COOKIE); - $_REQUEST = self::stripslashes_recursive($_REQUEST); - @ini_set('magic_quotes_gpc', FALSE); - } - if(function_exists('get_magic_quotes_gpc')) @set_magic_quotes_runtime(FALSE); - - // Try to remove any memory limitations - @ini_set('memory_limit', '-1'); - - // Try to set the PCRE recursion limit to a sane value - // See http://stackoverflow.com/a/7627962 - @ini_set('pcre.recursion_limit', '100'); - - // Set internal character encoding - @ini_set('default_charset', 'UTF-8'); - if(extension_loaded('mbstring')) { - @mb_internal_encoding('UTF-8'); - } - if(version_compare(PHP_VERSION, '5.6', '<') && extension_loaded('iconv')) { - @iconv_set_encoding('internal_encoding', 'UTF-8'); - } - - // Set the timezone - date_default_timezone_set('UTC'); - - // Set initialization status - self::$is_initialized = TRUE; - } - - /** - * Destroys the current PHP session. - */ - public static function sessionDestroy() { - $_SESSION = array(); - session_destroy(); - session_commit(); - } - - /** - * Starts a PHP session and provides basic protection against session hijacking. - */ - public static function sessionStart() { - // Determine current security data - $security_data = array( - 'server_ip' => $_SERVER['SERVER_ADDR'], - 'server_file' => __FILE__, - 'client_ip' => $_SERVER['REMOTE_ADDR'], - 'client_ua' => $_SERVER['HTTP_USER_AGENT'] - ); - - // Set the session life time to 24 hours - @ini_set('sessions.gc_maxlifetime', (string) (60 * 60 * 24)); - - // Set the session name - session_name('DBSR_session'); - - // Open a session to access and store user data - session_start(); - - // If the session is new... - if(session_id() == '' || !isset($_SESSION['_session_security_data'])) { - // Set the security data - $_SESSION['_session_security_data'] = $security_data; - } else { - // Check if the session is invalid - if($_SESSION['_session_security_data'] !== $security_data) { - // Destroy the current session - self::sessionDestroy(); - - // Start a new one - self::sessionStart(); - } - } - } - } +/** + * Provides basic bootstrapper functionality. + */ +class Bootstrapper { + /** + * Private variable used to prevent initialization function from running multiple times. + * + * @var boolean + */ + private static $is_initialized = FALSE; + + /** + * Helper function for converting errors to exceptions. + * + * @param integer $errno The type of the error. + * @param string $errstr The error message. + * @param string $errfile The filename where the error occured. + * @param integer $errline The line number where the error occured. + * @throws ErrorException With the given error, unless the error_reporting value does not include given error number. + */ + public static function exception_error_handler($errno, $errstr, $errfile, $errline ) { + if(($errno & error_reporting()) != 0) { + throw new ErrorException($errstr, 0, $errno, $errfile, $errline); + } + } + + /** + * Helper function for autoloading classes. + * + * @param string $class_name The name of the class to be loaded. + * @return boolean TRUE when the class was loaded successfully, FALSE otherwise. + */ + public static function autoloader($class_name) { + // Check if class already exists + if(class_exists($class_name)) { + return TRUE; + } + + // For each include path + $include_paths = explode(PATH_SEPARATOR, get_include_path()); + foreach($include_paths as $include_path) { + // Skip empty items + if(empty($include_path) || !is_dir($include_path)) { + continue; + } + + // Clean up the include path + $include_path = rtrim($include_path, '\\/' . DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; + + // For each extension + foreach(array('.php', '.php5', '.inc.php', '.inc.php5', '.inc') as $extension) { + // Check for filename in subdirectories + $count = substr_count($class_name, '_'); + for($i = 0; $i <= $count; $i++) { + // Replace $i'th first underscores by directory separators + $filename = $include_path . preg_replace('/_/', DIRECTORY_SEPARATOR, $class_name, $i) . $extension; + if(is_readable($filename)) { + include_once $filename; + if(class_exists($class_name)) { + return TRUE; + } + } + } + } + } + + // Appearantly, the class couldn't be loaded + return FALSE; + } + + /** + * Helper function for magic quote reversal: runs stripslashes recursively on an array. + * + * @see stripslashes() + * + * @param mixed $value The value to strip slashes from. + * @return mixed The value with slashes stripped off. + */ + protected static function stripslashes_recursive($value) { + return is_array($value) ? array_map(array(get_class(), 'stripslashes_recursive'), $value) : (is_string($value) ? stripslashes($value) : $value); + } + + /** + * Initializes basic PHP stuff like error handling, include paths, magic quote reversal, internal character encoding, timezones. + */ + public static function initialize() { + // Check initialization status + if(static::$is_initialized) { + return; + } + + // Set up error handling + set_error_handler(array(get_class(), 'exception_error_handler')); + + // Define DEBUG constant + if(!defined('DEBUG')) { + define('DEBUG', FALSE); + } + + // Set error reporting level + error_reporting(DEBUG ? E_ALL : 0); + + // Set up include path + set_include_path(get_include_path() . PATH_SEPARATOR . realpath(dirname(__FILE__))); + + // Set up autoloader + spl_autoload_register(array(get_class(), 'autoloader')); + + // Get rid of magic quotes + if(function_exists('get_magic_quotes_gpc') && @get_magic_quotes_gpc()) { + $_POST = static::stripslashes_recursive($_POST); + $_GET = static::stripslashes_recursive($_GET); + $_COOKIE = static::stripslashes_recursive($_COOKIE); + $_REQUEST = static::stripslashes_recursive($_REQUEST); + @ini_set('magic_quotes_gpc', FALSE); + } + if(function_exists('set_magic_quotes_runtime')) { + @set_magic_quotes_runtime(FALSE); + } + + // Try to remove any memory limitations + @ini_set('memory_limit', '-1'); + + // Try to set the PCRE recursion limit to a sane value + // See http://stackoverflow.com/a/7627962 + @ini_set('pcre.recursion_limit', '100'); + + // Set internal character encoding + @ini_set('default_charset', 'UTF-8'); + if(extension_loaded('mbstring')) { + @mb_internal_encoding('UTF-8'); + } + if(version_compare(PHP_VERSION, '5.6', '<') && extension_loaded('iconv')) { + @iconv_set_encoding('internal_encoding', 'UTF-8'); + } + + // Set the timezone + date_default_timezone_set('UTC'); + + // Set initialization status + static::$is_initialized = TRUE; + } + + /** + * Destroys the current PHP session. + */ + public static function sessionDestroy() { + $_SESSION = array(); + session_destroy(); + session_commit(); + } + + /** + * Starts a PHP session and provides basic protection against session hijacking. + */ + public static function sessionStart() { + // Determine current security data + $security_data = array( + 'server_ip' => $_SERVER['SERVER_ADDR'], + 'server_file' => __FILE__, + 'client_ip' => $_SERVER['REMOTE_ADDR'], + 'client_ua' => $_SERVER['HTTP_USER_AGENT'] + ); + + // Set the session life time to 24 hours + @ini_set('sessions.gc_maxlifetime', (string) (60 * 60 * 24)); + + // Set the session name + session_name('DBSR_session'); + + // Open a session to access and store user data + session_start(); + + // If the session is new... + if(session_id() == '' || !isset($_SESSION['_session_security_data'])) { + // Set the security data + $_SESSION['_session_security_data'] = $security_data; + } else { + // Check if the session is invalid + if($_SESSION['_session_security_data'] !== $security_data) { + // Destroy the current session + static::sessionDestroy(); + + // Start a new one + static::sessionStart(); + } + } + } +} diff --git a/DBSR.php b/DBSR.php index d328fb5..b74817a 100644 --- a/DBSR.php +++ b/DBSR.php @@ -1,1022 +1,1032 @@ . - */ - - /** - * DBSR provides functionality for commiting search-and-replace-operations on MySQL databases. - * - * @author Daniël van de Giessen - * @package DBSR - * @version 2.1.0 - */ - class DBSR { - /* Constants */ - /** - * Version string indicating the DBSR version. - * @var string - */ - const VERSION = '2.1.0'; - - /** - * Option: use case-insensitive search and replace. - * @var boolean - */ - const OPTION_CASE_INSENSITIVE = 0; - - /** - * Option: process *all* database rows. - * @var boolean - */ - const OPTION_EXTENSIVE_SEARCH = 1; - - /** - * Option: number of rows to process simultaneously. - * @var integer - */ - const OPTION_SEARCH_PAGE_SIZE = 2; - - /** - * Option: use strict matching. - * @var boolean - */ - const OPTION_VAR_MATCH_STRICT = 3; - - /** - * Option: up to how many decimals floats should be matched. - * @var integer - */ - const OPTION_FLOATS_PRECISION = 4; - - /** - * Option: automatically convert character sets. - * @var boolean - */ - const OPTION_CONVERT_CHARSETS = 5; - - /** - * Option: cast all replace-values to the original type. - * @var boolean - */ - const OPTION_VAR_CAST_REPLACE = 6; - - /** - * Option: write changed values back to the database. - * @var boolean - */ - const OPTION_DB_WRITE_CHANGES = 7; - - /** - * Option: interpret serialized strings as PHP types. - * @var boolean - */ - const OPTION_HANDLE_SERIALIZE = 8; - - /** - * Option: reverses the filters causing to search *only* in mentioned tables/columns. - * @var array - */ - const OPTION_REVERSED_FILTERS = 9; - - /* Static methods */ - /** - * Creates a new class with the given name if it does not exists. - * - * @param string $className The name of the class. - */ - public static function createClass($className) { - if(!class_exists($className, FALSE)) eval('class ' . $className . ' {}'); - } - - /** - * Returns the PHP type for any MySQL type according to the PHP's settype() documentation. - * Will return 'string' for unknown / invalidly formatted types. - * - * @see http://php.net/manual/en/function.settype.php - * - * @param string $mysql_type The MySQL type. - * - * @return string The corresponding PHP type. - */ - public static function getPHPType($mysql_type) { - // MySQL type regexes and corresponding PHP type - $types = array( - /* Boolean types */ - '/^\s*BOOL(EAN)?\s*$/i' => 'boolean', - - /* Integer types */ - '/^\s*TINYINT\s*(?:\(\s*\d+\s*\)\s*)?$/i' => 'integer', - '/^\s*SMALLINT\s*(?:\(\s*\d+\s*\)\s*)?$/i' => 'integer', - '/^\s*MEDIUMINT\s*(?:\(\s*\d+\s*\)\s*)?$/i' => 'integer', - '/^\s*INT(EGER)?\s*(?:\(\s*\d+\s*\)\s*)?$/i' => 'integer', - '/^\s*BIGINT\s*(?:\(\s*\d+\s*\)\s*)?$/i' => 'integer', - - /* Float types */ - '/^\s*FLOAT\s*(?:\(\s*\d+\s*(?:,\s*\d+\s*)?\)\s*)?$/i' => 'float', - '/^\s*DOUBLE(\s+PRECISION)?\s*(?:\(\s*\d+\s*(?:,\s*\d+\s*)?\)\s*)?$/i' => 'float', - '/^\s*REAL\s*(?:\(\s*\d+\s*(?:,\s*\d+\s*)?\)\s*)?$/i' => 'float', - '/^\s*DEC(IMAL)?\s*(?:\(\s*\d+\s*(?:,\s*\d+\s*)?\)\s*)?$/i' => 'float', - '/^\s*NUMERIC\s*(?:\(\s*\d+\s*(?:,\s*\d+\s*)?\)\s*)?$/i' => 'float', - '/^\s*FIXED\s*(?:\(\s*\d+\s*(?:,\s*\d+\s*)?\)\s*)?$/i' => 'float', - ); - - // Try each type - foreach($types as $regex => $type) { - // Test on a whitespace-free version - if(preg_match($regex, $mysql_type)) return $type; - } - - // If nothing matches, return default (string) - return 'string'; - } - - /* Properties */ - /** - * The PDO instance used for connecting to the database. - * @var PDO - */ - protected $pdo; - - /** - * The default charset used by the database connection. - * @var string - */ - private $_pdo_charset; - - /** - * The default collation used by the database connection. - * @var string - */ - private $_pdo_collation; - - /** - * The callback used by DBRunner. - * @var callback - */ - private $_dbr_callback; - - /** - * All options of the current instance. - * @var array - */ - protected $options = array( - self::OPTION_CASE_INSENSITIVE => FALSE, - self::OPTION_EXTENSIVE_SEARCH => FALSE, - self::OPTION_SEARCH_PAGE_SIZE => 10000, - self::OPTION_VAR_MATCH_STRICT => TRUE, - self::OPTION_FLOATS_PRECISION => 5, - self::OPTION_CONVERT_CHARSETS => TRUE, - self::OPTION_VAR_CAST_REPLACE => TRUE, - self::OPTION_DB_WRITE_CHANGES => TRUE, - self::OPTION_HANDLE_SERIALIZE => TRUE, - self::OPTION_REVERSED_FILTERS => FALSE, - ); - - /** - * The filters for tables/columns. - * @var array - */ - protected $filters = array(); - - /** - * The search-values. - * @var array - */ - protected $search = array(); - - /** - * The replace-values. - * @var array - */ - protected $replace = array(); - - /** - * An array of search-values converted per charset. - * @var array - */ - protected $search_converted = array(); - - /* Methods */ - /** - * Constructor: sets the PDO instance for use with this DBSR instance. - * - * @param PDO $pdo A PDO instance representing a connection to a MySQL database. - * @throws RuntimeException If the a required PHP extension is not available. - * @throws InvalidArgumentException If the given PDO instance does not represent a MySQL database. - */ - public function __construct(PDO $pdo) { - // Check if the required PCRE library is available - if(!extension_loaded('pcre')) { - throw new RuntimeException('The pcre (Perl-compatible regular expressions) extension is required for DBSR to work!'); - } - - // Check if the PDO represents a connection to a MySQL database - if($pdo->getAttribute(PDO::ATTR_DRIVER_NAME) != 'mysql') { - throw new InvalidArgumentException('The given PDO instance is not representing an MySQL database!'); - } - - // Save the PDO instance - $this->pdo = $pdo; - } - - /** - * Returns the value of an DBSR option. - * - * @param integer $option One of the DBSR::OPTION_* constants. - * - * @return mixed The value of the requested option or NULL if unsuccessful. - */ - public function getOption($option) { - return isset($this->options[$option]) ? $this->options[$option] : NULL; - } - - /** - * Sets an option on this instance. - * - * @param integer $attribute The attribute to be set. - * @param mixed $value The new value for the given attribute. - * - * @throws PDOException If any database error occurs. - * @throws PDOException If any database error occurs. - */ - public function setOption($option, $value) { - // Only set known options - if(!isset($this->options[$option])) return FALSE; - - switch($option) { - case self::OPTION_SEARCH_PAGE_SIZE: - // Require the page size to be greater than 0 - if(is_integer($value) && $value > 0) { - $this->options[$option] = $value; - return TRUE; - } else { - return FALSE; - } - - case self::OPTION_FLOATS_PRECISION: - // Require the precision to be greater than or equal to 0 - if(is_integer($value) && $value >= 0) { - $this->options[$option] = $value; - return TRUE; - } else { - return FALSE; - } - - default: - // By default, check if the type is equal - if(gettype($this->options[$option]) == gettype($value)) { - // Allow setting the same type - $this->options[$option] = $value; - return TRUE; - } else { - // Don't allow setting the wrong type - return FALSE; - } - - } - } - - /** - * Sets the filters by which to filter tables/columns. - * - * @param array $filters The filters as an associative array. For example: - * array( - * 'entire_table', - * array( - * 'column', - * 'in', - * 'every', - * 'table', - * ), - * 'table' => 'specific_column', - * 'table' => array( - * 'specific', - * 'columns', - * ), - * ) - */ - public function setFilters(array $filters) { - // Array for the parsed filters - $filters_parsed = array(); - - // For each filter - foreach($filters as $key => $value) { - if(is_int($key)) { - if(is_string($value)) { - // Entire table - $filters_parsed[$value] = TRUE; - } elseif(is_array($value)) { - // Skip empty arrays - if(!count($value)) continue; - - // Require strings - foreach($value as $v) if(!is_string($v)) throw new InvalidArgumentException('Only strings qualify as column names!'); - - // Save it - if(isset($filters_parsed['.'])) { - $filters_parsed['.'] = array_values(array_unique(array_merge($filters_parsed['.'], array_values($value)))); - } else { - $filters_parsed['.'] = array_values(array_unique($value)); - } - } else throw new InvalidArgumentException('The filter array can only contain strings or arrays!'); - } else { - if(is_string($value)) { - // Single column - if(isset($filters_parsed[$key])) { - $filters_parsed[$key] = array_values(array_unique(array_merge($filters_parsed[$key], array($value)))); - } else { - $filters_parsed[$key] = array($value); - } - } elseif(is_array($value)) { - // Skip empty arrays - if(!count($value)) continue; - - // Require strings - foreach($value as $v) if(!is_string($v)) throw new InvalidArgumentException('Only strings qualify as column names!'); - - // Save it - if(isset($filters_parsed[$key])) { - $filters_parsed[$key] = array_values(array_unique(array_merge($filters_parsed[$key], array_values($value)))); - } else { - $filters_parsed[$key] = array_values(array_unique($value)); - } - } else throw new InvalidArgumentException('The filter array can only contain strings or arrays!'); - } - } - - // Save the parsed filters - $this->filters = $filters_parsed; - } - - /** - * Resets all filters. - */ - public function resetFilters() { - $this->filters = array(); - } - - /** - * Indicated whether the given table / column is filtered. - * @param string $table The name of the table. - * @param string $column (Optional.) Then name of the column. - */ - public function isFiltered($table, $column = NULL) { - if($this->getOption(self::OPTION_REVERSED_FILTERS)) { - // Reversed filters - if($column == NULL) { - // Never filter reversed based on table only, since there may be non-table-specific columns in it - return FALSE; - } else { - // Process columns if the entire table is filtered or if the column is filtered for either this table or in global - return !( - isset($this->filters[$table]) && $this->filters[$table] === TRUE || - isset($this->filters[$table]) && in_array($column, $this->filters[$table], TRUE) || - isset($this->filters['.']) && in_array($column, $this->filters['.'], TRUE) - ); - } - } else { - // Normal filters - if($column == NULL) { - // Only skip tables if the entire table is filtered - return isset($this->filters[$table]) && $this->filters[$table] === TRUE; - } else { - // Skip columns if the entire table is filtered or if the column is filtered for either this table or in global - return - isset($this->filters[$table]) && $this->filters[$table] === TRUE || - isset($this->filters[$table]) && in_array($column, $this->filters[$table], TRUE) || - isset($this->filters['.']) && in_array($column, $this->filters['.'], TRUE) - ; - } - } - } - - /** - * Sets the search- and replace-values. - * - * @param array $search The values to search for. - * @param array $replace The values to replace with. - * @throws InvalidArgumentException If the search- or replace-values are invalid. - */ - public function setValues(array $search, array $replace) { - // Check array lengths - if(count($search) == 0 || count($replace) == 0 || count($search) != count($replace)) { - throw new InvalidArgumentException('The number of search- and replace-values is invalid!'); - } - - // Clean indices - $search = array_values($search); - $replace = array_values($replace); - - // Remove all identical values - for($i = 0; $i < count($search); $i++) { - if($search[$i] === $replace[$i]) { - array_splice($search, $i, 1); - array_splice($replace, $i, 1); - $i--; - } - } - - // Check the length again - if(count($search) == 0) throw new InvalidArgumentException('All given search- and replace-values are identical!'); - - // Set the values - $this->search = $search; - $this->replace = $replace; - } - - /** - * Runs a search- and replace-action on the database. - * - * @throws PDOException If any database error occurs. - * @throws UnexpectedValueException If an error occurs processing data retrieved from the database. - * @return integer The number of changed rows. - */ - public function exec() { - // Remove the time limit - if(!ini_get('safe_mode') && ini_get('max_execution_time') != '0') { - set_time_limit(0); - } - - // Call the DBRunner - return $this->DBRunner(array($this, 'searchReplace')); - } - - /** - * Runs through the database and execs the provided callback on every value. - * - * @param callable $callback The callback function to call on every value. - * @param array $search (Optional.) Search value to limit the matched rows to. - * @throws PDOException If any database error occurs. - * @throws UnexpectedValueException If an error occurs processing data retrieved from the database. - * @return integer The number of changed rows. - */ - protected function DBRunner($callback) { - // Save the callback - $this->_dbr_callback = $callback; - - // Count the number of changed rows - $result = 0; - - // Set unserialize object handler - $unserialize_callback_func = ini_set('unserialize_callback_func', get_class() . '::createClass'); - - // PDO attributes to set - $pdo_attributes = array( - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION - ); - - // Set PDO attributes and save the old values - foreach($pdo_attributes as $attribute => $value) { - $pdo_attributes[$attribute] = $this->pdo->getAttribute($attribute); - $this->pdo->setAttribute($attribute, $value); - } - - // Catch all Exceptions so that we can reset the errormode before rethrowing it - try { - // Figure out the connection character set and collation - $this->_pdo_charset = $this->pdo->query('SELECT @@character_set_client;', PDO::FETCH_COLUMN, 0)->fetch(); - $this->_pdo_collation = $this->pdo->query('SELECT @@collation_connection;', PDO::FETCH_COLUMN, 0)->fetch(); - - // Get a list of all tables - $tables = $this->pdo->query('SHOW TABLES;', PDO::FETCH_COLUMN, 0)->fetchAll(); - - // Lock each table - $this->pdo->query('LOCK TABLES `' . implode('` WRITE, `', $tables) . '` WRITE;'); - - // Loop through all the (non-filtered) tables - foreach($tables as $table) { - if(!$this->isFiltered($table)) $result += $this->_DBRTable($table, $callback); - } - } catch(Exception $e) {} - - // Unlock all locked tables - $this->pdo->query('UNLOCK TABLES'); - - // Restore the old PDO attribute values - foreach($pdo_attributes as $attribute => $value) { - $this->pdo->setAttribute($attribute, $value); - } - - // Reset the unserialize object handler - ini_set('unserialize_callback_func', $unserialize_callback_func); - - // Check whether an exception was thrown - if(isset($e) && $e instanceof Exception) { - // Rethrow the exception - throw $e; - } else { - // Return the results - return $result; - } - } - - /** - * DBRunner: processes the given table. - * - * @param string $table Name of the table. - * @throws UnexpectedValueException If an error occurs processing data retrieved from the database. - * @return integer The number of changed rows. - */ - private function _DBRTable($table) { - // List all columns of the current table - $columns_info = $this->pdo->query('SHOW FULL COLUMNS FROM `' . $table . '`;', PDO::FETCH_NAMED); - - // Empty arrays for columns and keys - $columns = array(); - $keys = array(); - - // Process each column - foreach($columns_info as $column_info) { - // Determine type - $columns[$column_info['Field']] = array( - 'null' => ($column_info['Null'] == 'YES'), - 'type' => self::getPHPType($column_info['Type']), - 'charset' => preg_replace('/^([a-z\d]+)_[\w\d]+$/i', '$1', $column_info['Collation']), - 'collation' => $column_info['Collation'], - ); - - // Determine wheter it's part of a candidate key - $keys[$column_info['Key']][$column_info['Field']] = $columns[$column_info['Field']]; - } - - // Determine prefered candidate key - if(isset($keys['PRI'])) { - // Always prefere a primary key(set) - $keys = $keys['PRI']; - } elseif(isset($keys['UNI'])) { - // Though a unique key(set) also works - $keys = $keys['UNI']; - } else { - // If everything else fails, use the full column set - $keys = $columns; - } - - // Filter columns - foreach($columns as $column => $column_info) { - if($this->isFiltered($table, $column)) unset($columns[$column]); - } - - // Prepare a smart WHERE-statement - if(!$this->getOption(self::OPTION_EXTENSIVE_SEARCH)) { - $where = $this->_DBRWhereSearch($columns); - } else { - // No WHERE-statement - $where = ''; - } - - // Check if after filtering and WHERE-matching any valid columns are left - if(count($columns) == 0) return; - - // Convert search-values to the correct charsets - if($this->getOption(self::OPTION_CONVERT_CHARSETS)) { - foreach($columns as $column => $column_info) if(!isset($this->search_converted[$column_info['charset']]) && $column_info['type'] == 'string' && !empty($column_info['charset']) && $column_info['charset'] != $this->_pdo_charset) { - $search_convert = array(); - foreach($this->search as $i => $item) if(is_string($item)) { - $this->search_converted[$column_info['charset']][$i] = $this->pdo->query('SELECT CONVERT(_' . $this->_pdo_charset . $this->pdo->quote($item) . ' USING ' . $column_info['charset'] . ');', PDO::FETCH_COLUMN, 0)->fetch(); - } else { - $this->search_converted[$column_info['charset']][$i] = $item; - } - } - } - - // Get the number of rows - $row_count = (int) $this->pdo->query('SELECT COUNT(*) FROM `' . $table . '`' . $where . ';', PDO::FETCH_COLUMN, 0)->fetch(); - - // Count the number of changed rows - $row_change_count = 0; - - // For each page - $page_size = $this->getOption(self::OPTION_SEARCH_PAGE_SIZE); - for($page_start = 0; $page_start < $row_count; $page_start += $page_size) { - // Get the rows of this page - $rows = $this->pdo->query('SELECT DISTINCT * FROM `' . $table . '`' . $where . 'LIMIT ' . $page_start . ', ' . $page_size . ';', PDO::FETCH_ASSOC); - - // Loop over each row - foreach($rows as $row) { - if($this->_DBRRow($table, $columns, $keys, $row) > 0) $row_change_count++; - } - } - - // Return the number of changed rows - return $row_change_count; - } - - /** - * DBRunner: processes the given row. - * - * @param string $table The name of the current table. - * @param array $columns The relevant columns of this table. - * @param array $keys The candidate keyset for this table. - * @param array $row The row to be processed. - * @throws UnexpectedValueException If an error occurs processing data retrieved from the database. - * @return integer The number of changed columns. - */ - private function _DBRRow($table, array $columns, array $keys, array $row) { - // Array with row changes - $changeset = array(); - - // Convert columns - foreach($columns + $keys as $column => $column_info) { - if(!settype($row[$column], $column_info['type'])) { - throw new UnexpectedValueException('Failed to convert `' . $table . '`.`' . $column . '` value to a ' . $column_info['type'] . ' for value "' . $row[$column] . '"!'); - } - } - - // Loop over each column - foreach($columns as $column => $column_info) { - // Set the value - $value = &$row[$column]; - - // Call the callback - if($this->getOption(self::OPTION_CONVERT_CHARSETS) && isset($this->search_converted[$column_info['charset']])) { - $value_new = call_user_func($this->_dbr_callback, $value, $this->search_converted[$column_info['charset']], $this->replace); - } else { - $value_new = call_user_func($this->_dbr_callback, $value); - } - - // Check the result - if($value_new !== $value) { - $changeset[$column] = $value_new; - } - } - - // Update the row if nessecary - if(count($changeset) > 0 && $this->getOption(self::OPTION_DB_WRITE_CHANGES)) { - // Build the WHERE-statement for this row - $where = $this->_DBRWhereRow($keys, $row); - - // Determine the updates - $updates = array(); - foreach($changeset as $column => $value_new) { - switch($columns[$column]['type']) { - case 'integer': - $updates[] = '`' . $column . '` = ' . (int) $value_new; - $search_where_column = TRUE; - break; - - case 'float': - $updates[] = '`' . $column . '` = ' . (string) round((float) $value_new, $this->getOption(self::OPTION_FLOATS_PRECISION)); - break; - - default: - case 'string': - // First, escape the string and add quotes - $update_string = $this->pdo->quote((string) $value_new); - - // Then, check the charset - if(!empty($columns[$column]['charset']) && $this->_pdo_charset != $columns[$column]['charset']) { - if($this->getOption(self::OPTION_CONVERT_CHARSETS)) { - $update_string = 'CONVERT(_' . $this->_pdo_charset . $update_string . ' USING ' . $columns[$column]['charset'] . ')'; - } else { - $update_string = 'BINARY ' . $update_string; - } - } - - // Then, check the collation - if(!empty($columns[$column]['collation']) && $this->getOption(self::OPTION_CONVERT_CHARSETS) && $this->_pdo_collation != $columns[$column]['collation']) { - $update_string .= ' COLLATE ' . $columns[$column]['collation']; - } - - // Finally, build and add the comparison for the WHERE-clause - $updates[] = '`' . $column . '` = ' . $update_string; - break; - } - } - - // Commit the updates - $this->pdo->query('UPDATE `' . $table . '` SET ' . implode(', ', $updates) . $where . ';'); - } - - // Return the number of changed columns - return count($changeset); - } - - /** - * DBRunner: constructs the WHERE-clause for searching. - * - * @param array $columns (Reference.) The columns to be searched. Inegible columns will be removed. - * @return mixed String with the constructed WHERE-clause, or FALSE if no column could be matched - * (thus the table may be skipped). - */ - private function _DBRWhereSearch(array &$columns) { - // Array for WHERE-clause elements - $where = array(); - - // Loop over all columns - foreach($columns as $column => $column_info) { - // By default there's no reason to include this column - $where_column = FALSE; - - // Loop over all search items - foreach($this->search as $item) { - // If there's a valid WHERE-component, add it - if($where_component = $this->_DBRWhereColumn($column, $column_info, $item, FALSE)) { - $where[] = $where_component; - $where_column = TRUE; - } - } - - // Remove all columns which will never match since no valid WHERE-components could be constructed - if(!$where_column) unset($columns[$column]); - } - - // Combine the WHERE-clause or empty it - if(count($where) > 0) { - return ' WHERE ' . implode(' OR ', $where) . ' '; - } else { - // Assert count($columns) == 0 - if(count($columns) != 0) throw new LogicException('No WHERE-clause was constructed, yet there are valid columns left!'); - - // Since there are no valid columns left, we can skip processing this table - return FALSE; - } - } - - /** - * DBRunner: Constructs a WHERE-clause for the given row. - * - * @param array $keys The candidate keys to be used for constructing the WHERE-clause. - * @param array $row The row values. - * @return string The WHERE-clause for the given row. - */ - private function _DBRWhereRow(array $keys, array $row) { - $where = array(); - foreach($keys as $key => $key_info) { - $where[] = $this->_DBRWhereColumn($key, $key_info, $row[$key], TRUE); - } - return ' WHERE ' . implode(' AND ', $where) . ' '; - } - - /** - * DBRunner: Constructs a WHERE component for the given column and value. - * - * @param string $column The column name. - * @param array $column_info Array with column info. - * @param mixed $value The value to match. - * @param boolean $string_exact Whether to use 'LIKE %value%'-style matching. - * - * @return mixed The WHERE component for the given parameters as a string, - * or FALSE if the value is not valid for the given column. - */ - private function _DBRWhereColumn($column, array $column_info, $value, $string_exact) { - switch($column_info['type']) { - case 'integer': - // Search for integer value - if(!$this->getOption(self::OPTION_VAR_MATCH_STRICT) || is_integer($value)) { - // Add a where clause for the integer value - return '`' . $column . '` = ' . (int) $value; - } - break; - - case 'float': - // Search for float difference (since floats aren't precise enough to compare directly) - if(!$this->getOption(self::OPTION_VAR_MATCH_STRICT) || is_float($value)) { - return 'ABS(`' . $column . '` - ' . (float) $value . ') < POW(1, -' . $this->getOption(self::OPTION_FLOATS_PRECISION) . ')'; - } - break; - - default: - case 'string': - // String search is even harder given the many possibly charsets - - // If the search item is a float, we have to limit it to the maximum precision first - if(is_float($value)) { - $value = round($value, $this->getOption(self::OPTION_FLOATS_PRECISION)); - } - - if(!$string_exact) { - $value = '%' . (string) $value . '%'; - } - - // First, escape the string and add quotes - $where_string = $this->pdo->quote((string) $value); - - // Then, check the charset - if(!empty($column_info['charset']) && $this->_pdo_charset != $column_info['charset']) { - if($this->getOption(self::OPTION_CONVERT_CHARSETS)) { - $where_string = 'CONVERT(_' . $this->_pdo_charset . $where_string . ' USING ' . $column_info['charset'] . ')'; - } else { - $where_string = 'BINARY ' . $where_string; - } - } - - // Then, check the collation - if(!empty($column_info['collation']) && $this->getOption(self::OPTION_CONVERT_CHARSETS) && $this->_pdo_collation != $column_info['collation']) { - if($this->getOption(self::OPTION_CASE_INSENSITIVE)) { - $where_string .= ' COLLATE ' . preg_replace('/_cs$/i', '_ci', $column_info['collation']); - } else { - $where_string .= ' COLLATE ' . $column_info['collation']; - } - } - - // Column name - $column = '`' . $column . '`'; - - // Case insensitivity - if(!empty($column_info['collation']) && $this->getOption(self::OPTION_CASE_INSENSITIVE) && preg_replace('/^.*_([a-z]+)$/i', '$1', $column_info['collation']) == 'cs') { - $column .= ' COLLATE ' . preg_replace('/_cs$/i', '_ci', $column_info['collation']); - } - - // Add the column - $where_string = $column . ' ' . ($string_exact ? '=' : 'LIKE') . ' ' . $where_string; - - if(!empty($column_info['charset']) && !$this->getOption(self::OPTION_CONVERT_CHARSETS) && $this->_pdo_charset != $column_info['charset']) { - $where_string = 'BINARY ' . $where_string; - } - - // Finally, build and add the comparison for the WHERE-clause - return $where_string; - } - - // It seems the value was not valid for this column - return FALSE; - } - - /** - * Runs a search-and-replace action on the provided value. - * - * @var mixed $value The value to search through. - * @var array $search (Optional.) The array of search-values. If not provided $this->search is used. - * @var array $replace (Optional.) The array of replace-values. If not provided $this->replace is used. - * @return mixed The value with all occurences of search items replaced. - */ - protected function searchReplace($value, array $search = NULL, array $replace = NULL) { - // Check the search- and replace-values - if(is_null($search)) $search = $this->search; - if(is_null($replace)) $replace = $this->replace; - - // The new value - $new_value = $value; - - // For each type - switch(TRUE) { - case is_array($value): - // The result is also an array - $new_value = array(); - // Loop through all the values - foreach($value as $key => $element) { - $new_value[$this->searchReplace($key)] = $this->searchReplace($element); - } - break; - - case is_bool($value): - for($i = 0; $i < count($search); $i++) { - if($new_value === $search[$i] || !$this->getOption(self::OPTION_VAR_MATCH_STRICT) && $new_value == $search[$i]) { - $new_value = $replace[$i]; - } - } - break; - - case is_float($value): - $float_precision = pow(10, -1 * $this->getOption(self::OPTION_FLOATS_PRECISION)); - for($i = 0; $i < count($search); $i++) { - if( is_float($search[$i]) && abs($new_value - $search[$i]) < $float_precision || - !$this->getOption(self::OPTION_VAR_MATCH_STRICT) && ( - $new_value == $search[$i] || - abs($new_value - (float) $search[$i]) < $float_precision - ) - ) { - $new_value = $replace[$i]; - } - } - break; - - case is_int($value): - for($i = 0; $i < count($search); $i++) { - if($new_value === $search[$i] || !$this->getOption(self::OPTION_VAR_MATCH_STRICT) && $new_value == $search[$i]) { - $new_value = $replace[$i]; - } - } - break; - - case is_object($value): - // Abuse the fact that corrupted serialized strings are handled by our own regexes - $new_value = unserialize($this->searchReplace(preg_replace('/^O:\\d+:/', 'O:0:', serialize($new_value)))); - break; - - case is_string($value): - // Regex for detecting serialized strings - $serialized_regex = '/^(a:\\d+:\\{.*\\}|b:[01];|d:\\d+\\.\\d+;|i:\\d+;|N;|O:\\d+:"[a-zA-Z_\\x7F-\\xFF][a-zA-Z0-9_\\x7F-\\xFF]*":\\d+:\\{.*\\}|s:\\d+:".*";)$/Ss'; - - // Try unserializing it - $unserialized = @unserialize($new_value); - - // Check if if actually was unserialized - if($this->getOption(self::OPTION_HANDLE_SERIALIZE) && ($unserialized !== FALSE || $new_value === serialize(FALSE)) && !is_object($unserialized)) { - // Process recursively - $new_value = serialize($this->searchReplace($unserialized)); - } elseif($this->getOption(self::OPTION_HANDLE_SERIALIZE) && (is_object($unserialized) || preg_match($serialized_regex, $new_value))) { - // If it looks like it's serialized, use special regexes for search-and-replace - - // TODO: split arrays/objects and process recursively? - - // Search and replace booleans - if($changed_value = preg_replace_callback('/b:([01]);/S', array($this, '_searchReplace_preg_callback_boolean'), $new_value)) { - $new_value = $changed_value; - } - - // Search and replace integers - if($changed_value = preg_replace_callback('/i:(\\d+);/S', array($this, '_searchReplace_preg_callback_integer'), $new_value)) { - $new_value = $changed_value; - } - - // Search and replace floats - if($changed_value = preg_replace_callback('/d:(\\d+)\.(\\d+);/S', array($this, '_searchReplace_preg_callback_float'), $new_value)) { - $new_value = $changed_value; - } - - // Search-and-replace object names (and update length) - if($changed_value = preg_replace_callback('/O:\\d+:"([a-zA-Z_\\x7F-\\xFF][a-zA-Z0-9_\\x7F-\\xFF]*)":(\\d+):{(.*)}/Ss', array($this, '_searchReplace_preg_callback_objectname'), $new_value)) { - $new_value = $changed_value; - } - - // Search-and-replace strings (and update length) - if($changed_value = preg_replace_callback('/s:\\d+:"(.*?|a:\\d+:{.*}|b:[01];|d:\\d+\\.\\d+;|i:\d+;|N;|O:\\d+:"[a-zA-Z_\\x7F-\\xFF][a-zA-Z0-9_\\x7F-\\xFF]*":\\d+:{.*}|s:\\d+:".*";)";/Ss', array($this, '_searchReplace_preg_callback_string'), $new_value)) { - $new_value = $changed_value; - } - - // If the regexes didn't change anything, run a normal replace just to be sure - if($new_value == $value) for($i = 0; $i < count($search); $i++) { - if(is_string($this->search[$i]) || !$this->getOption(self::OPTION_VAR_MATCH_STRICT)) { - $new_value = $this->getOption(self::OPTION_CASE_INSENSITIVE) ? str_ireplace((string) $search[$i], (string) $replace[$i], $new_value) : str_replace((string) $search[$i], (string) $replace[$i], $new_value); - } - } - } else for($i = 0; $i < count($search); $i++) { - // Do a normal search-and-replace - if(is_string($search[$i]) || !$this->getOption(self::OPTION_VAR_MATCH_STRICT)) { - $new_value = $this->getOption(self::OPTION_CASE_INSENSITIVE) ? str_ireplace((string) $search[$i], (string) $replace[$i], $new_value) : str_replace((string) $search[$i], (string) $replace[$i], $new_value); - } - } - break; - } - - // Return - return $new_value; - } - - /** - * searchReplace: Callback for serialized boolean replacement. - * @param array $matches The matches corresponding to the boolean value as provided by preg_replace_callback. - * @return string The serialized representation of the result. - */ - private function _searchReplace_preg_callback_boolean($matches) { - $result = $this->searchReplace((boolean) $matches[1]); - if(self::OPTION_VAR_CAST_REPLACE) $result = (boolean) $result; - return serialize($result); - } - - /** - * searchReplace: Callback for serialized integer replacement. - * @param array $matches The matches corresponding to the integer value as provided by preg_replace_callback. - * @return string The serialized representation of the result. - */ - private function _searchReplace_preg_callback_integer($matches) { - $result = $this->searchReplace((integer) $matches[1]); - if(self::OPTION_VAR_CAST_REPLACE) $result = (integer) $result; - return serialize($result); - } - - /** - * searchReplace: Callback for serialized float replacement. - * @param array $matches The matches corresponding to the float value as provided by preg_replace_callback. - * @return string The serialized representation of the result. - */ - private function _searchReplace_preg_callback_float($matches) { - $result = $this->searchReplace((float) ($matches[1].'.'.$matches[2])); - if(self::OPTION_VAR_CAST_REPLACE) $result = (float) $result; - return serialize($result); - } - - /** - * searchReplace: Callback for serialized object name replacement. - * @param array $matches The matches corresponding to the object name value as provided by preg_replace_callback. - * @return string The serialized representation of the result. - */ - private function _searchReplace_preg_callback_objectname($matches) { - $name = preg_replace('/[^a-zA-Z0-9_\x7F-\xFF]+/', '', (string) $this->searchReplace($matches[1])); - return 'O:' . strlen($name) . ':"' . $name . '":' . $matches[2] . ':{' . $matches[3] . '}'; - } - - /** - * searchReplace: Callback for serialized string replacement. - * @param array $matches The matches corresponding to the string value as provided by preg_replace_callback. - * @return string The serialized representation of the result. - */ - private function _searchReplace_preg_callback_string($matches) { - $result = $this->searchReplace($matches[1]); - if(self::OPTION_VAR_CAST_REPLACE) $result = (string) $result; - return serialize($result); - } - } +/** + * DBSR provides functionality for commiting search-and-replace-operations on MySQL databases. + */ +class DBSR { + /* Constants */ + /** + * Version string indicating the DBSR version. + * @var string + */ + const VERSION = '2.1.1'; + + /** + * Option: use case-insensitive search and replace. + * @var boolean + */ + const OPTION_CASE_INSENSITIVE = 0; + + /** + * Option: process *all* database rows. + * @var boolean + */ + const OPTION_EXTENSIVE_SEARCH = 1; + + /** + * Option: number of rows to process simultaneously. + * @var integer + */ + const OPTION_SEARCH_PAGE_SIZE = 2; + + /** + * Option: use strict matching. + * @var boolean + */ + const OPTION_VAR_MATCH_STRICT = 3; + + /** + * Option: up to how many decimals floats should be matched. + * @var integer + */ + const OPTION_FLOATS_PRECISION = 4; + + /** + * Option: automatically convert character sets. + * @var boolean + */ + const OPTION_CONVERT_CHARSETS = 5; + + /** + * Option: cast all replace-values to the original type. + * @var boolean + */ + const OPTION_VAR_CAST_REPLACE = 6; + + /** + * Option: write changed values back to the database. + * @var boolean + */ + const OPTION_DB_WRITE_CHANGES = 7; + + /** + * Option: interpret serialized strings as PHP types. + * @var boolean + */ + const OPTION_HANDLE_SERIALIZE = 8; + + /** + * Option: reverses the filters causing to search *only* in mentioned tables/columns. + * @var array + */ + const OPTION_REVERSED_FILTERS = 9; + + /* Static methods */ + /** + * Creates a new class with the given name if it does not exists. + * + * @param string $className The name of the class. + */ + public static function createClass($className) { + if(!class_exists($className, FALSE)) { + eval('class ' . $className . ' {}'); + } + } + + /** + * Returns the PHP type for any MySQL type according to the PHP's settype() documentation. + * Will return 'string' for unknown / invalidly formatted types. + * + * @see http://php.net/manual/en/function.settype.php + * + * @param string $mysql_type The MySQL type. + * + * @return string The corresponding PHP type. + */ + public static function getPHPType($mysql_type) { + // MySQL type regexes and corresponding PHP type + $types = array( + /* Boolean types */ + '/^\s*BOOL(EAN)?\s*$/i' => 'boolean', + + /* Integer types */ + '/^\s*TINYINT\s*(?:\(\s*\d+\s*\)\s*)?$/i' => 'integer', + '/^\s*SMALLINT\s*(?:\(\s*\d+\s*\)\s*)?$/i' => 'integer', + '/^\s*MEDIUMINT\s*(?:\(\s*\d+\s*\)\s*)?$/i' => 'integer', + '/^\s*INT(EGER)?\s*(?:\(\s*\d+\s*\)\s*)?$/i' => 'integer', + '/^\s*BIGINT\s*(?:\(\s*\d+\s*\)\s*)?$/i' => 'integer', + + /* Float types */ + '/^\s*FLOAT\s*(?:\(\s*\d+\s*(?:,\s*\d+\s*)?\)\s*)?$/i' => 'float', + '/^\s*DOUBLE(\s+PRECISION)?\s*(?:\(\s*\d+\s*(?:,\s*\d+\s*)?\)\s*)?$/i' => 'float', + '/^\s*REAL\s*(?:\(\s*\d+\s*(?:,\s*\d+\s*)?\)\s*)?$/i' => 'float', + '/^\s*DEC(IMAL)?\s*(?:\(\s*\d+\s*(?:,\s*\d+\s*)?\)\s*)?$/i' => 'float', + '/^\s*NUMERIC\s*(?:\(\s*\d+\s*(?:,\s*\d+\s*)?\)\s*)?$/i' => 'float', + '/^\s*FIXED\s*(?:\(\s*\d+\s*(?:,\s*\d+\s*)?\)\s*)?$/i' => 'float', + ); + + // Try each type + foreach($types as $regex => $type) { + // Test on a whitespace-free version + if(preg_match($regex, $mysql_type)) { + return $type; + } + } + + // If nothing matches, return default (string) + return 'string'; + } + + /* Properties */ + /** + * The PDO instance used for connecting to the database. + * @var PDO + */ + protected $pdo; + + /** + * The default charset used by the database connection. + * @var string + */ + private $_pdo_charset; + + /** + * The default collation used by the database connection. + * @var string + */ + private $_pdo_collation; + + /** + * The callback used by DBRunner. + * @var callback + */ + private $_dbr_callback; + + /** + * All options of the current instance. + * @var array + */ + protected $options = array( + self::OPTION_CASE_INSENSITIVE => FALSE, + self::OPTION_EXTENSIVE_SEARCH => FALSE, + self::OPTION_SEARCH_PAGE_SIZE => 10000, + self::OPTION_VAR_MATCH_STRICT => TRUE, + self::OPTION_FLOATS_PRECISION => 5, + self::OPTION_CONVERT_CHARSETS => TRUE, + self::OPTION_VAR_CAST_REPLACE => TRUE, + self::OPTION_DB_WRITE_CHANGES => TRUE, + self::OPTION_HANDLE_SERIALIZE => TRUE, + self::OPTION_REVERSED_FILTERS => FALSE, + ); + + /** + * The filters for tables/columns. + * @var array + */ + protected $filters = array(); + + /** + * The search-values. + * @var array + */ + protected $search = array(); + + /** + * The replace-values. + * @var array + */ + protected $replace = array(); + + /** + * An array of search-values converted per charset. + * @var array + */ + protected $search_converted = array(); + + /* Methods */ + /** + * Constructor: sets the PDO instance for use with this DBSR instance. + * + * @param PDO $pdo A PDO instance representing a connection to a MySQL database. + * @throws RuntimeException If the a required PHP extension is not available. + * @throws InvalidArgumentException If the given PDO instance does not represent a MySQL database. + */ + public function __construct(PDO $pdo) { + // Check if the required PCRE library is available + if(!extension_loaded('pcre')) { + throw new RuntimeException('The pcre (Perl-compatible regular expressions) extension is required for DBSR to work!'); + } + + // Check if the PDO represents a connection to a MySQL database + if($pdo->getAttribute(PDO::ATTR_DRIVER_NAME) != 'mysql') { + throw new InvalidArgumentException('The given PDO instance is not representing an MySQL database!'); + } + + // Save the PDO instance + $this->pdo = $pdo; + } + + /** + * Returns the value of an DBSR option. + * + * @param integer $option One of the DBSR::OPTION_* constants. + * + * @return mixed The value of the requested option or NULL if unsuccessful. + */ + public function getOption($option) { + return isset($this->options[$option]) ? $this->options[$option] : NULL; + } + + /** + * Sets an option on this instance. + * + * @param integer $attribute The attribute to be set. + * @param mixed $value The new value for the given attribute. + * + * @throws PDOException If any database error occurs. + * @throws PDOException If any database error occurs. + */ + public function setOption($option, $value) { + // Only set known options + if(!isset($this->options[$option])) { + return FALSE; + } + + switch($option) { + case static::OPTION_SEARCH_PAGE_SIZE: + // Require the page size to be greater than 0 + if(is_integer($value) && $value > 0) { + $this->options[$option] = $value; + return TRUE; + } else { + return FALSE; + } + + case static::OPTION_FLOATS_PRECISION: + // Require the precision to be greater than or equal to 0 + if(is_integer($value) && $value >= 0) { + $this->options[$option] = $value; + return TRUE; + } else { + return FALSE; + } + + default: + // By default, check if the type is equal + if(gettype($this->options[$option]) == gettype($value)) { + // Allow setting the same type + $this->options[$option] = $value; + return TRUE; + } else { + // Don't allow setting the wrong type + return FALSE; + } + + } + } + + /** + * Sets the filters by which to filter tables/columns. + * + * @param array $filters The filters as an associative array. For example: + * array( + * 'entire_table', + * array( + * 'column', + * 'in', + * 'every', + * 'table', + * ), + * 'table' => 'specific_column', + * 'table' => array( + * 'specific', + * 'columns', + * ), + * ) + */ + public function setFilters(array $filters) { + // Array for the parsed filters + $filters_parsed = array(); + + // For each filter + foreach($filters as $key => $value) { + if(is_int($key)) { + if(is_string($value)) { + // Entire table + $filters_parsed[$value] = TRUE; + } elseif(is_array($value)) { + // Skip empty arrays + if(!count($value)) continue; + + // Require strings + foreach($value as $v) if(!is_string($v)) throw new InvalidArgumentException('Only strings qualify as column names!'); + + // Save it + if(isset($filters_parsed['.'])) { + $filters_parsed['.'] = array_values(array_unique(array_merge($filters_parsed['.'], array_values($value)))); + } else { + $filters_parsed['.'] = array_values(array_unique($value)); + } + } else throw new InvalidArgumentException('The filter array can only contain strings or arrays!'); + } else { + if(is_string($value)) { + // Single column + if(isset($filters_parsed[$key])) { + $filters_parsed[$key] = array_values(array_unique(array_merge($filters_parsed[$key], array($value)))); + } else { + $filters_parsed[$key] = array($value); + } + } elseif(is_array($value)) { + // Skip empty arrays + if(!count($value)) continue; + + // Require strings + foreach($value as $v) if(!is_string($v)) throw new InvalidArgumentException('Only strings qualify as column names!'); + + // Save it + if(isset($filters_parsed[$key])) { + $filters_parsed[$key] = array_values(array_unique(array_merge($filters_parsed[$key], array_values($value)))); + } else { + $filters_parsed[$key] = array_values(array_unique($value)); + } + } else throw new InvalidArgumentException('The filter array can only contain strings or arrays!'); + } + } + + // Save the parsed filters + $this->filters = $filters_parsed; + } + + /** + * Resets all filters. + */ + public function resetFilters() { + $this->filters = array(); + } + + /** + * Indicated whether the given table / column is filtered. + * @param string $table The name of the table. + * @param string $column (Optional.) Then name of the column. + */ + public function isFiltered($table, $column = NULL) { + if($this->getOption(static::OPTION_REVERSED_FILTERS)) { + // Reversed filters + if($column == NULL) { + // Never filter reversed based on table only, since there may be non-table-specific columns in it + return FALSE; + } else { + // Process columns if the entire table is filtered or if the column is filtered for either this table or in global + return !( + isset($this->filters[$table]) && $this->filters[$table] === TRUE || + isset($this->filters[$table]) && in_array($column, $this->filters[$table], TRUE) || + isset($this->filters['.']) && in_array($column, $this->filters['.'], TRUE) + ); + } + } else { + // Normal filters + if($column == NULL) { + // Only skip tables if the entire table is filtered + return isset($this->filters[$table]) && $this->filters[$table] === TRUE; + } else { + // Skip columns if the entire table is filtered or if the column is filtered for either this table or in global + return + isset($this->filters[$table]) && $this->filters[$table] === TRUE || + isset($this->filters[$table]) && in_array($column, $this->filters[$table], TRUE) || + isset($this->filters['.']) && in_array($column, $this->filters['.'], TRUE) + ; + } + } + } + + /** + * Sets the search- and replace-values. + * + * @param array $search The values to search for. + * @param array $replace The values to replace with. + * @throws InvalidArgumentException If the search- or replace-values are invalid. + */ + public function setValues(array $search, array $replace) { + // Check array lengths + if(count($search) == 0 || count($replace) == 0 || count($search) != count($replace)) { + throw new InvalidArgumentException('The number of search- and replace-values is invalid!'); + } + + // Clean indices + $search = array_values($search); + $replace = array_values($replace); + + // Remove all identical values + for($i = 0; $i < count($search); $i++) { + if($search[$i] === $replace[$i]) { + array_splice($search, $i, 1); + array_splice($replace, $i, 1); + $i--; + } + } + + // Check the length again + if(count($search) == 0) { + throw new InvalidArgumentException('All given search- and replace-values are identical!'); + } + + // Set the values + $this->search = $search; + $this->replace = $replace; + } + + /** + * Runs a search- and replace-action on the database. + * + * @throws PDOException If any database error occurs. + * @throws UnexpectedValueException If an error occurs processing data retrieved from the database. + * @return integer The number of changed rows. + */ + public function exec() { + // Remove the time limit + if(!ini_get('safe_mode') && ini_get('max_execution_time') != '0') { + set_time_limit(0); + } + + // Call the DBRunner + return $this->DBRunner(array($this, 'searchReplace')); + } + + /** + * Runs through the database and execs the provided callback on every value. + * + * @param callable $callback The callback function to call on every value. + * @param array $search (Optional.) Search value to limit the matched rows to. + * @throws PDOException If any database error occurs. + * @throws UnexpectedValueException If an error occurs processing data retrieved from the database. + * @return integer The number of changed rows. + */ + protected function DBRunner($callback) { + // Save the callback + $this->_dbr_callback = $callback; + + // Count the number of changed rows + $result = 0; + + // Set unserialize object handler + $unserialize_callback_func = ini_set('unserialize_callback_func', get_class() . '::createClass'); + + // PDO attributes to set + $pdo_attributes = array( + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION + ); + + // Set PDO attributes and save the old values + foreach($pdo_attributes as $attribute => $value) { + $pdo_attributes[$attribute] = $this->pdo->getAttribute($attribute); + $this->pdo->setAttribute($attribute, $value); + } + + // Catch all Exceptions so that we can reset the errormode before rethrowing it + try { + // Figure out the connection character set and collation + $this->_pdo_charset = $this->pdo->query('SELECT @@character_set_client;', PDO::FETCH_COLUMN, 0)->fetch(); + $this->_pdo_collation = $this->pdo->query('SELECT @@collation_connection;', PDO::FETCH_COLUMN, 0)->fetch(); + + // Get a list of all tables + $tables = $this->pdo->query('SHOW TABLES;', PDO::FETCH_COLUMN, 0)->fetchAll(); + + // Lock each table + $this->pdo->query('LOCK TABLES `' . implode('` WRITE, `', $tables) . '` WRITE;'); + + // Loop through all the (non-filtered) tables + foreach($tables as $table) { + if(!$this->isFiltered($table)) { + $result += $this->_DBRTable($table, $callback); + } + } + } catch(Exception $e) { + // Since we support PHP 5.3 we cannot use finally, thus this block is empty and we continue below + } + + // Unlock all locked tables + $this->pdo->query('UNLOCK TABLES'); + + // Restore the old PDO attribute values + foreach($pdo_attributes as $attribute => $value) { + $this->pdo->setAttribute($attribute, $value); + } + + // Reset the unserialize object handler + ini_set('unserialize_callback_func', $unserialize_callback_func); + + // Check whether an exception was thrown + if(isset($e) && $e instanceof Exception) { + // Rethrow the exception + throw $e; + } else { + // Return the results + return $result; + } + } + + /** + * DBRunner: processes the given table. + * + * @param string $table Name of the table. + * @throws UnexpectedValueException If an error occurs processing data retrieved from the database. + * @return integer The number of changed rows. + */ + private function _DBRTable($table) { + // List all columns of the current table + $columns_info = $this->pdo->query('SHOW FULL COLUMNS FROM `' . $table . '`;', PDO::FETCH_NAMED); + + // Empty arrays for columns and keys + $columns = array(); + $keys = array(); + + // Process each column + foreach($columns_info as $column_info) { + // Determine type + $columns[$column_info['Field']] = array( + 'null' => ($column_info['Null'] == 'YES'), + 'type' => static::getPHPType($column_info['Type']), + 'charset' => preg_replace('/^([a-z\d]+)_[\w\d]+$/i', '$1', $column_info['Collation']), + 'collation' => $column_info['Collation'], + ); + + // Determine wheter it's part of a candidate key + $keys[$column_info['Key']][$column_info['Field']] = $columns[$column_info['Field']]; + } + + // Determine prefered candidate key + if(isset($keys['PRI'])) { + // Always prefere a primary key(set) + $keys = $keys['PRI']; + } elseif(isset($keys['UNI'])) { + // Though a unique key(set) also works + $keys = $keys['UNI']; + } else { + // If everything else fails, use the full column set + $keys = $columns; + } + + // Filter columns + foreach($columns as $column => $column_info) { + if($this->isFiltered($table, $column)) { + unset($columns[$column]); + } + } + + // Prepare a smart WHERE-statement + if(!$this->getOption(static::OPTION_EXTENSIVE_SEARCH)) { + $where = $this->_DBRWhereSearch($columns); + } else { + // No WHERE-statement + $where = ''; + } + + // Check if after filtering and WHERE-matching any valid columns are left + if(count($columns) == 0) { + return; + } + + // Convert search-values to the correct charsets + if($this->getOption(static::OPTION_CONVERT_CHARSETS)) { + foreach($columns as $column => $column_info) { + if(!isset($this->search_converted[$column_info['charset']]) && $column_info['type'] == 'string' && !empty($column_info['charset']) && $column_info['charset'] != $this->_pdo_charset) { + foreach($this->search as $i => $item) { + if(is_string($item)) { + $this->search_converted[$column_info['charset']][$i] = $this->pdo->query('SELECT CONVERT(_' . $this->_pdo_charset . $this->pdo->quote($item) . ' USING ' . $column_info['charset'] . ');', PDO::FETCH_COLUMN, 0)->fetch(); + } else { + $this->search_converted[$column_info['charset']][$i] = $item; + } + } + } + } + } + + // Get the number of rows + $row_count = (int) $this->pdo->query('SELECT COUNT(*) FROM `' . $table . '`' . $where . ';', PDO::FETCH_COLUMN, 0)->fetch(); + + // Count the number of changed rows + $row_change_count = 0; + + // For each page + $page_size = $this->getOption(static::OPTION_SEARCH_PAGE_SIZE); + for($page_start = 0; $page_start < $row_count; $page_start += $page_size) { + // Get the rows of this page + $rows = $this->pdo->query('SELECT DISTINCT * FROM `' . $table . '`' . $where . 'LIMIT ' . $page_start . ', ' . $page_size . ';', PDO::FETCH_ASSOC); + + // Loop over each row + foreach($rows as $row) { + if($this->_DBRRow($table, $columns, $keys, $row) > 0) { + $row_change_count++; + } + } + } + + // Return the number of changed rows + return $row_change_count; + } + + /** + * DBRunner: processes the given row. + * + * @param string $table The name of the current table. + * @param array $columns The relevant columns of this table. + * @param array $keys The candidate keyset for this table. + * @param array $row The row to be processed. + * @throws UnexpectedValueException If an error occurs processing data retrieved from the database. + * @return integer The number of changed columns. + */ + private function _DBRRow($table, array $columns, array $keys, array $row) { + // Array with row changes + $changeset = array(); + + // Convert columns + foreach($columns + $keys as $column => $column_info) { + if(!settype($row[$column], $column_info['type'])) { + throw new UnexpectedValueException('Failed to convert `' . $table . '`.`' . $column . '` value to a ' . $column_info['type'] . ' for value "' . $row[$column] . '"!'); + } + } + + // Loop over each column + foreach($columns as $column => $column_info) { + // Set the value + $value = &$row[$column]; + + // Call the callback + if($this->getOption(static::OPTION_CONVERT_CHARSETS) && isset($this->search_converted[$column_info['charset']])) { + $value_new = call_user_func($this->_dbr_callback, $value, $this->search_converted[$column_info['charset']], $this->replace); + } else { + $value_new = call_user_func($this->_dbr_callback, $value); + } + + // Check the result + if($value_new !== $value) { + $changeset[$column] = $value_new; + } + } + + // Update the row if nessecary + if(count($changeset) > 0 && $this->getOption(static::OPTION_DB_WRITE_CHANGES)) { + // Build the WHERE-statement for this row + $where = $this->_DBRWhereRow($keys, $row); + + // Determine the updates + $updates = array(); + foreach($changeset as $column => $value_new) { + switch($columns[$column]['type']) { + case 'integer': + $updates[] = '`' . $column . '` = ' . (int) $value_new; + break; + + case 'float': + $updates[] = '`' . $column . '` = ' . (string) round((float) $value_new, $this->getOption(static::OPTION_FLOATS_PRECISION)); + break; + + case 'string': + default: + // First, escape the string and add quotes + $update_string = $this->pdo->quote((string) $value_new); + + // Then, check the charset + if(!empty($columns[$column]['charset']) && $this->_pdo_charset != $columns[$column]['charset']) { + if($this->getOption(static::OPTION_CONVERT_CHARSETS)) { + $update_string = 'CONVERT(_' . $this->_pdo_charset . $update_string . ' USING ' . $columns[$column]['charset'] . ')'; + } else { + $update_string = 'BINARY ' . $update_string; + } + } + + // Then, check the collation + if(!empty($columns[$column]['collation']) && $this->getOption(static::OPTION_CONVERT_CHARSETS) && $this->_pdo_collation != $columns[$column]['collation']) { + $update_string .= ' COLLATE ' . $columns[$column]['collation']; + } + + // Finally, build and add the comparison for the WHERE-clause + $updates[] = '`' . $column . '` = ' . $update_string; + break; + } + } + + // Commit the updates + $this->pdo->query('UPDATE `' . $table . '` SET ' . implode(', ', $updates) . $where . ';'); + } + + // Return the number of changed columns + return count($changeset); + } + + /** + * DBRunner: constructs the WHERE-clause for searching. + * + * @param array $columns (Reference.) The columns to be searched. Inegible columns will be removed. + * @return mixed String with the constructed WHERE-clause, or FALSE if no column could be matched + * (thus the table may be skipped). + */ + private function _DBRWhereSearch(array &$columns) { + // Array for WHERE-clause elements + $where = array(); + + // Loop over all columns + foreach($columns as $column => $column_info) { + // By default there's no reason to include this column + $where_column = FALSE; + + // Loop over all search items + foreach($this->search as $item) { + // If there's a valid WHERE-component, add it + if($where_component = $this->_DBRWhereColumn($column, $column_info, $item, FALSE)) { + $where[] = $where_component; + $where_column = TRUE; + } + } + + // Remove all columns which will never match since no valid WHERE-components could be constructed + if(!$where_column) { + unset($columns[$column]); + } + } + + // Combine the WHERE-clause or empty it + if(count($where) > 0) { + return ' WHERE ' . implode(' OR ', $where) . ' '; + } else { + // Assert count($columns) == 0 + if(count($columns) != 0) { + throw new LogicException('No WHERE-clause was constructed, yet there are valid columns left!'); + } + + // Since there are no valid columns left, we can skip processing this table + return FALSE; + } + } + + /** + * DBRunner: Constructs a WHERE-clause for the given row. + * + * @param array $keys The candidate keys to be used for constructing the WHERE-clause. + * @param array $row The row values. + * @return string The WHERE-clause for the given row. + */ + private function _DBRWhereRow(array $keys, array $row) { + $where = array(); + foreach($keys as $key => $key_info) { + $where[] = $this->_DBRWhereColumn($key, $key_info, $row[$key], TRUE); + } + return ' WHERE ' . implode(' AND ', $where) . ' '; + } + + /** + * DBRunner: Constructs a WHERE component for the given column and value. + * + * @param string $column The column name. + * @param array $column_info Array with column info. + * @param mixed $value The value to match. + * @param boolean $string_exact Whether to use 'LIKE %value%'-style matching. + * + * @return mixed The WHERE component for the given parameters as a string, + * or FALSE if the value is not valid for the given column. + */ + private function _DBRWhereColumn($column, array $column_info, $value, $string_exact) { + switch($column_info['type']) { + case 'integer': + // Search for integer value + if(!$this->getOption(static::OPTION_VAR_MATCH_STRICT) || is_integer($value)) { + // Add a where clause for the integer value + return '`' . $column . '` = ' . (int) $value; + } + break; + + case 'float': + // Search for float difference (since floats aren't precise enough to compare directly) + if(!$this->getOption(static::OPTION_VAR_MATCH_STRICT) || is_float($value)) { + return 'ABS(`' . $column . '` - ' . (float) $value . ') < POW(1, -' . $this->getOption(static::OPTION_FLOATS_PRECISION) . ')'; + } + break; + + case 'string': + default: + // String search is even harder given the many possibly charsets + + // If the search item is a float, we have to limit it to the maximum precision first + if(is_float($value)) { + $value = round($value, $this->getOption(static::OPTION_FLOATS_PRECISION)); + } + + if(!$string_exact) { + $value = '%' . (string) $value . '%'; + } + + // First, escape the string and add quotes + $where_string = $this->pdo->quote((string) $value); + + // Then, check the charset + if(!empty($column_info['charset']) && $this->_pdo_charset != $column_info['charset']) { + if($this->getOption(static::OPTION_CONVERT_CHARSETS)) { + $where_string = 'CONVERT(_' . $this->_pdo_charset . $where_string . ' USING ' . $column_info['charset'] . ')'; + } else { + $where_string = 'BINARY ' . $where_string; + } + } + + // Then, check the collation + if(!empty($column_info['collation']) && $this->getOption(static::OPTION_CONVERT_CHARSETS) && $this->_pdo_collation != $column_info['collation']) { + if($this->getOption(static::OPTION_CASE_INSENSITIVE)) { + $where_string .= ' COLLATE ' . preg_replace('/_cs$/i', '_ci', $column_info['collation']); + } else { + $where_string .= ' COLLATE ' . $column_info['collation']; + } + } + + // Column name + $column = '`' . $column . '`'; + + // Case insensitivity + if(!empty($column_info['collation']) && $this->getOption(static::OPTION_CASE_INSENSITIVE) && preg_replace('/^.*_([a-z]+)$/i', '$1', $column_info['collation']) == 'cs') { + $column .= ' COLLATE ' . preg_replace('/_cs$/i', '_ci', $column_info['collation']); + } + + // Add the column + $where_string = $column . ' ' . ($string_exact ? '=' : 'LIKE') . ' ' . $where_string; + + if(!empty($column_info['charset']) && !$this->getOption(static::OPTION_CONVERT_CHARSETS) && $this->_pdo_charset != $column_info['charset']) { + $where_string = 'BINARY ' . $where_string; + } + + // Finally, build and add the comparison for the WHERE-clause + return $where_string; + } + + // It seems the value was not valid for this column + return FALSE; + } + + /** + * Runs a search-and-replace action on the provided value. + * + * @var mixed $value The value to search through. + * @return mixed The value with all occurences of search items replaced. + */ + protected function searchReplace($value) { + // The new value + $new_value = $value; + + // For each type + switch(TRUE) { + case is_array($value): + // The result is also an array + $new_value = array(); + // Loop through all the values + foreach($value as $key => $element) { + $new_value[$this->searchReplace($key)] = $this->searchReplace($element); + } + break; + + case is_bool($value): + for($i = 0; $i < count($this->search); $i++) { + if($new_value === $this->search[$i] || !$this->getOption(static::OPTION_VAR_MATCH_STRICT) && $new_value == $this->search[$i]) { + $new_value = $this->replace[$i]; + } + } + break; + + case is_float($value): + $float_precision = pow(10, -1 * $this->getOption(static::OPTION_FLOATS_PRECISION)); + for($i = 0; $i < count($this->search); $i++) { + if( is_float($this->search[$i]) && abs($new_value - $this->search[$i]) < $float_precision || + !$this->getOption(static::OPTION_VAR_MATCH_STRICT) && ( + $new_value == $this->search[$i] || + abs($new_value - (float) $this->search[$i]) < $float_precision + ) + ) { + $new_value = $this->replace[$i]; + } + } + break; + + case is_int($value): + for($i = 0; $i < count($this->search); $i++) { + if($new_value === $this->search[$i] || !$this->getOption(static::OPTION_VAR_MATCH_STRICT) && $new_value == $this->search[$i]) { + $new_value = $this->replace[$i]; + } + } + break; + + case is_object($value): + // Abuse the fact that corrupted serialized strings are handled by our own regexes + $new_value = unserialize($this->searchReplace(preg_replace('/^O:\\d+:/', 'O:0:', serialize($new_value)))); + break; + + case is_string($value): + // Regex for detecting serialized strings + $serialized_regex = '/^(?:a:\\d+:\\{.*\\}|b:[01];|d:\\d+\\.\\d+;|i:\\d+;|N;|O:\\d+:"[a-zA-Z_\\x7F-\\xFF][a-zA-Z0-9_\\x7F-\\xFF]*(?:\\\\[a-zA-Z_\\x7F-\\xFF][a-zA-Z0-9_\\x7F-\\xFF]*)*":\\d+:\\{.*\\}|s:\\d+:".*";)$/Ss'; + + // Try unserializing it + $unserialized = @unserialize($new_value); + + // Check if if actually was unserialized + if($this->getOption(static::OPTION_HANDLE_SERIALIZE) && ($unserialized !== FALSE || $new_value === serialize(FALSE)) && !is_object($unserialized)) { + // Process recursively + $new_value = serialize($this->searchReplace($unserialized)); + } elseif($this->getOption(static::OPTION_HANDLE_SERIALIZE) && (is_object($unserialized) || preg_match($serialized_regex, $new_value))) { + // If it looks like it's serialized, use special regexes for search-and-replace + + // TODO: split arrays/objects and process recursively? + + // Search and replace booleans + if($changed_value = preg_replace_callback('/b:([01]);/S', array($this, '_searchReplace_preg_callback_boolean'), $new_value)) { + $new_value = $changed_value; + } + + // Search and replace integers + if($changed_value = preg_replace_callback('/i:(\\d+);/S', array($this, '_searchReplace_preg_callback_integer'), $new_value)) { + $new_value = $changed_value; + } + + // Search and replace floats + if($changed_value = preg_replace_callback('/d:(\\d+)\.(\\d+);/S', array($this, '_searchReplace_preg_callback_float'), $new_value)) { + $new_value = $changed_value; + } + + // Search-and-replace object names (and update length) + if($changed_value = preg_replace_callback('/O:\\d+:"([a-zA-Z_\\x7F-\\xFF][a-zA-Z0-9_\\x7F-\\xFF]*(?:\\\\[a-zA-Z_\\x7F-\\xFF][a-zA-Z0-9_\\x7F-\\xFF]*)*)":(\\d+):{(.*)}/Ss', array($this, '_searchReplace_preg_callback_objectname'), $new_value)) { + $new_value = $changed_value; + } + + // Search-and-replace strings (and update length) + if($changed_value = preg_replace_callback('/s:\\d+:"(.*?|a:\\d+:{.*}|b:[01];|d:\\d+\\.\\d+;|i:\d+;|N;|O:\\d+:"[a-zA-Z_\\x7F-\\xFF][a-zA-Z0-9_\\x7F-\\xFF]*":\\d+:{.*}|s:\\d+:".*";)";/Ss', array($this, '_searchReplace_preg_callback_string'), $new_value)) { + $new_value = $changed_value; + } + + // If the regexes didn't change anything, run a normal replace just to be sure + if($new_value == $value) { + for($i = 0; $i < count($this->search); $i++) { + if(is_string($this->search[$i]) || !$this->getOption(static::OPTION_VAR_MATCH_STRICT)) { + $new_value = $this->getOption(static::OPTION_CASE_INSENSITIVE) ? str_ireplace((string) $this->search[$i], (string) $this->replace[$i], $new_value) : str_replace((string) $this->search[$i], (string) $this->replace[$i], $new_value); + } + } + } + } else { + for($i = 0; $i < count($this->search); $i++) { + // Do a normal search-and-replace + if(is_string($this->search[$i]) || !$this->getOption(static::OPTION_VAR_MATCH_STRICT)) { + $new_value = $this->getOption(static::OPTION_CASE_INSENSITIVE) ? str_ireplace((string) $this->search[$i], (string) $this->replace[$i], $new_value) : str_replace((string) $this->search[$i], (string) $this->replace[$i], $new_value); + } + } + } + break; + } + + // Return + return $new_value; + } + + /** + * searchReplace: Callback for serialized boolean replacement. + * @param array $matches The matches corresponding to the boolean value as provided by preg_replace_callback. + * @return string The serialized representation of the result. + */ + private function _searchReplace_preg_callback_boolean($matches) { + $result = $this->searchReplace((boolean) $matches[1]); + if(static::OPTION_VAR_CAST_REPLACE) { + $result = (boolean) $result; + } + return serialize($result); + } + + /** + * searchReplace: Callback for serialized integer replacement. + * @param array $matches The matches corresponding to the integer value as provided by preg_replace_callback. + * @return string The serialized representation of the result. + */ + private function _searchReplace_preg_callback_integer($matches) { + $result = $this->searchReplace((integer) $matches[1]); + if(static::OPTION_VAR_CAST_REPLACE) { + $result = (integer) $result; + } + return serialize($result); + } + + /** + * searchReplace: Callback for serialized float replacement. + * @param array $matches The matches corresponding to the float value as provided by preg_replace_callback. + * @return string The serialized representation of the result. + */ + private function _searchReplace_preg_callback_float($matches) { + $result = $this->searchReplace((float) ($matches[1].'.'.$matches[2])); + if(static::OPTION_VAR_CAST_REPLACE) { + $result = (float) $result; + } + return serialize($result); + } + + /** + * searchReplace: Callback for serialized object name replacement. + * @param array $matches The matches corresponding to the object name value as provided by preg_replace_callback. + * @return string The serialized representation of the result. + */ + private function _searchReplace_preg_callback_objectname($matches) { + $name = preg_replace('/[^a-zA-Z0-9_\\x7F-\\xFF\\\\]+/', '', (string) $this->searchReplace($matches[1])); + return 'O:' . strlen($name) . ':"' . $name . '":' . $matches[2] . ':{' . $matches[3] . '}'; + } + + /** + * searchReplace: Callback for serialized string replacement. + * @param array $matches The matches corresponding to the string value as provided by preg_replace_callback. + * @return string The serialized representation of the result. + */ + private function _searchReplace_preg_callback_string($matches) { + $result = $this->searchReplace($matches[1]); + if(static::OPTION_VAR_CAST_REPLACE) { + $result = (string) $result; + } + return serialize($result); + } +} diff --git a/DBSR_CLI.php b/DBSR_CLI.php index 3f07a54..72356e4 100644 --- a/DBSR_CLI.php +++ b/DBSR_CLI.php @@ -1,547 +1,567 @@ . - */ - - /** - * DBSR_CLI provides a CLI interface for the DBSR class. - * - * @author Daniël van de Giessen - * @package DBSR - * @version 2.0.2 - */ - class DBSR_CLI { - /* Constants */ - /** - * Version string indicating the DBSR CLI version. - * @var string - */ - const VERSION = '2.0.2'; - - /* Static properties */ - /** - * Options available as parameters and their default values. - * @var array - */ - protected static $default_options = array( - 'CLI' => array( - 'help' => array( - 'name' => array('help', 'h', '?'), - 'parameter' => NULL, - 'description' => 'display this help and exit', - 'default_value' => NULL, - ), - 'version' => array( - 'name' => array('version', 'v'), - 'parameter' => NULL, - 'description' => 'print version information and exit', - 'default_value' => NULL, - ), - 'file' => array( - 'name' => array('file', 'configfile', 'config', 'f'), - 'parameter' => 'FILENAME', - 'description' => 'JSON-encoded configfile to load', - 'default_value' => NULL, - ), - 'output' => array( - 'name' => array('output', 'o'), - 'parameter' => 'text|json', - 'description' => 'output format', - 'default_value' => 'text', - ), - ), - - 'PDO' => array( - 'host' => array( - 'name' => array('host', 'hostname'), - 'parameter' => 'HOSTNAME', - 'description' => 'hostname of the MySQL server', - 'default_value' => NULL, - ), - 'port' => array( - 'name' => array('port', 'portnumber'), - 'parameter' => 'PORTNUMBER', - 'description' => 'port number of the MySQL server', - 'default_value' => NULL, - ), - 'user' => array( - 'name' => array('user', 'username', 'u'), - 'parameter' => 'USERNAME', - 'description' => 'username used for connecting to the MySQL server', - 'default_value' => NULL, - ), - 'password' => array( - 'name' => array('password', 'pass', 'p'), - 'parameter' => 'PASSWORD', - 'description' => 'password used for connecting to the MySQL server', - 'default_value' => NULL, - ), - 'database' => array( - 'name' => array('database', 'db', 'd'), - 'parameter' => 'DATABASE', - 'description' => 'name of the database to be searched', - 'default_value' => NULL, - ), - 'charset' => array( - 'name' => array('charset', 'characterset', 'char'), - 'parameter' => 'CHARSET', - 'description' => 'character set used for connecting to the MySQL server', - 'default_value' => NULL, - ), - ), - - 'DBSR' => array( - DBSR::OPTION_CASE_INSENSITIVE => array( - 'name' => 'case-insensitive', - 'parameter' => '[true|false]', - 'description' => 'use case-insensitive search and replace', - 'default_value' => FALSE, - ), - DBSR::OPTION_EXTENSIVE_SEARCH => array( - 'name' => 'extensive-search', - 'parameter' => '[true|false]', - 'description' => 'process *all* database rows', - 'default_value' => FALSE, - ), - DBSR::OPTION_SEARCH_PAGE_SIZE => array( - 'name' => 'search-page-size', - 'parameter' => 'SIZE', - 'description' => 'number of rows to process simultaneously', - 'default_value' => 10000, - ), - DBSR::OPTION_VAR_MATCH_STRICT => array( - 'name' => 'var-match-strict', - 'parameter' => '[true|false]', - 'description' => 'use strict matching', - 'default_value' => TRUE, - ), - DBSR::OPTION_FLOATS_PRECISION => array( - 'name' => 'floats-precision', - 'parameter' => 'PRECISION', - 'description' => 'up to how many decimals floats should be matched', - 'default_value' => 5, - ), - DBSR::OPTION_CONVERT_CHARSETS => array( - 'name' => 'convert-charsets', - 'parameter' => '[true|false]', - 'description' => 'automatically convert character sets', - 'default_value' => TRUE, - ), - DBSR::OPTION_VAR_CAST_REPLACE => array( - 'name' => 'var-cast-replace', - 'parameter' => '[true|false]', - 'description' => 'cast all replace-values to the original type', - 'default_value' => TRUE, - ), - DBSR::OPTION_DB_WRITE_CHANGES => array( - 'name' => 'db-write-changes', - 'parameter' => '[true|false]', - 'description' => 'write changed values back to the database', - 'default_value' => TRUE, - ), - DBSR::OPTION_HANDLE_SERIALIZE => array( - 'name' => 'handle-serialize', - 'parameter' => '[true|false]', - 'description' => 'interpret serialized strings as their PHP types', - 'default_value' => TRUE, - ), - ), - ); - - /* Static methods */ - /** - * Prints the version string. - */ - public static function printVersion() { - echo 'DBSR CLI v', self::VERSION, ', based on DBSR v' . DBSR::VERSION . ', running on PHP ', PHP_VERSION, ' (', PHP_SAPI, '), ', PHP_OS, '.', "\n"; - } - - /** - * Prints the help text based on $default_options. - * - * @param $filename The filename to display, NULL for autodetect using $argv. - */ - public static function printHelp($filename = NULL) { - $pad_left = 4; - $width_left = 40; - $width_right = 32; - if(is_null($filename)) $filename = isset($_SERVER['argv']) && is_array($_SERVER['argv']) ? $_SERVER['argv'][0] : basename($_SERVER['SCRIPT_NAME']); - - self::printVersion(); - - echo "\n", - 'Usage: ', $filename, ' [options] -- SEARCH REPLACE [SEARCH REPLACE...]', "\n" . - ' ', $filename, ' --file FILENAME', "\n" . - "\n"; - foreach(self::$default_options as $name => $optionset) { - echo $name, ' options:', "\n"; - foreach($optionset as $key => $option) { - // Force type to array - $option['name'] = (array) $option['name']; - - // Option - $parameter = (strlen($option['name'][0]) > 1 ? '--' : '-') . $option['name'][0]; - - // Parameter - if(!is_null($option['parameter'])) $parameter .= ' ' . $option['parameter']; - - // Description - $description_array = preg_split('/(.{1,' . $width_right . '}(?:\s(?!$)|(?=$)))/', $option['description'], NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); - $description = $description_array[0]; - for($i = 1; $i < count($description_array); $i++) { - $description .= "\n" . str_repeat(' ', $width_left + $pad_left) . $description_array[$i]; - } - - // Default - if(!is_null($option['default_value'])) { - $default = $option['default_value']; - if(is_bool($default)) { - $default = $default ? 'true' : 'false'; - } else { - $default = (string) $default; - } - $default = ' (default: ' . $default . ')'; - if(strlen($description_array[count($description_array) - 1]) + strlen($default) > $width_right) { - $description .= "\n" . str_repeat(' ', $width_left + $pad_left - 1); - } - $description .= $default; - } - - // Echo the option - echo str_repeat(' ', $pad_left), str_pad($parameter, $width_left), $description, "\n"; - } - } - } - - /** - * Returns the corresponding default option given a switch name. - * - * @param string $switch The switch to search for. - * @param boolean $check_prefix Whether to check for the correct prefix. - * @return mixed The option array, or FALSE if the switch was not found. - */ - protected static function getOption($switch, $check_prefix = TRUE) { - foreach(self::$default_options as $setname => $set) { - foreach($set as $id => $option) { - foreach((array) $option['name'] as $name) if($switch == ($check_prefix ? (strlen($name) > 1 ? ('--' . $name) : ('-' . $name)) : $name)) { - $option['set'] = $setname; - $option['id'] = $id; - return $option; - } - } - } - return FALSE; - } - - /* Properties */ - /** - * @var PDO - */ - protected $pdo; - - /** - * @var DBSR - */ - protected $dbsr; - - /** - * Options currently set. - * @var array - */ - protected $options = array(); - - /** - * The search-values. - * @var array - */ - protected $search = array(); - - /** - * The replace-values. - * @var array - */ - protected $replace = array(); - - /** - * List of configfiles which have been processed. - * Used to reprevent recursive inclusion. - * - * @var array - */ - private $configfiles = array(); - - /* Methods */ - /** - * Constructor: builds a new DBSR_CLI object and initalizes all options to their defaults - */ - public function __construct() { - foreach(self::$default_options as $setname => $set) { - foreach($set as $id => $option) { - if(!is_null($option['default_value'])) $this->options[$setname][$id] = $option['default_value']; - } - } - } - - /** - * Executes DBSR with the currently set options. Does not return but dies with the result. - */ - public function exec() { - // Prepare the DSN and PDO options array - $pdo_options = array( - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, - ); - - $dsn = 'mysql:'; - if(isset($this->options['PDO']['host'])) { - $dsn .= 'host=' . $this->options['PDO']['host']; - if(isset($this->options['PDO']['port'])) { - $dsn .= ':' . $this->options['PDO']['port']; - } - $dsn .= ';'; - } - if(isset($this->options['PDO']['database'])) $dsn .= 'dbname=' . $this->options['PDO']['database'] . ';'; - if(isset($this->options['PDO']['charset'])) { - $pdo_options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES ' . $this->options['PDO']['charset']; - $dsn .= 'charset=' . $this->options['PDO']['charset'] . ';'; - } - - try { - // Try building a PDO, DBSR and running the search- and replace-action - $this->pdo = new PDO($dsn, @$this->options['PDO']['user'], @$this->options['PDO']['password'], $pdo_options); - $this->dbsr = new DBSR($this->pdo); - - // Set the DBSR options - foreach($this->options['DBSR'] as $option => $value) { - $this->dbsr->setOption($option, $value); - } - - // Set the search- and replace-values - $this->dbsr->setValues($this->search, $this->replace); - - // Execute DBSR - $result = $this->dbsr->exec(); - } catch(Exception $e) { - // Check the output type for the exception - switch($this->options['CLI']['output']) { - case 'text': - die($e->getMessage()); - - case 'json': - die(json_encode(array('error' => $e->getMessage()))); - } - } - - // Output the result - switch($this->options['CLI']['output']) { - case 'text': - die('Result: ' . $result . ' rows were ' . ($this->options['DBSR'][DBSR::OPTION_DB_WRITE_CHANGES] ? 'changed' : 'matched (no changes were written to the databasse)') . '!'); - - case 'json': - die(json_encode(array('result' => $result))); - } - } - - /** - * Parses command line arguments. Directly outputs and dies in case of errors. - * - * @param array $arguments The array of arguments, the first element being the script's filename. - */ - public function parseArguments(array $arguments) { - if(empty($arguments)) $arguments = isset($_SERVER['argv']) && is_array($_SERVER['argv']) ? $_SERVER['argv'] : array(basename($_SERVER['SCRIPT_NAME'])); - - // Check if there are no arguments - if(count($arguments) <= 1) { - echo 'Usage: ', $arguments[0], ' [options] -- SEARCH REPLACE [SEARCH REPLACE...]', "\n" . - ' ', $arguments[0], ' --file FILENAME', "\n" . - 'Try `', $arguments[0], ' --help` for more information.', "\n"; - die(); - } - - // Loop over all arguments - for($i = 1; $i < count($arguments); $i++) { - switch($arguments[$i]) { - case '--': - // Check the number of search- and replace-values - if(count($arguments) - 1 - $i == 0) { - die('Missing search- and replace-values!'); - } - if((count($arguments) - 1 - $i) % 2 != 0) { - die('Missing replace-value for seach-value: ' . (string) $arguments[count($arguments) - 1]); - } - - // Save all search- and replace-values - for(++$i; $i < count($arguments); $i++) { - $this->search[] = $arguments[$i]; - $this->replace[] = $arguments[++$i]; - } - break; - - default: - // Get the option - $option = self::getOption($arguments[$i]); - if(!$option) die('Unknown argument: ' . (string) $arguments[$i]); - - // Check for any arguments - if(!is_null($option['parameter'])) { - $arg = @$arguments[$i + 1]; - - // Boolean value without argument? - if(is_bool($option['default_value']) && (is_null($arg) || preg_match('/^\-/', $arg))) { - $this->options[$option['set']][$option['id']] = !$option['default_value']; - break; - } - - // Missing argument? - if(is_null($arg) || preg_match('/^\-/', $arg)) { - die('Missing option for ' . (string) $arguments[$i]); - } - - // Special cases - switch($option['set'] . '/' . $option['id']) { - case 'CLI/file': - if(!$this->parseConfig($arg)) die('Failed to parse config file: ' . (string) $arg); - $i++; - break 2; - } - - // Parse the argument - if(!is_null($option['default_value'])) { - // Special cases and specific error messages - if(is_bool($option['default_value'])) { - if(strtolower($arg) == 'true') { - $arg = TRUE; - } elseif(strtolower($arg) == 'false') { - $arg = FALSE; - } elseif(is_numeric($arg)) { - $arg = (bool) (int) $arg; - } else { - die('Invalid argument, expected boolean for ' . (string) $arguments[$i]); - } - } elseif(is_int()) { - if(is_numeric($arg)) { - $arg = (int) $arg; - } else { - die('Invalid argument, expected integer for ' . (string) $arguments[$i]); - } - } elseif(is_float()) { - if(is_numeric($arg)) { - $arg = (float) $arg; - } else { - die('Invalid argument, expected float for ' . (string) $arguments[$i]); - } - } - - // Typeset - settype($arg, gettype($option['default_value'])); - } - - // Save the argument - $this->options[$option['set']][$option['id']] = $arg; - $i++; - } else - // Special cases - switch($option['set'] . '/' . $option['id']) { - case 'CLI/help': - die(self::printHelp($arguments[0])); - - case 'CLI/version': - die(self::printVersion()); - } - break; - } - } - } - - /** - * Parses a config file - * @param string $file Path to the config file. - */ - public function parseConfig($file) { - // Check if the file exists - if(!file_exists($file) || !realpath($file)) return FALSE; - - // Check if we've read the file before - if(in_array(realpath($file), $this->configfiles)) return FALSE; - $this->configfiles[] = realpath($file); - - // Read file contents - $file_contents = @file_get_contents($file); - if(!$file_contents) { - return FALSE; - } - - // Decode content - $file_array = json_decode($file_contents, TRUE); - if(!is_array($file_array)) return FALSE; - - // Load search- and replace-values (if existing) - if(isset($file_array['search']) && is_array($file_array['search'])) { - $this->search += $file_array['search']; - } - if(isset($file_array['replace']) && is_array($file_array['replace'])) { - $this->replace += $file_array['replace']; - } - - // Check for options - if(isset($file_array['options']) && is_array($file_array['options'])) { - // Return success - return $this->_parseConfigArray($file_array['options']); - } else { - // No options, no problems - return TRUE; - } - - } - - /** - * parseConfig: runs through an option array and parses every option. - * - * @param array $array The array of options - * $return boolean TRUE if the array was parsed succesfully, FALSE otherwise. - */ - private function _parseConfigArray(array $array) { - foreach($array as $key => $element) { - if(is_array($element)) { - if(!$this->_parseConfigArray($element)) return FALSE; - } else { - // Check the option - $option = self::getOption($key, FALSE); - if(!$option) return FALSE; - - // Special cases without paramaters - switch($option['set'] . '/' . $option['id']) { - case 'CLI/help': - die(self::printHelp()); - - case 'CLI/version': - die(self::printVersion()); - } - - // No parameter? No go! - if(is_null($option['parameter'])) return FALSE; - - // Special cases with paramaters - switch($option['set'] . '/' . $option['id']) { - case 'CLI/file': - if(!$this->parseConfig($element)) return FALSE; - } - - // Save the value - $this->options[$option['set']][$option['id']] = $element; - } - } - return TRUE; - } - } +/** + * DBSR_CLI provides a CLI interface for the DBSR class. + */ +class DBSR_CLI { + /* Constants */ + /** + * Version string indicating the DBSR CLI version. + * @var string + */ + const VERSION = '2.0.3'; + + /* Static properties */ + /** + * Options available as parameters and their default values. + * @var array + */ + protected static $default_options = array( + 'CLI' => array( + 'help' => array( + 'name' => array('help', 'h', '?'), + 'parameter' => NULL, + 'description' => 'display this help and exit', + 'default_value' => NULL, + ), + 'version' => array( + 'name' => array('version', 'v'), + 'parameter' => NULL, + 'description' => 'print version information and exit', + 'default_value' => NULL, + ), + 'file' => array( + 'name' => array('file', 'configfile', 'config', 'f'), + 'parameter' => 'FILENAME', + 'description' => 'JSON-encoded configfile to load', + 'default_value' => NULL, + ), + 'output' => array( + 'name' => array('output', 'o'), + 'parameter' => 'text|json', + 'description' => 'output format', + 'default_value' => 'text', + ), + ), + + 'PDO' => array( + 'host' => array( + 'name' => array('host', 'hostname'), + 'parameter' => 'HOSTNAME', + 'description' => 'hostname of the MySQL server', + 'default_value' => NULL, + ), + 'port' => array( + 'name' => array('port', 'portnumber'), + 'parameter' => 'PORTNUMBER', + 'description' => 'port number of the MySQL server', + 'default_value' => NULL, + ), + 'user' => array( + 'name' => array('user', 'username', 'u'), + 'parameter' => 'USERNAME', + 'description' => 'username used for connecting to the MySQL server', + 'default_value' => NULL, + ), + 'password' => array( + 'name' => array('password', 'pass', 'p'), + 'parameter' => 'PASSWORD', + 'description' => 'password used for connecting to the MySQL server', + 'default_value' => NULL, + ), + 'database' => array( + 'name' => array('database', 'db', 'd'), + 'parameter' => 'DATABASE', + 'description' => 'name of the database to be searched', + 'default_value' => NULL, + ), + 'charset' => array( + 'name' => array('charset', 'characterset', 'char'), + 'parameter' => 'CHARSET', + 'description' => 'character set used for connecting to the MySQL server', + 'default_value' => NULL, + ), + ), + + 'DBSR' => array( + DBSR::OPTION_CASE_INSENSITIVE => array( + 'name' => 'case-insensitive', + 'parameter' => '[true|false]', + 'description' => 'use case-insensitive search and replace', + 'default_value' => FALSE, + ), + DBSR::OPTION_EXTENSIVE_SEARCH => array( + 'name' => 'extensive-search', + 'parameter' => '[true|false]', + 'description' => 'process *all* database rows', + 'default_value' => FALSE, + ), + DBSR::OPTION_SEARCH_PAGE_SIZE => array( + 'name' => 'search-page-size', + 'parameter' => 'SIZE', + 'description' => 'number of rows to process simultaneously', + 'default_value' => 10000, + ), + DBSR::OPTION_VAR_MATCH_STRICT => array( + 'name' => 'var-match-strict', + 'parameter' => '[true|false]', + 'description' => 'use strict matching', + 'default_value' => TRUE, + ), + DBSR::OPTION_FLOATS_PRECISION => array( + 'name' => 'floats-precision', + 'parameter' => 'PRECISION', + 'description' => 'up to how many decimals floats should be matched', + 'default_value' => 5, + ), + DBSR::OPTION_CONVERT_CHARSETS => array( + 'name' => 'convert-charsets', + 'parameter' => '[true|false]', + 'description' => 'automatically convert character sets', + 'default_value' => TRUE, + ), + DBSR::OPTION_VAR_CAST_REPLACE => array( + 'name' => 'var-cast-replace', + 'parameter' => '[true|false]', + 'description' => 'cast all replace-values to the original type', + 'default_value' => TRUE, + ), + DBSR::OPTION_DB_WRITE_CHANGES => array( + 'name' => 'db-write-changes', + 'parameter' => '[true|false]', + 'description' => 'write changed values back to the database', + 'default_value' => TRUE, + ), + DBSR::OPTION_HANDLE_SERIALIZE => array( + 'name' => 'handle-serialize', + 'parameter' => '[true|false]', + 'description' => 'interpret serialized strings as their PHP types', + 'default_value' => TRUE, + ), + ), + ); + + /* Static methods */ + /** + * Prints the version string. + */ + public static function printVersion() { + echo 'DBSR CLI v', static::VERSION, ', based on DBSR v' . DBSR::VERSION . ', running on PHP ', PHP_VERSION, ' (', PHP_SAPI, '), ', PHP_OS, '.', "\n"; + } + + /** + * Prints the help text based on $default_options. + * + * @param $filename The filename to display, NULL for autodetect using $argv. + */ + public static function printHelp($filename = NULL) { + $pad_left = 4; + $width_left = 40; + $width_right = 32; + if(is_null($filename)) { + if(isset($_SERVER['argv']) && is_array($_SERVER['argv'])) { + $filename = $_SERVER['argv'][0]; + } else { + $filename = basename($_SERVER['SCRIPT_NAME']); + } + } + + static::printVersion(); + + echo "\n", + 'Usage: ', $filename, ' [options] -- SEARCH REPLACE [SEARCH REPLACE...]', "\n" . + ' ', $filename, ' --file FILENAME', "\n" . + "\n"; + foreach(static::$default_options as $name => $optionset) { + echo $name, ' options:', "\n"; + foreach($optionset as $option) { + // Force type to array + $option['name'] = (array) $option['name']; + + // Option + $parameter = (strlen($option['name'][0]) > 1 ? '--' : '-') . $option['name'][0]; + + // Parameter + if(!is_null($option['parameter'])) { + $parameter .= ' ' . $option['parameter']; + } + + // Description + $description_array = preg_split('/(.{1,' . $width_right . '}(?:\s(?!$)|(?=$)))/', $option['description'], NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); + $description = $description_array[0]; + for($i = 1; $i < count($description_array); $i++) { + $description .= "\n" . str_repeat(' ', $width_left + $pad_left) . $description_array[$i]; + } + + // Default + if(!is_null($option['default_value'])) { + $default = $option['default_value']; + if(is_bool($default)) { + $default = $default ? 'true' : 'false'; + } else { + $default = (string) $default; + } + $default = ' (default: ' . $default . ')'; + if(strlen($description_array[count($description_array) - 1]) + strlen($default) > $width_right) { + $description .= "\n" . str_repeat(' ', $width_left + $pad_left - 1); + } + $description .= $default; + } + + // Echo the option + echo str_repeat(' ', $pad_left), str_pad($parameter, $width_left), $description, "\n"; + } + } + } + + /** + * Returns the corresponding default option given a switch name. + * + * @param string $switch The switch to search for. + * @param boolean $check_prefix Whether to check for the correct prefix. + * @return mixed The option array, or FALSE if the switch was not found. + */ + protected static function getOption($switch, $check_prefix = TRUE) { + foreach(static::$default_options as $setname => $set) { + foreach($set as $id => $option) { + foreach((array) $option['name'] as $name) { + if($switch == ($check_prefix ? (strlen($name) > 1 ? ('--' . $name) : ('-' . $name)) : $name)) { + $option['set'] = $setname; + $option['id'] = $id; + return $option; + } + } + } + } + return FALSE; + } + + /* Properties */ + /** + * @var PDO + */ + protected $pdo; + + /** + * @var DBSR + */ + protected $dbsr; + + /** + * Options currently set. + * @var array + */ + protected $options = array(); + + /** + * The search-values. + * @var array + */ + protected $search = array(); + + /** + * The replace-values. + * @var array + */ + protected $replace = array(); + + /** + * List of configfiles which have been processed. + * Used to reprevent recursive inclusion. + * + * @var array + */ + private $configfiles = array(); + + /* Methods */ + /** + * Constructor: builds a new DBSR_CLI object and initalizes all options to their defaults + */ + public function __construct() { + foreach(static::$default_options as $setname => $set) { + foreach($set as $id => $option) { + if(!is_null($option['default_value'])) { + $this->options[$setname][$id] = $option['default_value']; + } + } + } + } + + /** + * Executes DBSR with the currently set options. Does not return but dies with the result. + */ + public function exec() { + // Prepare the DSN and PDO options array + $pdo_options = array( + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + ); + + $dsn = 'mysql:'; + if(isset($this->options['PDO']['host'])) { + $dsn .= 'host=' . $this->options['PDO']['host']; + if(isset($this->options['PDO']['port'])) { + $dsn .= ':' . $this->options['PDO']['port']; + } + $dsn .= ';'; + } + if(isset($this->options['PDO']['database'])) { + $dsn .= 'dbname=' . $this->options['PDO']['database'] . ';'; + } + if(isset($this->options['PDO']['charset'])) { + $pdo_options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES ' . $this->options['PDO']['charset']; + $dsn .= 'charset=' . $this->options['PDO']['charset'] . ';'; + } + + try { + // Try building a PDO, DBSR and running the search- and replace-action + $this->pdo = new PDO($dsn, @$this->options['PDO']['user'], @$this->options['PDO']['password'], $pdo_options); + $this->dbsr = new DBSR($this->pdo); + + // Set the DBSR options + foreach($this->options['DBSR'] as $option => $value) { + $this->dbsr->setOption($option, $value); + } + + // Set the search- and replace-values + $this->dbsr->setValues($this->search, $this->replace); + + // Execute DBSR + $result = $this->dbsr->exec(); + } catch(Exception $e) { + // Check the output type for the exception + switch($this->options['CLI']['output']) { + case 'json': + exit(json_encode(array('error' => $e->getMessage()))); + + case 'text': + default: + exit($e->getMessage()); + } + } + + // Output the result + switch($this->options['CLI']['output']) { + case 'text': + exit('Result: ' . $result . ' rows were ' . ($this->options['DBSR'][DBSR::OPTION_DB_WRITE_CHANGES] ? 'changed' : 'matched (no changes were written to the databasse)') . '!'); + + case 'json': + exit(json_encode(array('result' => $result))); + } + } + + /** + * Parses command line arguments. Directly outputs and dies in case of errors. + * + * @param array $arguments The array of arguments, the first element being the script's filename. + */ + public function parseArguments(array $arguments) { + if(empty($arguments)) { + if(isset($_SERVER['argv']) && is_array($_SERVER['argv'])) { + $arguments = $_SERVER['argv']; + } else { + $arguments = array(basename($_SERVER['SCRIPT_NAME'])); + } + } + + // Check if there are no arguments + if(count($arguments) <= 1) { + echo 'Usage: ', $arguments[0], ' [options] -- SEARCH REPLACE [SEARCH REPLACE...]', "\n" . + ' ', $arguments[0], ' --file FILENAME', "\n" . + 'Try `', $arguments[0], ' --help` for more information.', "\n"; + exit; + } + + // Loop over all arguments + for($i = 1; $i < count($arguments); $i++) { + switch($arguments[$i]) { + case '--': + // Check the number of search- and replace-values + if(count($arguments) - 1 - $i == 0) { + exit('Missing search- and replace-values!'); + } + if((count($arguments) - 1 - $i) % 2 != 0) { + exit('Missing replace-value for seach-value: ' . (string) $arguments[count($arguments) - 1]); + } + + // Save all search- and replace-values + for(++$i; $i < count($arguments); $i++) { + $this->search[] = $arguments[$i]; + $this->replace[] = $arguments[++$i]; + } + break; + + default: + // Get the option + $option = static::getOption($arguments[$i]); + if(!$option) { + exit('Unknown argument: ' . (string) $arguments[$i]); + } + + // Check for any arguments + if(!is_null($option['parameter'])) { + $arg = @$arguments[$i + 1]; + + // Boolean value without argument? + if(is_bool($option['default_value']) && (is_null($arg) || preg_match('/^\-/', $arg))) { + $this->options[$option['set']][$option['id']] = !$option['default_value']; + break; + } + + // Missing argument? + if(is_null($arg) || preg_match('/^\-/', $arg)) { + exit('Missing option for ' . (string) $arguments[$i]); + } + + // Special cases + switch($option['set'] . '/' . $option['id']) { + case 'CLI/file': + if(!$this->parseConfig($arg)) { + exit('Failed to parse config file: ' . (string) $arg); + } + $i++; + break 2; + } + + // Parse the argument + if(!is_null($option['default_value'])) { + // Special cases and specific error messages + if(is_bool($option['default_value'])) { + if(strtolower($arg) == 'true') { + $arg = TRUE; + } elseif(strtolower($arg) == 'false') { + $arg = FALSE; + } elseif(is_numeric($arg)) { + $arg = (bool) (int) $arg; + } else { + exit('Invalid argument, expected boolean for ' . (string) $arguments[$i]); + } + } elseif(is_int()) { + if(is_numeric($arg)) { + $arg = (int) $arg; + } else { + exit('Invalid argument, expected integer for ' . (string) $arguments[$i]); + } + } elseif(is_float()) { + if(is_numeric($arg)) { + $arg = (float) $arg; + } else { + exit('Invalid argument, expected float for ' . (string) $arguments[$i]); + } + } + + // Typeset + settype($arg, gettype($option['default_value'])); + } + + // Save the argument + $this->options[$option['set']][$option['id']] = $arg; + $i++; + } else { + // Special cases + switch($option['set'] . '/' . $option['id']) { + case 'CLI/help': + exit(static::printHelp($arguments[0])); + + case 'CLI/version': + exit(static::printVersion()); + } + } + break; + } + } + } + + /** + * Parses a config file + * @param string $file Path to the config file. + */ + public function parseConfig($file) { + // Check if the file exists + if(!file_exists($file) || !realpath($file)) { + return FALSE; + } + + // Check if we've read the file before + if(in_array(realpath($file), $this->configfiles)) { + return FALSE; + } + $this->configfiles[] = realpath($file); + + // Read file contents + $file_contents = @file_get_contents($file); + if(!$file_contents) { + return FALSE; + } + + // Decode content + $file_array = json_decode($file_contents, TRUE); + if(!is_array($file_array)) { + return FALSE; + } + + // Load search- and replace-values (if existing) + if(isset($file_array['search']) && is_array($file_array['search'])) { + $this->search += $file_array['search']; + } + if(isset($file_array['replace']) && is_array($file_array['replace'])) { + $this->replace += $file_array['replace']; + } + + // Check for options + if(isset($file_array['options']) && is_array($file_array['options'])) { + // Return success + return $this->_parseConfigArray($file_array['options']); + } else { + // No options, no problems + return TRUE; + } + + } + + /** + * parseConfig: runs through an option array and parses every option. + * + * @param array $array The array of options + * $return boolean TRUE if the array was parsed succesfully, FALSE otherwise. + */ + private function _parseConfigArray(array $array) { + foreach($array as $key => $element) { + if(is_array($element)) { + if(!$this->_parseConfigArray($element)) { + return FALSE; + } + } else { + // Check the option + $option = static::getOption($key, FALSE); + if(!$option) { + return FALSE; + } + + // Special cases without paramaters + switch($option['set'] . '/' . $option['id']) { + case 'CLI/help': + exit(static::printHelp()); + + case 'CLI/version': + exit(static::printVersion()); + } + + // No parameter? No go! + if(is_null($option['parameter'])) { + return FALSE; + } + + // Special cases with paramaters + switch($option['set'] . '/' . $option['id']) { + case 'CLI/file': + if(!$this->parseConfig($element)) { + return FALSE; + } + } + + // Save the value + $this->options[$option['set']][$option['id']] = $element; + } + } + return TRUE; + } +} diff --git a/DBSR_CLI_Bootstrapper.php b/DBSR_CLI_Bootstrapper.php index 6e8c930..a4c4ebf 100644 --- a/DBSR_CLI_Bootstrapper.php +++ b/DBSR_CLI_Bootstrapper.php @@ -1,70 +1,52 @@ . - */ - /** - * Bootstrapper for the DBSR CLI. - * - * @author Daniël van de Giessen - * @package DBSR - */ - - // Initialization - Bootstrapper::initialize(); - - // If it seems we're running from a webserver - if(PHP_SAPI != 'cli' && !empty($_SERVER['REMOTE_ADDR'])) { - // Build a argument array - $_SERVER['argv'] = array(basename($_SERVER['SCRIPT_FILENAME'])); - if(isset($_GET['args']) && strlen(trim($_GET['args'])) > 0) { - $_SERVER['argv'] = array_merge($_SERVER['argv'], explode(' ', trim($_GET['args']))); - } - - // Don't output HTML in any of the internal functions - @ini_set('html_errors', 0); - - /** Output buffer callback function with a simple CLI webinterface */ - function DBSR_CLI_output($output) { - header('Content-Type: text/html; charset=UTF-8'); - return '' . "\n" . - '' . "\n" . - '' . "\n" . - "\t" . '' . "\n" . - "\t" . 'DBSR CLI' . "\n" . - '' . "\n" . - '' . "\n" . - "\t" . '
' . "\n" . - "\t\t" . '

' . htmlspecialchars(@$_SERVER['argv'][0]) . '

' . "\n" . - "\t" . '
' . "\n" . - "\t" . '
' . htmlspecialchars($output) . '
' . "\n" . - '' . "\n" . - ''; - } - - // Start the CLI output bufferer - ob_start('DBSR_CLI_output'); - } - - // Create a new DBSR_CLI instance - $cli = new DBSR_CLI(); - - // Parse the arguments passed to the script - $cli->parseArguments($_SERVER['argv']); - - // Execute the actual search- and replace-action - $cli->exec(); - - // There's no continuing after including this file - die(); +/** + * Bootstrapper for the DBSR CLI. + */ + +// Initialization +Bootstrapper::initialize(); + +// If it seems we're running from a webserver +if(PHP_SAPI != 'cli' && !empty($_SERVER['REMOTE_ADDR'])) { + // Build a argument array + $_SERVER['argv'] = array(basename($_SERVER['SCRIPT_FILENAME'])); + if(isset($_GET['args']) && strlen(trim($_GET['args'])) > 0) { + $_SERVER['argv'] = array_merge($_SERVER['argv'], explode(' ', trim($_GET['args']))); + } + + // Don't output HTML in any of the internal functions + @ini_set('html_errors', 0); + + /** Output buffer callback function with a simple CLI webinterface */ + function DBSR_CLI_output($output) { + header('Content-Type: text/html; charset=UTF-8'); + return '' . "\n" . + '' . "\n" . + '' . "\n" . + "\t" . '' . "\n" . + "\t" . 'DBSR CLI' . "\n" . + '' . "\n" . + '' . "\n" . + "\t" . '
' . "\n" . + "\t\t" . '

' . htmlspecialchars(@$_SERVER['argv'][0]) . '

' . "\n" . + "\t" . '
' . "\n" . + "\t" . '
' . htmlspecialchars($output) . '
' . "\n" . + '' . "\n" . + ''; + } + + // Start the CLI output bufferer + ob_start('DBSR_CLI_output'); +} + +// Create a new DBSR_CLI instance +$cli = new DBSR_CLI(); + +// Parse the arguments passed to the script +$cli->parseArguments($_SERVER['argv']); + +// Execute the actual search- and replace-action +$cli->exec(); + +// There's no continuing after including this file +exit; diff --git a/DBSR_GUI.php b/DBSR_GUI.php index 88275a2..7121fc8 100644 --- a/DBSR_GUI.php +++ b/DBSR_GUI.php @@ -1,799 +1,813 @@ . - */ - - /** - * DBSR_GUI provides functionality for the GUI interface for the DBSR class. - * - * @author Daniël van de Giessen - * @package DBSR - * @version 2.0.3 - */ - class DBSR_GUI { - /* Constants */ - /** - * Version string indicating the DBSR GUI version. - * @var string - */ - const VERSION = '2.0.3'; - - /** - * Formatting option: formats as a plain, HTML-safe, string. - */ - const FORMAT_STRING_PLAINHTML = 0; - - /** - * Formatting option: formats as a PHP escaped string. - */ - const FORMAT_STRING_PHPESCAPE = 1; - - /** - * Formatting option: formats as a hex editor. - */ - const FORMAT_STRING_HEXEDITOR = 2; - - /* Properties */ - /** - * Options set during for this DBSR_GUI instance. - * @var array - */ - protected $options = array(); - - /** - * The maximum step completed succesfully. - * @var integer - */ - protected $maxStep = 0; - - /* Static methods */ - /** - * Formats a string according to the given formatting style. - * - * @param string $string The string to be formatted. - * @param int $format One of the DBSR_GUI::FORMAT_STRING_* constants. - * @return string The formatted string. - */ - public static function formatString($string, $format = self::FORMAT_STRING_PLAINHTML) { - // Check input - if(!is_string($string)) return FALSE; - - // Result string - $result = ''; - - // Switch format - switch($format) { - default: - case self::FORMAT_STRING_PLAINHTML: - $result = htmlspecialchars($string); - break; - - case self::FORMAT_STRING_PHPESCAPE: - $result .= '"'; - for($i = 0; $i < strlen($string); $i++) { - switch($string[$i]) { - case "\n": - $result .= '\\n'; - break; - case "\r": - $result .= '\\r'; - break; - case "\t": - $result .= '\\t'; - break; - case "\x0B": - $result .= '\\v'; - break; - case "\x1B": - $result .= '\\e'; - break; - case "\x0C": - $result .= '\\f'; - break; - case '\\': - $result .= '\\\\'; - break; - case "\"": - $result .= '\\"'; - break; - default: - $ord = ord($string[$i]); - if($ord >= 32 && $ord < 127) { - $result .= htmlspecialchars($string[$i]); - } else { - $result .= '\\x' . str_pad(strtoupper(dechex($ord)), 2, '0', STR_PAD_LEFT); - } - break; - } - } - $result .= '"'; - break; - - case self::FORMAT_STRING_HEXEDITOR: - // Padding for non-visible characters - static $pad = '.'; - - // Calculate strst padding string - static $from = ''; - static $to = ''; - if($from === '') { - for($i = 0; $i <= 0xFF; $i++) { - $from .= chr($i); - $to .= ($i >= 0x20 && $i <= 0x7E) ? chr($i) : $pad; - } - } - - // Number of bytes per line - $width = max(min(strlen($string), strlen($string) > 48 ? 16 : 8), 1); - - $hex = str_split(bin2hex($string), $width * 2); - $chars = str_split(strtr($string, $from, $to), $width); - - $offset = 0; - $leftpad = strlen((string) strlen($string)); - foreach($hex as $i => $line) { - $result .= ''; - $result .= str_pad($offset, $leftpad, ' ', STR_PAD_LEFT); - $result .= ' : '; - $result .= str_pad(implode(' ', str_split($line, 2)), 3 * $width, ' ', STR_PAD_RIGHT); - $result .= ' ['; - $result .= htmlspecialchars(str_pad($chars[$i], $width, ' ', STR_PAD_RIGHT)); - $result .= ']' . "\n"; - $offset += $width; - } - break; - } - - // Return the result - return $result; - } - - /** - * Returns the levenshtein distance between two strings. - * - * Though having the same complexity (O(n*m)) as the - * build-in PHP function it's implemented a lot more - * efficiently. The build-in version of PHP uses a m*n - * matrix to calculate the distance, resulting in a huge - * memory hog (which is why the maximum string length is - * limited to 255 chars). This version uses a bottom-up - * dynamic programming approach which limits the matrix - * size to 2*n, speeding up the memory allocation and - * allowing for longer input strings. - * - * @param string $str1 The first string to be compared. - * @param string $str2 The seconds string to be compared. - * @return integer The levenshtein distance between the two strings. - */ - public static function levenshtein($str1, $str2) { - // Save string lengths - $len1 = strlen($str1); - $len2 = strlen($str2); - - // Strip common prefix - $i = 0; - do { - if(substr($str1, $i, 1) != substr($str2, $i, 1)) break; - $i++; - $len1--; - $len2--; - } while($len1 > 0 && $len2 > 0); - if($i > 0) { - $str1 = substr($str1, $i); - $str2 = substr($str2, $i); - } - - // Strip common suffix - $i = 0; - do { - if(substr($str1, $len1 - 1, 1) != substr($str2, $len2-1, 1)) break; - $i++; - $len1--; - $len2--; - } while($len1 > 0 && $len2 > 0); - if($i > 0) { - $str1 = substr($str1, 0, $len1); - $str2 = substr($str2, 0, $len2); - } - - // If either of the strings has length 0; return the length of the other string - if ($len1 == 0) return $len2; - if ($len2 == 0) return $len1; - - // Create the arrays - $v0 = range(0, $len1); - $v1 = array(); - - // The actual algorithm - for ($i = 1; $i <= $len2; $i++) { - $v1[0] = $i; - $str2j = substr($str2, $i - 1, 1); - - for ($j = 1; $j <= $len1; $j++) { - $cost = (substr($str1, $j - 1, 1) == $str2j) ? 0 : 1; - - $m_min = $v0[$j] + 1; - $b = $v1[$j - 1] + 1; - $c = $v0[$j - 1] + $cost; - - if ($b < $m_min) $m_min = $b; - if ($c < $m_min) $m_min = $c; - - $v1[$j] = $m_min; - } - - $vTmp = $v0; - $v0 = $v1; - $v1 = $vTmp; - } - - return $v0[$len1]; - } - - /** - * Get the resource file content. - * @param string $resource The filename of the resource. - * @return mixed The content of the file as string, or FALSE if unsuccessful. - */ - public static function getResource($resource) { - // Check if a compiled version is available - if(class_exists('DBSR_GUI_Resources', FALSE)) return DBSR_GUI_Resources::getResource($resource); - - // No directory traversing - if(preg_match('/\\.\\.[\\/\\\\]/', $resource)) { - return FALSE; - } - - // Add path to filename - $resource = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'DBSR_GUI_Resources' . DIRECTORY_SEPARATOR . $resource; - - // Does the file exists - if(!is_readable($resource) || !is_file($resource)) { - return FALSE; - } - - // Return the content - return @file_get_contents($resource); - } - - /** - * Returns a MySQL PDO instance according to the given parameters. - * - * @param string $db_host - * @param integer $db_port - * @param string $db_user - * @param string $db_pass - * @param string $db_name - * @param sttring $db_char - * - * @throws PDOException - */ - public static function getPDO($db_host = NULL, $db_port = NULL, $db_user = NULL, $db_pass = NULL, $db_name = NULL, $db_char = NULL) { - // Prepare the DSN and PDO options array - $pdo_options = array( - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, - ); - - $dsn = 'mysql:'; - if(!empty($db_host)) { - $dsn .= 'host=' . $db_host; - if(!empty($db_port)) { - $dsn .= ':' . $db_port; - } - $dsn .= ';'; - } - if(!empty($db_name)) $dsn .= 'dbname=' . $db_name. ';'; - if(!empty($db_char)) { - $pdo_options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES ' . $db_char; - $dsn .= 'charset=' . $db_char . ';'; - } - - // Try connecting - return new PDO($dsn, $db_user, $db_pass, $pdo_options); - } - - /** - * Searches available configuration files for database configuration. - * - * @return array All values detected from configuration files. - */ - public static function detectConfig() { - // Variables to retrieve - $variables = array( - 'db_host' => 'DB_HOST', - 'db_user' => 'DB_USER', - 'db_pass' => 'DB_PASSWORD', - 'db_name' => 'DB_NAME', - 'db_char' => 'DB_CHARSET', - 'wp_prefix' => '$table_prefix', - ); - - // Configfiles, will be seached in order - $configfiles = array( - 'database.conf.php', - 'wp-config.php', - '..' . DIRECTORY_SEPARATOR . 'database.conf.php', - '..' . DIRECTORY_SEPARATOR . 'wp-config.php', - ); - - // Result array - $detected = array(); - - // For each configfile - foreach($configfiles as $configfile) if(count($variables) > 0) { - // Load it - if(file_exists($configfile) && ($config = file_get_contents($configfile))) { - // By default, the entire file is the block - $config_blocks = array($config); - - // Try to determine if a specific block contains our needs - $regex_block = '/(?:[iI][fF]\s*\(\s*[sS][tT][rR][iI]?(?:[sS][tT][rR]|[pP][oO][sS])\s*\(\s*\$_SERVER\s*\[\s*[\'"]SERVER_NAME[\'"]\s*\]\s*,\s*[\'"]((?:[^\'"]|\\\\\'|\\\\")*)[\'"]\s*\)[^\)]*\)|[eE][lL][sS][eE])\s*(\{(?:[^\{\}]*|\2)*\})/ms'; - if(preg_match_all($regex_block, $config, $matches, PREG_SET_ORDER)) { - // For each subset - foreach($matches as &$match) { - // Discard the complete match - array_shift($match); - - // Check if the detected name matches agains the current server name - if($match[0] == '' || stripos($_SERVER['SERVER_NAME'], $match[0]) !== FALSE) { - // Add this block as prefered block - array_unshift($config_blocks, $match[1]); - break; - } - } - } - - // Loop through each block - foreach($config_blocks as $config_block) if(count($variables) > 0) { - // Search for each variable and unset it if found - foreach($variables as $varname => $variable) if(count($variables) > 0) { - // Is this a define or a simple variable? - if($variable[0] == '$') { - $regex_variable = '/' . preg_quote($variable) . '\s*=\s*[\'"](([^\'"]|\\\\\'|\\\\")*)[\'"]\s*;/'; - } else { - $regex_variable = '/[dD][eE][fF][iI][nN][eE]\s*\(\s*[\'"]' . preg_quote($variable) . '[\'"]\s*,\s*[\'"](([^\'"]|\\\\\'|\\\\")*)[\'"]\s*\)\s*;/'; - } - - // Find the variable - if(preg_match($regex_variable, $config_block, $matches)) { - $detected[$varname] = $matches[1]; - unset($variables[$varname]); - } - } - } - } - } - - // Special case: extract the port number from the hostname - if(isset($detected['db_host']) && preg_match('/^(.*):(\d+)$/', $detected['db_host'], $matches)) { - $detected['db_host'] = $matches[1]; - $detected['db_port'] = $matches[2]; - } - if(isset($detected['db_host']) && !isset($detected['db_port'])) $detected['db_port'] = NULL; - - // Return the results - return $detected; - } - - /** - * Provides auto-complete hints for a given field. - * - * @param string $id The id of the field. - * @param string $term The currently typed term. - * @param array $arguments Other arguments currently filled in the form. - * @return array The hints, in order of likelyhood. - */ - public static function autoComplete($id, $term, $arguments) { - switch($id) { - case 'db_name': - try { - // Check if we can connect to the database with the given arguments - $pdo = self::getPDO(@$arguments['db_host'], @$arguments['db_port'], @$arguments['db_user'], @$arguments['db_pass'], NULL, NULL); - - // Fetch a list of databases - $result = $pdo->query('SHOW DATABASES;', PDO::FETCH_COLUMN, 0); - - // Filter matching databases - $databases = array(); - foreach($result as $r) { - if(strtolower(substr($r, 0, strlen($term))) == strtolower($term)) { - $databases[] = $r; - } - } - - // Return result - return $databases; - } catch(Exception $e) { - // Error: return nothing - return array(); - } - break; - - case 'db_char': - try { - // Check if we can connect to the database with the given arguments - $pdo = self::getPDO(@$arguments['db_host'], @$arguments['db_port'], @$arguments['db_user'], @$arguments['db_pass'], NULL, NULL); - - // Fetch a list of databases - $result = $pdo->query('SHOW CHARACTER SET;', PDO::FETCH_COLUMN, 0); - - // Filter matching databases - $charsets = array(); - foreach($result as $r) { - if(strtolower(substr($r, 0, strlen($term))) == strtolower($term)) { - $charsets[] = $r; - } - } - - // Return result - return $charsets; - } catch(Exception $e) { - // Error: return nothing - return array(); - } - break; - - default: - // Unknown field, return nothing - return array(); - } - } - - /* Methods */ - /** - * Constructor: resets the step for every new instance. - */ - public function __construct() { - $this->resetStep(); - } - - /** - * Validates the AJAX requests and returns a response for the GUI. - * - * @param integer $step The step to validate. - * @param array $arguments The arguments for validating this step. - * - * @return array The response to send to the GUI. - */ - public function completeStep($step, $arguments) { - if($step > $this->maxStep + 1) return array( - 'valid' => FALSE, - 'error' => 'First complete step ' . ($this->maxStep + 1) . '!' - ); - - switch($step) { - case 1: - // Validate the database connection information - if(!isset($arguments['db_host']) || empty($arguments['db_host'])) { - return array( - 'valid' => FALSE, - 'error' => 'Please enter a hostname!', - ); - } - if(!isset($arguments['db_name']) || empty($arguments['db_name'])) { - return array( - 'valid' => FALSE, - 'error' => 'Please enter a database name!', - ); - } - if(!isset($arguments['db_char']) || empty($arguments['db_char'])) { - return array( - 'valid' => FALSE, - 'error' => 'Please enter a character set!', - ); - } - - // Try to connect - try { - $pdo = self::getPDO(@$arguments['db_host'], @$arguments['db_port'], @$arguments['db_user'], @$arguments['db_pass'], @$arguments['db_name'], @$arguments['db_char']); - $pdo->query('SHOW TABLES;'); - } catch(Exception $e) { - return array( - 'valid' => FALSE, - 'error' => $e->getMessage(), - ); - } - - // Save maximum step - $this->maxStep = $step; - - // Save options - $this->options['db_host'] = @$arguments['db_host']; - $this->options['db_port'] = @$arguments['db_port']; - $this->options['db_user'] = @$arguments['db_user']; - $this->options['db_pass'] = @$arguments['db_pass']; - $this->options['db_name'] = @$arguments['db_name']; - $this->options['db_char'] = @$arguments['db_char']; - - // Return data for the GUI - return array( - 'valid' => TRUE, - 'data' => array( - 'db_host' => @$arguments['db_host'], - 'db_port' => @$arguments['db_port'], - 'db_user' => @$arguments['db_user'], - 'db_pass' => @$arguments['db_pass'], - 'db_name' => @$arguments['db_name'], - 'db_char' => @$arguments['db_char'], - ), - ); - - case 2: - // Check the search- and replace-values - if(!is_array(@$arguments['search']) || count(@$arguments['search']) == 0) { - return array( - 'valid' => FALSE, - 'error' => 'Missing search values!', - ); - } - if(!is_array(@$arguments['replace']) || count(@$arguments['replace']) == 0 || count(@$arguments['search']) != count(@$arguments['replace'])) { - return array( - 'valid' => FALSE, - 'error' => 'Missing replace values!', - ); - } - - // Clean indices - $arguments['search'] = array_values(@$arguments['search']); - $arguments['replace'] = array_values(@$arguments['replace']); - - // Parse escaped values - $escapedvalues = isset($arguments['escapedvalues']) && strtolower($arguments['escapedvalues']) == 'on'; - if($escapedvalues) { - for($i = 0; $i < count($arguments['search']); $i++) { - $arguments['search'][$i] = stripcslashes($arguments['search'][$i]); - $arguments['replace'][$i] = stripcslashes($arguments['replace'][$i]); - } - } - - // Remove all identical values - for($i = 0; $i < count($arguments['search']); $i++) { - if(empty($arguments['search'][$i])) return array( - 'valid' => FALSE, - 'error' => 'Search-value cannot be empty!', - ); - if($arguments['search'][$i] === $arguments['replace'][$i]) { - array_splice($arguments['search'], $i, 1); - array_splice($arguments['replace'], $i, 1); - $i--; - } - } - - // Check the length again - if(count($arguments['search']) == 0) return array( - 'valid' => FALSE, - 'error' => 'All given search- and replace-values are identical!', - ); - - // Save maximum step - $this->maxStep = $step; - - // Save options - $this->options['search'] = $arguments['search']; - $this->options['replace'] = $arguments['replace']; - - $this->options['escapedvalues'] = $escapedvalues; - $this->options['dbsr_caseinsensitive'] = isset($arguments['dbsr_caseinsensitive']) && strtolower($arguments['dbsr_caseinsensitive']) == 'on'; - $this->options['dbsr_extensivesearch'] = isset($arguments['dbsr_extensivesearch']) && strtolower($arguments['dbsr_extensivesearch']) == 'on'; - - // Return data for the GUI - $values = array(); - foreach(array( - 'values_raw' => self::FORMAT_STRING_PLAINHTML, - 'values_escaped' => self::FORMAT_STRING_PHPESCAPE, - 'values_hex' => self::FORMAT_STRING_HEXEDITOR, - ) as $name => $type) { - $values[$name] = ''; - for($i = 0; $i < count($arguments['search']); $i++) { - $values[$name] .= ''; - $values[$name] .= self::formatString($arguments['search'][$i], $type); - $values[$name] .= ''; - $values[$name] .= self::formatString($arguments['replace'][$i], $type); - $values[$name] .= ''; - } - } - - // Determine suggestions - $suggestions = $this->getSuggestions(); - if(count($suggestions) > 0) { - $values['suggestions'] = '

' . implode('

', $suggestions) . '

'; - } else { - $values['suggestions'] = ''; - } - - return array( - 'valid' => TRUE, - 'data' => array( - 'escapedvalues' => $this->options['escapedvalues'], - 'dbsr_caseinsensitive' => $this->options['dbsr_caseinsensitive'], - 'dbsr_extensivesearch' => $this->options['dbsr_extensivesearch'], - ), - 'html' => $values, - ); - - case 3: - if(!isset($arguments['confirmed']) || strtolower($arguments['confirmed']) != 'on') return array( - 'valid' => FALSE, - 'error' => 'Please confirm the data stated above is correct!', - ); - - // Run DBSR - try { - // Build a PDO instance - $pdo = self::getPDO($this->options['db_host'], $this->options['db_port'], $this->options['db_user'], $this->options['db_pass'], $this->options['db_name'], $this->options['db_char']); - - // Build a DBSR instance - $dbsr = new DBSR($pdo); - - // Set some DBSR options - $dbsr->setOption(DBSR::OPTION_CASE_INSENSITIVE, $this->options['dbsr_caseinsensitive']); - $dbsr->setOption(DBSR::OPTION_EXTENSIVE_SEARCH, $this->options['dbsr_extensivesearch']); - - // Set the search- and replace-values - $dbsr->setValues($this->options['search'], $this->options['replace']); - - // Reset the maximum step - $this->resetStep(); - - // Execute DBSR - $result = $dbsr->exec(); - - // Return the result - return array( - 'valid' => TRUE, - 'data' => array( - 'result' => $result, - ), - ); - } catch(Exception $e) { - // Return the error - return array( - 'valid' => TRUE, - 'error' => $e->getMessage(), - ); - } - - default: - return array( - 'valid' => FALSE, - 'error' => 'Unknown step!', - ); - } - } - - /** - * Resets the maximum step. - */ - public function resetStep() { - $this->maxStep = 0; - } - - /** - * Provides simple suggestions for common mistakes based on the search- and replace-values. - */ - protected function getSuggestions() { - // Array with all our messages - $messages = array(); - - // Build a PDO instance - $pdo = self::getPDO($this->options['db_host'], $this->options['db_port'], $this->options['db_user'], $this->options['db_pass'], $this->options['db_name'], $this->options['db_char']); - - // Try to determine the WP prefix - $config = self::detectConfig(); - $wp_prefix = !empty($config['wp_prefix']) ? $config['wp_prefix'] : 'wp_'; - - // Define the regex for matching domain names - $domain_regex = '/^https?:\\/\\/([a-z0-9](?:[-a-z0-9]*[a-z0-9])?(?:\\.[a-z0-9](?:[-a-z0-9]*[a-z0-9])?)*)\\/?$/iS'; - - // Switches to prevent double messages - $domain = FALSE; - $specialchars = FALSE; - $newlines = FALSE; - - // Get some of the server info to use a spelling probes - $spelling_probes = array( - $_SERVER['SERVER_NAME'], // current server name - dirname(__FILE__), // current directory - ); - - // Find WP siteurl - try { - $result = $pdo->query('SELECT `option_value` FROM `' . $wp_prefix . 'options` WHERE `option_name` = \'siteurl\'', PDO::FETCH_COLUMN, 0)->fetch(); - if(!empty($result)) { - // Save the domain name - $result = preg_replace($domain_regex, '$1', $result); - if(!in_array($result, $spelling_probes)) { - $spelling_probes[] = $result; - } - - // WWW-less domain name - $result = preg_replace('/^www\\.(.+)$/i', '$1', $result); - if(!in_array($result, $spelling_probes)) { - $spelling_probes[] = $result; - } - } - } catch(PDOException $e) {} - - // Find WP path - try { - $result = $pdo->query('SELECT `option_value` FROM `' . $wp_prefix . 'options` WHERE `option_name` = \'recently_edited\'', PDO::FETCH_COLUMN, 0)->fetch(); - if(!empty($result)) { - $result = preg_replace('/^(\\/.*)\\/wp-content\\/.*$/i', '$1', preg_replace('/^.*s:\d+:"([^"]+)";.*$/i', '$1', $result)); - if(strpos($result, '"') === FALSE && !in_array($result, $spelling_probes)) { - $spelling_probes[] = $result; - } - } - } catch(PDOException $e) {} - - // Loop over all values - for($i = 0; $i < count($this->options['search']); $i++) { - if(!$domain && preg_match($domain_regex, $this->options['search'][$i]) && preg_match($domain_regex, $this->options['replace'][$i])) { - // Domain name - $domain = TRUE; - $messages[] = 'It seems you\'re going to replace a domain name.
Be aware that it is recommended to omit any pre- and suffixes (such as http:// or a trailing slash) to ensure all occurences of the domain name will be replaced.'; - } else { - // Spelling - foreach($spelling_probes as $probe) { - if($this->options['dbsr_caseinsensitive']) { - if(strtolower($this->options['search'][$i]) != strtolower($probe) && self::levenshtein(strtolower($this->options['search'][$i]), strtolower($probe)) < 4) { - $messages[] = 'I suspect you might have made a typo in the ' . ($i + 1) . 'th search-value. Did you mean "' . htmlspecialchars($probe) . '"?'; - } - if(strtolower($this->options['replace'][$i]) != strtolower($probe) && self::levenshtein(strtolower($this->options['replace'][$i]), strtolower($probe)) < 4) { - $messages[] = 'I suspect you might have made a typo in the ' . ($i + 1) . 'th replace-value. Did you mean "' . htmlspecialchars($probe) . '"?'; - } - } else { - if($this->options['search'][$i] != $probe && self::levenshtein($this->options['search'][$i], $probe) < 4) { - $messages[] = 'I suspect you might have made a typo in the ' . ($i + 1) . 'th search-value. Did you mean "' . htmlspecialchars($probe) . '"?'; - } - if($this->options['replace'][$i] != $probe && self::levenshtein($this->options['replace'][$i], $probe) < 4) { - $messages[] = 'I suspect you might have made a typo in the ' . ($i + 1) . 'th replace-value. Did you mean "' . htmlspecialchars($probe) . '"?'; - } - } - } - } - - // Non-ASCII characters - for($j = 0; $j < strlen($this->options['search'][$i]) && !$specialchars; $j++) { - $ord = ord($this->options['search'][$i][$j]); - if($ord < 9 || ($ord > 10 && $ord < 13) || ($ord > 13 && $ord < 32) || $ord >= 127) { - $messages[] = 'There are some non-ASCII characters in your search value(s).
Be aware that this script does not provide any transliteration support, thus leaving character encoding entirely up to your browser and the database. Be sure to set the correct charset!'; - $specialchars = TRUE; - } - } - for($j = 0; $j < strlen($this->options['replace'][$i]) && !$specialchars; $j++) { - $ord = ord($this->options['replace'][$i][$j]); - if($ord < 9 || ($ord > 10 && $ord < 13) || ($ord > 13 && $ord < 32) || $ord >= 127) { - $messages[] = 'There are some non-ASCII characters in your replace value(s).
Be aware that this script does not provide any transliteration support, thus leaving character encoding entirely up to your browser and the database. Be sure to set the correct charset!'; - $specialchars = TRUE; - } - } - - // Newlines - if(!$newlines && !$this->options['escapedvalues']) { - if(strpos($this->options['search'][$i], "\n") !== FALSE) { - $newlines = TRUE; - $messages[] = 'You\'ve used ' . (strpos($_SESSION['search'][$i], "\r\n") !== FALSE ? 'Windows-style ("\r\n")' : 'Unix-style ("\n")') . ' line endings. If this is not what you want, go back and change it.'; - } - if(!$newlines && strpos($this->options['replace'][$i], "\n") !== FALSE) { - $newlines = TRUE; - $messages[] = 'You\'ve used ' . (strpos($_SESSION['replace'][$i], "\r\n") !== FALSE ? 'Windows-style ("\r\n")' : 'Unix-style ("\n")') . ' line endings. If this is not what you want, go back and change it.'; - } - } - } - - // Return the messages - return $messages; - } - } +/** + * DBSR_GUI provides functionality for the GUI interface for the DBSR class. + */ +class DBSR_GUI { + /* Constants */ + /** + * Version string indicating the DBSR GUI version. + * @var string + */ + const VERSION = '2.0.4'; + + /** + * Formatting option: formats as a plain, HTML-safe, string. + */ + const FORMAT_STRING_PLAINHTML = 0; + + /** + * Formatting option: formats as a PHP escaped string. + */ + const FORMAT_STRING_PHPESCAPE = 1; + + /** + * Formatting option: formats as a hex editor. + */ + const FORMAT_STRING_HEXEDITOR = 2; + + /* Properties */ + /** + * Options set during for this DBSR_GUI instance. + * @var array + */ + protected $options = array(); + + /** + * The maximum step completed succesfully. + * @var integer + */ + protected $maxStep = 0; + + /* Static methods */ + /** + * Formats a string according to the given formatting style. + * + * @param string $string The string to be formatted. + * @param int $format One of the DBSR_GUI::FORMAT_STRING_* constants. + * @return string The formatted string. + */ + public static function formatString($string, $format = self::FORMAT_STRING_PLAINHTML) { + // Check input + if(!is_string($string)) { + return FALSE; + } + + // Result string + $result = ''; + + // Switch format + switch($format) { + case static::FORMAT_STRING_PHPESCAPE: + $result .= '"'; + for($i = 0; $i < strlen($string); $i++) { + switch($string[$i]) { + case "\n": + $result .= '\\n'; + break; + case "\r": + $result .= '\\r'; + break; + case "\t": + $result .= '\\t'; + break; + case "\x0B": + $result .= '\\v'; + break; + case "\x1B": + $result .= '\\e'; + break; + case "\x0C": + $result .= '\\f'; + break; + case '\\': + $result .= '\\\\'; + break; + case "\"": + $result .= '\\"'; + break; + default: + $ord = ord($string[$i]); + if($ord >= 32 && $ord < 127) { + $result .= htmlspecialchars($string[$i]); + } else { + $result .= '\\x' . str_pad(strtoupper(dechex($ord)), 2, '0', STR_PAD_LEFT); + } + break; + } + } + $result .= '"'; + break; + + case static::FORMAT_STRING_HEXEDITOR: + // Padding for non-visible characters + static $pad = '.'; + + // Calculate strst padding string + static $from = ''; + static $to = ''; + if($from === '') { + for($i = 0; $i <= 0xFF; $i++) { + $from .= chr($i); + $to .= ($i >= 0x20 && $i <= 0x7E) ? chr($i) : $pad; + } + } + + // Number of bytes per line + $width = max(min(strlen($string), strlen($string) > 48 ? 16 : 8), 1); + + $hex = str_split(bin2hex($string), $width * 2); + $chars = str_split(strtr($string, $from, $to), $width); + + $offset = 0; + $leftpad = strlen((string) strlen($string)); + foreach($hex as $i => $line) { + $result .= ''; + $result .= str_pad($offset, $leftpad, ' ', STR_PAD_LEFT); + $result .= ' : '; + $result .= str_pad(implode(' ', str_split($line, 2)), 3 * $width, ' ', STR_PAD_RIGHT); + $result .= ' ['; + $result .= htmlspecialchars(str_pad($chars[$i], $width, ' ', STR_PAD_RIGHT)); + $result .= ']' . "\n"; + $offset += $width; + } + break; + + case static::FORMAT_STRING_PLAINHTML: + default: + $result = htmlspecialchars($string); + break; + } + + // Return the result + return $result; + } + + /** + * Returns the levenshtein distance between two strings. + * + * Though having the same complexity (O(n*m)) as the + * build-in PHP function it's implemented a lot more + * efficiently. The build-in version of PHP uses a m*n + * matrix to calculate the distance, resulting in a huge + * memory hog (which is why the maximum string length is + * limited to 255 chars). This version uses a bottom-up + * dynamic programming approach which limits the matrix + * size to 2*n, speeding up the memory allocation and + * allowing for longer input strings. + * + * @param string $str1 The first string to be compared. + * @param string $str2 The seconds string to be compared. + * @return integer The levenshtein distance between the two strings. + */ + public static function levenshtein($str1, $str2) { + // Save string lengths + $len1 = strlen($str1); + $len2 = strlen($str2); + + // Strip common prefix + $i = 0; + do { + if(substr($str1, $i, 1) != substr($str2, $i, 1)) { + break; + } + $i++; + $len1--; + $len2--; + } while($len1 > 0 && $len2 > 0); + if($i > 0) { + $str1 = substr($str1, $i); + $str2 = substr($str2, $i); + } + + // Strip common suffix + $i = 0; + do { + if(substr($str1, $len1 - 1, 1) != substr($str2, $len2-1, 1)) { + break; + } + $i++; + $len1--; + $len2--; + } while($len1 > 0 && $len2 > 0); + if($i > 0) { + $str1 = substr($str1, 0, $len1); + $str2 = substr($str2, 0, $len2); + } + + // If either of the strings has length 0; return the length of the other string + if ($len1 == 0) { + return $len2; + } + if ($len2 == 0) { + return $len1; + } + + // Create the arrays + $v0 = range(0, $len1); + $v1 = array(); + + // The actual algorithm + for ($i = 1; $i <= $len2; $i++) { + $v1[0] = $i; + $str2j = substr($str2, $i - 1, 1); + + for ($j = 1; $j <= $len1; $j++) { + $cost = (substr($str1, $j - 1, 1) == $str2j) ? 0 : 1; + + $m_min = $v0[$j] + 1; + $b = $v1[$j - 1] + 1; + $c = $v0[$j - 1] + $cost; + + if ($b < $m_min) { + $m_min = $b; + } + if ($c < $m_min) { + $m_min = $c; + } + + $v1[$j] = $m_min; + } + + $vTmp = $v0; + $v0 = $v1; + $v1 = $vTmp; + } + + return $v0[$len1]; + } + + /** + * Get the resource file content. + * @param string $resource The filename of the resource. + * @return mixed The content of the file as string, or FALSE if unsuccessful. + */ + public static function getResource($resource) { + // Check if a compiled version is available + if(class_exists('DBSR_GUI_Resources', FALSE)) { + return DBSR_GUI_Resources::getResource($resource); + } + + // No directory traversing + if(preg_match('/\\.\\.[\\/\\\\]/', $resource)) { + return FALSE; + } + + // Add path to filename + $resource = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'DBSR_GUI_Resources' . DIRECTORY_SEPARATOR . $resource; + + // Does the file exists + if(!is_readable($resource) || !is_file($resource)) { + return FALSE; + } + + // Return the content + return @file_get_contents($resource); + } + + /** + * Returns a MySQL PDO instance according to the given parameters. + * + * @param string $db_host + * @param integer $db_port + * @param string $db_user + * @param string $db_pass + * @param string $db_name + * @param sttring $db_char + * + * @throws PDOException + */ + public static function getPDO($db_host = NULL, $db_port = NULL, $db_user = NULL, $db_pass = NULL, $db_name = NULL, $db_char = NULL) { + // Prepare the DSN and PDO options array + $pdo_options = array( + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + ); + + $dsn = 'mysql:'; + if(!empty($db_host)) { + $dsn .= 'host=' . $db_host; + if(!empty($db_port)) { + $dsn .= ':' . $db_port; + } + $dsn .= ';'; + } + if(!empty($db_name)) { + $dsn .= 'dbname=' . $db_name. ';'; + } + if(!empty($db_char)) { + $pdo_options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES ' . $db_char; + $dsn .= 'charset=' . $db_char . ';'; + } + + // Try connecting + return new PDO($dsn, $db_user, $db_pass, $pdo_options); + } + + /** + * Searches available configuration files for database configuration. + * + * @return array All values detected from configuration files. + */ + public static function detectConfig() { + // Variables to retrieve + $variables = array( + 'db_host' => 'DB_HOST', + 'db_user' => 'DB_USER', + 'db_pass' => 'DB_PASSWORD', + 'db_name' => 'DB_NAME', + 'db_char' => 'DB_CHARSET', + 'wp_prefix' => '$table_prefix', + ); + + // Configfiles, will be seached in order + $configfiles = array( + 'database.conf.php', + 'wp-config.php', + '..' . DIRECTORY_SEPARATOR . 'database.conf.php', + '..' . DIRECTORY_SEPARATOR . 'wp-config.php', + ); + + // Result array + $detected = array(); + + // For each configfile + foreach($configfiles as $configfile) { + if(count($variables) > 0) { + // Load it + if(file_exists($configfile) && ($config = file_get_contents($configfile))) { + // By default, the entire file is the block + $config_blocks = array($config); + + // Try to determine if a specific block contains our needs + $regex_block = '/(?:[iI][fF]\s*\(\s*[sS][tT][rR][iI]?(?:[sS][tT][rR]|[pP][oO][sS])\s*\(\s*\$_SERVER\s*\[\s*[\'"]SERVER_NAME[\'"]\s*\]\s*,\s*[\'"]((?:[^\'"]|\\\\\'|\\\\")*)[\'"]\s*\)[^\)]*\)|[eE][lL][sS][eE])\s*(\{(?:[^\{\}]*|\2)*\})/ms'; + if(preg_match_all($regex_block, $config, $matches, PREG_SET_ORDER)) { + // For each subset + foreach($matches as &$match) { + // Discard the complete match + array_shift($match); + + // Check if the detected name matches agains the current server name + if($match[0] == '' || stripos($_SERVER['SERVER_NAME'], $match[0]) !== FALSE) { + // Add this block as prefered block + array_unshift($config_blocks, $match[1]); + break; + } + } + } + + // Loop through each block + foreach($config_blocks as $config_block) if(count($variables) > 0) { + // Search for each variable and unset it if found + foreach($variables as $varname => $variable) if(count($variables) > 0) { + // Is this a define or a simple variable? + if($variable[0] == '$') { + $regex_variable = '/' . preg_quote($variable) . '\s*=\s*[\'"](([^\'"]|\\\\\'|\\\\")*)[\'"]\s*;/'; + } else { + $regex_variable = '/[dD][eE][fF][iI][nN][eE]\s*\(\s*[\'"]' . preg_quote($variable) . '[\'"]\s*,\s*[\'"](([^\'"]|\\\\\'|\\\\")*)[\'"]\s*\)\s*;/'; + } + + // Find the variable + if(preg_match($regex_variable, $config_block, $matches)) { + $detected[$varname] = $matches[1]; + unset($variables[$varname]); + } + } + } + } + } + } + + // Special case: extract the port number from the hostname + if(isset($detected['db_host']) && preg_match('/^(.*):(\d+)$/', $detected['db_host'], $matches)) { + $detected['db_host'] = $matches[1]; + $detected['db_port'] = $matches[2]; + } + if(isset($detected['db_host']) && !isset($detected['db_port'])) { + $detected['db_port'] = NULL; + } + + // Return the results + return $detected; + } + + /** + * Provides auto-complete hints for a given field. + * + * @param string $id The id of the field. + * @param string $term The currently typed term. + * @param array $arguments Other arguments currently filled in the form. + * @return array The hints, in order of likelyhood. + */ + public static function autoComplete($id, $term, $arguments) { + switch($id) { + case 'db_name': + try { + // Check if we can connect to the database with the given arguments + $pdo = static::getPDO(@$arguments['db_host'], @$arguments['db_port'], @$arguments['db_user'], @$arguments['db_pass'], NULL, NULL); + + // Fetch a list of databases + $result = $pdo->query('SHOW DATABASES;', PDO::FETCH_COLUMN, 0); + + // Filter matching databases + $databases = array(); + foreach($result as $r) { + if(strtolower(substr($r, 0, strlen($term))) == strtolower($term)) { + $databases[] = $r; + } + } + + // Return result + return $databases; + } catch(Exception $e) { + // Error: return nothing + return array(); + } + break; + + case 'db_char': + try { + // Check if we can connect to the database with the given arguments + $pdo = static::getPDO(@$arguments['db_host'], @$arguments['db_port'], @$arguments['db_user'], @$arguments['db_pass'], NULL, NULL); + + // Fetch a list of databases + $result = $pdo->query('SHOW CHARACTER SET;', PDO::FETCH_COLUMN, 0); + + // Filter matching databases + $charsets = array(); + foreach($result as $r) { + if(strtolower(substr($r, 0, strlen($term))) == strtolower($term)) { + $charsets[] = $r; + } + } + + // Return result + return $charsets; + } catch(Exception $e) { + // Error: return nothing + return array(); + } + break; + + default: + // Unknown field, return nothing + return array(); + } + } + + /* Methods */ + /** + * Constructor: resets the step for every new instance. + */ + public function __construct() { + $this->resetStep(); + } + + /** + * Validates the AJAX requests and returns a response for the GUI. + * + * @param integer $step The step to validate. + * @param array $arguments The arguments for validating this step. + * + * @return array The response to send to the GUI. + */ + public function completeStep($step, $arguments) { + if($step > $this->maxStep + 1) { + return array( + 'valid' => FALSE, + 'error' => 'First complete step ' . ($this->maxStep + 1) . '!' + ); + } + + switch($step) { + case 1: + // Validate the database connection information + if(!isset($arguments['db_host']) || empty($arguments['db_host'])) { + return array( + 'valid' => FALSE, + 'error' => 'Please enter a hostname!', + ); + } + if(!isset($arguments['db_name']) || empty($arguments['db_name'])) { + return array( + 'valid' => FALSE, + 'error' => 'Please enter a database name!', + ); + } + if(!isset($arguments['db_char']) || empty($arguments['db_char'])) { + return array( + 'valid' => FALSE, + 'error' => 'Please enter a character set!', + ); + } + + // Try to connect + try { + $pdo = static::getPDO(@$arguments['db_host'], @$arguments['db_port'], @$arguments['db_user'], @$arguments['db_pass'], @$arguments['db_name'], @$arguments['db_char']); + $pdo->query('SHOW TABLES;'); + } catch(Exception $e) { + return array( + 'valid' => FALSE, + 'error' => $e->getMessage(), + ); + } + + // Save maximum step + $this->maxStep = $step; + + // Save options + $this->options['db_host'] = @$arguments['db_host']; + $this->options['db_port'] = @$arguments['db_port']; + $this->options['db_user'] = @$arguments['db_user']; + $this->options['db_pass'] = @$arguments['db_pass']; + $this->options['db_name'] = @$arguments['db_name']; + $this->options['db_char'] = @$arguments['db_char']; + + // Return data for the GUI + return array( + 'valid' => TRUE, + 'data' => array( + 'db_host' => @$arguments['db_host'], + 'db_port' => @$arguments['db_port'], + 'db_user' => @$arguments['db_user'], + 'db_pass' => @$arguments['db_pass'], + 'db_name' => @$arguments['db_name'], + 'db_char' => @$arguments['db_char'], + ), + ); + + case 2: + // Check the search- and replace-values + if(!is_array(@$arguments['search']) || count(@$arguments['search']) == 0) { + return array( + 'valid' => FALSE, + 'error' => 'Missing search values!', + ); + } + if(!is_array(@$arguments['replace']) || count(@$arguments['replace']) == 0 || count(@$arguments['search']) != count(@$arguments['replace'])) { + return array( + 'valid' => FALSE, + 'error' => 'Missing replace values!', + ); + } + + // Clean indices + $arguments['search'] = array_values(@$arguments['search']); + $arguments['replace'] = array_values(@$arguments['replace']); + + // Parse escaped values + $escapedvalues = isset($arguments['escapedvalues']) && strtolower($arguments['escapedvalues']) == 'on'; + if($escapedvalues) { + for($i = 0; $i < count($arguments['search']); $i++) { + $arguments['search'][$i] = stripcslashes($arguments['search'][$i]); + $arguments['replace'][$i] = stripcslashes($arguments['replace'][$i]); + } + } + + // Remove all identical values + for($i = 0; $i < count($arguments['search']); $i++) { + if(empty($arguments['search'][$i])) { + return array( + 'valid' => FALSE, + 'error' => 'Search-value cannot be empty!', + ); + } + if($arguments['search'][$i] === $arguments['replace'][$i]) { + array_splice($arguments['search'], $i, 1); + array_splice($arguments['replace'], $i, 1); + $i--; + } + } + + // Check the length again + if(count($arguments['search']) == 0) { + return array( + 'valid' => FALSE, + 'error' => 'All given search- and replace-values are identical!', + ); + } + + // Save maximum step + $this->maxStep = $step; + + // Save options + $this->options['search'] = $arguments['search']; + $this->options['replace'] = $arguments['replace']; + + $this->options['escapedvalues'] = $escapedvalues; + $this->options['dbsr_caseinsensitive'] = isset($arguments['dbsr_caseinsensitive']) && strtolower($arguments['dbsr_caseinsensitive']) == 'on'; + $this->options['dbsr_extensivesearch'] = isset($arguments['dbsr_extensivesearch']) && strtolower($arguments['dbsr_extensivesearch']) == 'on'; + + // Return data for the GUI + $values = array(); + foreach(array( + 'values_raw' => static::FORMAT_STRING_PLAINHTML, + 'values_escaped' => static::FORMAT_STRING_PHPESCAPE, + 'values_hex' => static::FORMAT_STRING_HEXEDITOR, + ) as $name => $type) { + $values[$name] = ''; + for($i = 0; $i < count($arguments['search']); $i++) { + $values[$name] .= ''; + $values[$name] .= static::formatString($arguments['search'][$i], $type); + $values[$name] .= ''; + $values[$name] .= static::formatString($arguments['replace'][$i], $type); + $values[$name] .= ''; + } + } + + // Determine suggestions + $suggestions = $this->getSuggestions(); + if(count($suggestions) > 0) { + $values['suggestions'] = '

' . implode('

', $suggestions) . '

'; + } else { + $values['suggestions'] = ''; + } + + return array( + 'valid' => TRUE, + 'data' => array( + 'escapedvalues' => $this->options['escapedvalues'], + 'dbsr_caseinsensitive' => $this->options['dbsr_caseinsensitive'], + 'dbsr_extensivesearch' => $this->options['dbsr_extensivesearch'], + ), + 'html' => $values, + ); + + case 3: + if(!isset($arguments['confirmed']) || strtolower($arguments['confirmed']) != 'on') { + return array( + 'valid' => FALSE, + 'error' => 'Please confirm the data stated above is correct!', + ); + } + + // Run DBSR + try { + // Build a PDO instance + $pdo = static::getPDO($this->options['db_host'], $this->options['db_port'], $this->options['db_user'], $this->options['db_pass'], $this->options['db_name'], $this->options['db_char']); + + // Build a DBSR instance + $dbsr = new DBSR($pdo); + + // Set some DBSR options + $dbsr->setOption(DBSR::OPTION_CASE_INSENSITIVE, $this->options['dbsr_caseinsensitive']); + $dbsr->setOption(DBSR::OPTION_EXTENSIVE_SEARCH, $this->options['dbsr_extensivesearch']); + + // Set the search- and replace-values + $dbsr->setValues($this->options['search'], $this->options['replace']); + + // Reset the maximum step + $this->resetStep(); + + // Execute DBSR + $result = $dbsr->exec(); + + // Return the result + return array( + 'valid' => TRUE, + 'data' => array( + 'result' => $result, + ), + ); + } catch(Exception $e) { + // Return the error + return array( + 'valid' => TRUE, + 'error' => $e->getMessage(), + ); + } + + default: + return array( + 'valid' => FALSE, + 'error' => 'Unknown step!', + ); + } + } + + /** + * Resets the maximum step. + */ + public function resetStep() { + $this->maxStep = 0; + } + + /** + * Provides simple suggestions for common mistakes based on the search- and replace-values. + */ + protected function getSuggestions() { + // Array with all our messages + $messages = array(); + + // Build a PDO instance + $pdo = static::getPDO($this->options['db_host'], $this->options['db_port'], $this->options['db_user'], $this->options['db_pass'], $this->options['db_name'], $this->options['db_char']); + + // Try to determine the WP prefix + $config = static::detectConfig(); + $wp_prefix = !empty($config['wp_prefix']) ? $config['wp_prefix'] : 'wp_'; + + // Define the regex for matching domain names + $domain_regex = '/^https?:\\/\\/([a-z0-9](?:[-a-z0-9]*[a-z0-9])?(?:\\.[a-z0-9](?:[-a-z0-9]*[a-z0-9])?)*)\\/?$/iS'; + + // Switches to prevent double messages + $domain = FALSE; + $specialchars = FALSE; + $newlines = FALSE; + + // Get some of the server info to use a spelling probes + $spelling_probes = array( + $_SERVER['SERVER_NAME'], // current server name + dirname(__FILE__), // current directory + ); + + // Find WP siteurl + try { + $result = $pdo->query('SELECT `option_value` FROM `' . $wp_prefix . 'options` WHERE `option_name` = \'siteurl\'', PDO::FETCH_COLUMN, 0)->fetch(); + if(!empty($result)) { + // Save the domain name + $result = preg_replace($domain_regex, '$1', $result); + if(!in_array($result, $spelling_probes)) { + $spelling_probes[] = $result; + } + + // WWW-less domain name + $result = preg_replace('/^www\\.(.+)$/i', '$1', $result); + if(!in_array($result, $spelling_probes)) { + $spelling_probes[] = $result; + } + } + } catch(PDOException $e) { + // Ignore exceptions when retrieving probes + } + + // Find WP path + try { + $result = $pdo->query('SELECT `option_value` FROM `' . $wp_prefix . 'options` WHERE `option_name` = \'recently_edited\'', PDO::FETCH_COLUMN, 0)->fetch(); + if(!empty($result)) { + $result = preg_replace('/^(\\/.*)\\/wp-content\\/.*$/i', '$1', preg_replace('/^.*s:\d+:"([^"]+)";.*$/i', '$1', $result)); + if(strpos($result, '"') === FALSE && !in_array($result, $spelling_probes)) { + $spelling_probes[] = $result; + } + } + } catch(PDOException $e) { + // Ignore exceptions when retrieving probes + } + + // Loop over all values + for($i = 0; $i < count($this->options['search']); $i++) { + if(!$domain && preg_match($domain_regex, $this->options['search'][$i]) && preg_match($domain_regex, $this->options['replace'][$i])) { + // Domain name + $domain = TRUE; + $messages[] = 'It seems you\'re going to replace a domain name.
Be aware that it is recommended to omit any pre- and suffixes (such as http:// or a trailing slash) to ensure all occurences of the domain name will be replaced.'; + } else { + // Spelling + foreach($spelling_probes as $probe) { + if($this->options['dbsr_caseinsensitive']) { + if(strtolower($this->options['search'][$i]) != strtolower($probe) && static::levenshtein(strtolower($this->options['search'][$i]), strtolower($probe)) < 4) { + $messages[] = 'I suspect you might have made a typo in the ' . ($i + 1) . 'th search-value. Did you mean "' . htmlspecialchars($probe) . '"?'; + } + if(strtolower($this->options['replace'][$i]) != strtolower($probe) && static::levenshtein(strtolower($this->options['replace'][$i]), strtolower($probe)) < 4) { + $messages[] = 'I suspect you might have made a typo in the ' . ($i + 1) . 'th replace-value. Did you mean "' . htmlspecialchars($probe) . '"?'; + } + } else { + if($this->options['search'][$i] != $probe && static::levenshtein($this->options['search'][$i], $probe) < 4) { + $messages[] = 'I suspect you might have made a typo in the ' . ($i + 1) . 'th search-value. Did you mean "' . htmlspecialchars($probe) . '"?'; + } + if($this->options['replace'][$i] != $probe && static::levenshtein($this->options['replace'][$i], $probe) < 4) { + $messages[] = 'I suspect you might have made a typo in the ' . ($i + 1) . 'th replace-value. Did you mean "' . htmlspecialchars($probe) . '"?'; + } + } + } + } + + // Non-ASCII characters + for($j = 0; $j < strlen($this->options['search'][$i]) && !$specialchars; $j++) { + $ord = ord($this->options['search'][$i][$j]); + if($ord < 9 || ($ord > 10 && $ord < 13) || ($ord > 13 && $ord < 32) || $ord >= 127) { + $messages[] = 'There are some non-ASCII characters in your search value(s).
Be aware that this script does not provide any transliteration support, thus leaving character encoding entirely up to your browser and the database. Be sure to set the correct charset!'; + $specialchars = TRUE; + } + } + for($j = 0; $j < strlen($this->options['replace'][$i]) && !$specialchars; $j++) { + $ord = ord($this->options['replace'][$i][$j]); + if($ord < 9 || ($ord > 10 && $ord < 13) || ($ord > 13 && $ord < 32) || $ord >= 127) { + $messages[] = 'There are some non-ASCII characters in your replace value(s).
Be aware that this script does not provide any transliteration support, thus leaving character encoding entirely up to your browser and the database. Be sure to set the correct charset!'; + $specialchars = TRUE; + } + } + + // Newlines + if(!$newlines && !$this->options['escapedvalues']) { + if(strpos($this->options['search'][$i], "\n") !== FALSE) { + $newlines = TRUE; + $messages[] = 'You\'ve used ' . (strpos($_SESSION['search'][$i], "\r\n") !== FALSE ? 'Windows-style ("\r\n")' : 'Unix-style ("\n")') . ' line endings. If this is not what you want, go back and change it.'; + } + if(!$newlines && strpos($this->options['replace'][$i], "\n") !== FALSE) { + $newlines = TRUE; + $messages[] = 'You\'ve used ' . (strpos($_SESSION['replace'][$i], "\r\n") !== FALSE ? 'Windows-style ("\r\n")' : 'Unix-style ("\n")') . ' line endings. If this is not what you want, go back and change it.'; + } + } + } + + // Return the messages + return $messages; + } +} diff --git a/DBSR_GUI_Bootstrapper.php b/DBSR_GUI_Bootstrapper.php index 9613961..232d766 100644 --- a/DBSR_GUI_Bootstrapper.php +++ b/DBSR_GUI_Bootstrapper.php @@ -1,168 +1,156 @@ . - */ - /** - * Bootstrapper for the DBSR GUI. - * - * @author Daniël van de Giessen - * @package DBSR - */ - - // Initialization - Bootstrapper::initialize(); - Bootstrapper::sessionStart(); - - // Set our exception handler - function DBSR_GUI_ExceptionHandler($e) { - // Check if the current request is an AJAX-request - if(isset($_GET['ajax'])) { - // Send the error as JSON - header('Content-Type: application/json'); - die(json_encode(array( - 'error' => $e->getMessage(), - 'errorCode' => $e->getCode(), - 'errorFile' => $e->getFile(), - 'errorLine' => $e->getLine(), - 'errorTrace' => $e->getTrace(), - ))); - } else { - // Rethrow - throw $e; - } - } - set_exception_handler('DBSR_GUI_ExceptionHandler'); - - // Check if we're reloading the page? - if(!isset($_GET['ajax']) && !isset($_GET['resource'])) { - Bootstrapper::sessionDestroy(); - Bootstrapper::sessionStart(); - } - - // Save a DBSR_GUI instance in the session - if(!isset($_SESSION['DBSR_GUI'])) { - $_SESSION['DBSR_GUI'] = new DBSR_GUI(); - } - $dbsr_gui = $_SESSION['DBSR_GUI']; - - // Check if this is a AJAX-request - if(isset($_GET['ajax'])) { - // Build a JSON-response - header('Content-Type: application/json'); - - // Check for JSON extension - if(!extension_loaded('json')) die('{"error":"The PHP JSON extension is not available!"}'); - - // Initialization - if(isset($_GET['initialize'])) { - die(json_encode(array( - 'data' => DBSR_GUI::detectConfig() + array( - 'DBSR_version' => DBSR::VERSION, - 'DBSR_GUI_version' => DBSR_GUI::VERSION, - ), - 'selfdestruct' => class_exists('DBSR_GUI_Resources', FALSE) && realpath(__FILE__) == realpath($_SERVER['SCRIPT_FILENAME']) && is_writable(__FILE__), - ))); - } - - // Autocomplete - if(isset($_GET['autocomplete'])) { - die(json_encode(DBSR_GUI::autoComplete($_POST['id'], $_POST['term'], $_POST))); - } - - // Step - if(isset($_GET['step'])) { - die(json_encode($dbsr_gui->completeStep((int) $_POST['step'], $_POST))); - } - - // Selfdestruct - if(isset($_GET['selfdestruct'])) { - die(json_encode(class_exists('DBSR_GUI_Resources', FALSE) && realpath(__FILE__) == realpath($_SERVER['SCRIPT_FILENAME']) && @unlink(__FILE__))); - } - - // No autocomplete or step? - header('HTTP/1.1 400 Bad Request'); - die(json_encode(array('error' => 'Unknown action!'))); - - } else { - // If no specific resource is requested, serve the template - if(!isset($_GET['resource'])) $_GET['resource'] = 'template.html'; - - // Get the resource - if($resource = DBSR_GUI::getResource($_GET['resource'])) { - // Set the correct headers - switch(strtolower(preg_replace('/^.*\.(\w+)$/', '$1', $_GET['resource']))) { - case 'html': - header('Content-Type: text/html; charset=UTF-8'); - // Internet Explorer has always held a special place in our code - // Try using Chrome Frame for IE8 and lower, and else at least disable the compatibility view - if(isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') !== FALSE) { - header('X-UA-Compatible: IE=edge,chrome=IE8'); - } - break; - - case 'css': - header('Content-Type: text/css'); - break; - - case 'js': - header('Content-Type: text/javascript'); - break; - - case 'png': - header('Content-Type: image/png'); - break; - - case 'woff': - header('Content-Type: application/x-font-woff'); - break; - - case 'otf': - header('Content-Type: application/x-font-otf'); - break; - - case 'eot': - header('Content-Type: application/x-font-eot'); - break; - - case 'ttf': - header('Content-Type: application/x-font-ttf'); - break; - - case 'svg': - header('Content-Type: image/svg+xml'); - break; - - case 'ico': - header('Content-Type: image/vnd.microsoft.icon'); - break; - } - header('Content-Disposition: inline; filename=' . basename($_GET['resource'])); - - // Compress output (zlib takes care of client/server headers automatically) - if(extension_loaded('zlib') && ini_get('output_handler') != 'ob_gzhandler') { - @ini_set('zlib.output_compression', TRUE); - } - - // Set expires header (only when running a compressed version) - if(class_exists('DBSR_GUI_Resources', FALSE)) header('Expires: ' . gmdate('D, d M Y H:i:s \\G\\M\\T', time() + 7 * 24 * 60 * 60)); - - // Output the resource - die($resource); - } else { - // Not found - header('HTTP/1.1 404 Not Found'); - die(); - } - } +/** + * Bootstrapper for the DBSR GUI. + */ + +// Initialization +Bootstrapper::initialize(); +Bootstrapper::sessionStart(); + +// Set our exception handler +function DBSR_GUI_ExceptionHandler($e) { + // Check if the current request is an AJAX-request + if(isset($_GET['ajax'])) { + // Send the error as JSON + header('Content-Type: application/json'); + exit(json_encode(array( + 'error' => $e->getMessage(), + 'errorCode' => $e->getCode(), + 'errorFile' => $e->getFile(), + 'errorLine' => $e->getLine(), + 'errorTrace' => $e->getTrace(), + ))); + } else { + // Rethrow + throw $e; + } +} +set_exception_handler('DBSR_GUI_ExceptionHandler'); + +// Check if we're reloading the page? +if(!isset($_GET['ajax']) && !isset($_GET['resource'])) { + Bootstrapper::sessionDestroy(); + Bootstrapper::sessionStart(); +} + +// Save a DBSR_GUI instance in the session +if(!isset($_SESSION['DBSR_GUI'])) { + $_SESSION['DBSR_GUI'] = new DBSR_GUI(); +} +$dbsr_gui = $_SESSION['DBSR_GUI']; + +// Check if this is a AJAX-request +if(isset($_GET['ajax'])) { + // Build a JSON-response + header('Content-Type: application/json'); + + // Check for JSON extension + if(!extension_loaded('json')) { + exit('{"error":"The PHP JSON extension is not available!"}'); + } + + // Initialization + if(isset($_GET['initialize'])) { + exit(json_encode(array( + 'data' => DBSR_GUI::detectConfig() + array( + 'DBSR_version' => DBSR::VERSION, + 'DBSR_GUI_version' => DBSR_GUI::VERSION, + ), + 'selfdestruct' => class_exists('DBSR_GUI_Resources', FALSE) && realpath(__FILE__) == realpath($_SERVER['SCRIPT_FILENAME']) && is_writable(__FILE__), + ))); + } + + // Autocomplete + if(isset($_GET['autocomplete'])) { + exit(json_encode(DBSR_GUI::autoComplete($_POST['id'], $_POST['term'], $_POST))); + } + + // Step + if(isset($_GET['step'])) { + exit(json_encode($dbsr_gui->completeStep((int) $_POST['step'], $_POST))); + } + + // Selfdestruct + if(isset($_GET['selfdestruct'])) { + exit(json_encode(class_exists('DBSR_GUI_Resources', FALSE) && realpath(__FILE__) == realpath($_SERVER['SCRIPT_FILENAME']) && @unlink(__FILE__))); + } + + // No autocomplete or step? + header('HTTP/1.1 400 Bad Request'); + exit(json_encode(array('error' => 'Unknown action!'))); + +} else { + // If no specific resource is requested, serve the template + if(!isset($_GET['resource'])) { + $_GET['resource'] = 'template.html'; + } + + // Get the resource + if($resource = DBSR_GUI::getResource($_GET['resource'])) { + // Set the correct headers + switch(strtolower(preg_replace('/^.*\.(\w+)$/', '$1', $_GET['resource']))) { + case 'html': + header('Content-Type: text/html; charset=UTF-8'); + // Internet Explorer has always held a special place in our code + // Try using Chrome Frame for IE8 and lower, and else at least disable the compatibility view + if(isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') !== FALSE) { + header('X-UA-Compatible: IE=edge,chrome=IE8'); + } + break; + + case 'css': + header('Content-Type: text/css'); + break; + + case 'js': + header('Content-Type: text/javascript'); + break; + + case 'png': + header('Content-Type: image/png'); + break; + + case 'woff': + header('Content-Type: application/x-font-woff'); + break; + + case 'otf': + header('Content-Type: application/x-font-otf'); + break; + + case 'eot': + header('Content-Type: application/x-font-eot'); + break; + + case 'ttf': + header('Content-Type: application/x-font-ttf'); + break; + + case 'svg': + header('Content-Type: image/svg+xml'); + break; + + case 'ico': + header('Content-Type: image/vnd.microsoft.icon'); + break; + } + header('Content-Disposition: inline; filename=' . basename($_GET['resource'])); + + // Compress output (zlib takes care of client/server headers automatically) + if(extension_loaded('zlib') && ini_get('output_handler') != 'ob_gzhandler') { + @ini_set('zlib.output_compression', TRUE); + } + + // Set expires header (only when running a compressed version) + if(class_exists('DBSR_GUI_Resources', FALSE)) { + header('Expires: ' . gmdate('D, d M Y H:i:s \\G\\M\\T', time() + 7 * 24 * 60 * 60)); + } + + // Output the resource + exit($resource); + } else { + // Not found + header('HTTP/1.1 404 Not Found'); + exit; + } +} diff --git a/DBSR_GUI_Resources/css/jquery-ui-1.12.1.custom.min.css b/DBSR_GUI_Resources/css/jquery-ui-1.12.1.custom.min.css new file mode 100644 index 0000000..35187a0 --- /dev/null +++ b/DBSR_GUI_Resources/css/jquery-ui-1.12.1.custom.min.css @@ -0,0 +1,7 @@ +/*! jQuery UI - v1.12.1 - 2017-02-14 +* http://jqueryui.com +* Includes: core.css, autocomplete.css, menu.css, theme.css +* To view and modify this theme, visit http://jqueryui.com/themeroller/?scope=&folderName=base&cornerRadiusShadow=8px&offsetLeftShadow=0px&offsetTopShadow=0px&thicknessShadow=5px&opacityShadow=30&bgImgOpacityShadow=0&bgTextureShadow=flat&bgColorShadow=666666&opacityOverlay=30&bgImgOpacityOverlay=0&bgTextureOverlay=flat&bgColorOverlay=aaaaaa&iconColorError=cc0000&fcError=5f3f3f&borderColorError=f1a899&bgTextureError=flat&bgColorError=fddfdf&iconColorHighlight=777620&fcHighlight=777620&borderColorHighlight=dad55e&bgTextureHighlight=flat&bgColorHighlight=fffa90&iconColorActive=ffffff&fcActive=ffffff&borderColorActive=003eff&bgTextureActive=flat&bgColorActive=007fff&iconColorHover=555555&fcHover=2b2b2b&borderColorHover=cccccc&bgTextureHover=flat&bgColorHover=ededed&iconColorDefault=777777&fcDefault=454545&borderColorDefault=c5c5c5&bgTextureDefault=flat&bgColorDefault=f6f6f6&iconColorContent=444444&fcContent=333333&borderColorContent=dddddd&bgTextureContent=flat&bgColorContent=ffffff&iconColorHeader=444444&fcHeader=333333&borderColorHeader=dddddd&bgTextureHeader=flat&bgColorHeader=e9e9e9&cornerRadius=3px&fwDefault=normal&fsDefault=1em&ffDefault=Arial%2CHelvetica%2Csans-serif +* Copyright jQuery Foundation and other contributors; Licensed MIT */ + +.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important;pointer-events:none}.ui-icon{display:inline-block;vertical-align:middle;margin-top:-.25em;position:relative;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-icon-block{left:50%;margin-left:-8px;display:block}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default}.ui-menu{list-style:none;padding:0;margin:0;display:block;outline:0}.ui-menu .ui-menu{position:absolute}.ui-menu .ui-menu-item{margin:0;cursor:pointer;list-style-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")}.ui-menu .ui-menu-item-wrapper{position:relative;padding:3px 1em 3px .4em}.ui-menu .ui-menu-divider{margin:5px 0;height:0;font-size:0;line-height:0;border-width:1px 0 0 0}.ui-menu .ui-state-focus,.ui-menu .ui-state-active{margin:-1px}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item-wrapper{padding-left:2em}.ui-menu .ui-icon{position:absolute;top:0;bottom:0;left:.2em;margin:auto 0}.ui-menu .ui-menu-icon{left:auto;right:0}.ui-widget{font-family:Arial,Helvetica,sans-serif;font-size:1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Arial,Helvetica,sans-serif;font-size:1em}.ui-widget.ui-widget-content{border:1px solid #c5c5c5}.ui-widget-content{border:1px solid #ddd;background:#fff;color:#333}.ui-widget-content a{color:#333}.ui-widget-header{border:1px solid #ddd;background:#e9e9e9;color:#333;font-weight:bold}.ui-widget-header a{color:#333}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default,.ui-button,html .ui-button.ui-state-disabled:hover,html .ui-button.ui-state-disabled:active{border:1px solid #c5c5c5;background:#f6f6f6;font-weight:normal;color:#454545}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited,a.ui-button,a:link.ui-button,a:visited.ui-button,.ui-button{color:#454545;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus,.ui-button:hover,.ui-button:focus{border:1px solid #ccc;background:#ededed;font-weight:normal;color:#2b2b2b}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited,a.ui-button:hover,a.ui-button:focus{color:#2b2b2b;text-decoration:none}.ui-visual-focus{box-shadow:0 0 3px 1px rgb(94,158,214)}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active,a.ui-button:active,.ui-button:active,.ui-button.ui-state-active:hover{border:1px solid #003eff;background:#007fff;font-weight:normal;color:#fff}.ui-icon-background,.ui-state-active .ui-icon-background{border:#003eff;background-color:#fff}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#fff;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #dad55e;background:#fffa90;color:#777620}.ui-state-checked{border:1px solid #dad55e;background:#fffa90}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#777620}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #f1a899;background:#fddfdf;color:#5f3f3f}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#5f3f3f}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#5f3f3f}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url("?resource=img/ui-icons_444444_256x240.png")}.ui-widget-header .ui-icon{background-image:url("?resource=img/ui-icons_444444_256x240.png")}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon,.ui-button:hover .ui-icon,.ui-button:focus .ui-icon{background-image:url("?resource=img/ui-icons_555555_256x240.png")}.ui-state-active .ui-icon,.ui-button:active .ui-icon{background-image:url("?resource=img/ui-icons_ffffff_256x240.png")}.ui-state-highlight .ui-icon,.ui-button .ui-state-highlight.ui-icon{background-image:url("?resource=img/ui-icons_777620_256x240.png")}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url("?resource=img/ui-icons_cc0000_256x240.png")}.ui-button .ui-icon{background-image:url("?resource=img/ui-icons_777777_256x240.png")}.ui-icon-blank{background-position:16px 16px}.ui-icon-caret-1-n{background-position:0 0}.ui-icon-caret-1-ne{background-position:-16px 0}.ui-icon-caret-1-e{background-position:-32px 0}.ui-icon-caret-1-se{background-position:-48px 0}.ui-icon-caret-1-s{background-position:-65px 0}.ui-icon-caret-1-sw{background-position:-80px 0}.ui-icon-caret-1-w{background-position:-96px 0}.ui-icon-caret-1-nw{background-position:-112px 0}.ui-icon-caret-2-n-s{background-position:-128px 0}.ui-icon-caret-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-65px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-65px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:1px -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:3px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:3px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:3px}.ui-widget-overlay{background:#aaa;opacity:.3;filter:Alpha(Opacity=30)}.ui-widget-shadow{-webkit-box-shadow:0 0 5px #666;box-shadow:0 0 5px #666} \ No newline at end of file diff --git a/DBSR_GUI_Resources/css/style.css b/DBSR_GUI_Resources/css/style.css index 00a4d1b..ea4a64b 100644 --- a/DBSR_GUI_Resources/css/style.css +++ b/DBSR_GUI_Resources/css/style.css @@ -14,167 +14,167 @@ * along with DBSR. If not, see . */ html { - width: 100%; - height: 100%; + width: 100%; + height: 100%; - background-color: #F2F2F2; + background-color: #F2F2F2; } body { - position: relative; + position: relative; - width: 90%; - height: 94%; - margin: 0 auto 0; + width: 90%; + height: 94%; + margin: 0 auto 0; - min-width: 900px; - min-height: 650px; + min-width: 900px; + min-height: 650px; - font-family: 'Source Sans Pro', Verdana, Geneva, sans-serif; - font-size: 16px; + font-family: 'Source Sans Pro', Verdana, Geneva, sans-serif; + font-size: 16px; } h2 { - font-size: 24px; - font-weight: bolder; - margin-top: 15px; + font-size: 24px; + font-weight: bolder; + margin-top: 15px; } p { - margin: 10px 0; + margin: 10px 0; } code, input[type="text"], textarea { - font-family: 'Source Code Pro', 'Consolas', 'Courier New', monospace; + font-family: 'Source Code Pro', 'Consolas', 'Courier New', monospace; } body > h1 { - height: 4%; - margin-top: 0.75%; - margin-left: 2%; + height: 4%; + margin-top: 0.75%; + margin-left: 2%; - font-family: 'Source Code Pro', 'Consolas', 'Courier New', monospace; - font-weight: normal; - font-size: 26px; - text-shadow: 2px 2px 2px #999999; + font-family: 'Source Code Pro', 'Consolas', 'Courier New', monospace; + font-weight: normal; + font-size: 26px; + text-shadow: 2px 2px 2px #999999; } body > .version { - position: absolute; - top: 0; - right: 0; + position: absolute; + top: 0; + right: 0; - height: 4%; - margin-top: 0.75%; - margin-left: 2%; + height: 4%; + margin-top: 0.75%; + margin-left: 2%; - font-family: 'Source Code Pro', 'Consolas', 'Courier New', monospace; - font-weight: normal; - font-size: 12px; - text-shadow: 2px 2px 2px #999999; + font-family: 'Source Code Pro', 'Consolas', 'Courier New', monospace; + font-weight: normal; + font-size: 12px; + text-shadow: 2px 2px 2px #999999; } #container { - width: 100%; - height: 96% !important; + width: 100%; + height: 96% !important; - margin-top: 0.75%; + margin-top: 0.75%; - border: 1px solid #333333; - box-shadow: 0 0 20px #737373; + border: 1px solid #333333; + box-shadow: 0 0 20px #737373; - background-color: #FFFFFF; + background-color: #FFFFFF; } #container .slide > h2 { - width: 48px !important; - height: 100% !important; - -webkit-transform: none; - -moz-transform: none; - -ms-transform: none; - -o-transform: none; - transform: none; + width: 48px !important; + height: 100% !important; + -webkit-transform: none; + -moz-transform: none; + -ms-transform: none; + -o-transform: none; + transform: none; - font-family: 'Source Code Pro', 'Consolas', 'Courier New', monospace; + font-family: 'Source Code Pro', 'Consolas', 'Courier New', monospace; } #container .slide > h2 span { - height: 44px; - width: 99999px; - margin-top: 25px; - margin-left: 4px; - padding-right: 0; - vertical-align: baseline; - -webkit-transform: translateX(-100%) rotate(-90deg); - -moz-transform: translateX(-100%) rotate(-90deg); - -ms-transform: translateX(-100%) rotate(-90deg); - -o-transform: translateX(-100%) rotate(-90deg); - transform: translateX(-100%) rotate(-90deg); - - -webkit-transform-origin: right top; - -moz-transform-origin: right top; - -ms-transform-origin: right top; - -o-transform-origin: right top; - transform-origin: right top; + height: 44px; + width: 99999px; + margin-top: 25px; + margin-left: 4px; + padding-right: 0; + vertical-align: baseline; + -webkit-transform: translateX(-100%) rotate(-90deg); + -moz-transform: translateX(-100%) rotate(-90deg); + -ms-transform: translateX(-100%) rotate(-90deg); + -o-transform: translateX(-100%) rotate(-90deg); + transform: translateX(-100%) rotate(-90deg); + + -webkit-transform-origin: right top; + -moz-transform-origin: right top; + -ms-transform-origin: right top; + -o-transform-origin: right top; + transform-origin: right top; } #container .slide > h2 b { - top: auto; - bottom: 10px; - left: 18px; - font-size: 22px; - -webkit-transform: none; - -moz-transform: none; - -ms-transform: none; - -o-transform: none; - transform: none; + top: auto; + bottom: 10px; + left: 18px; + font-size: 22px; + -webkit-transform: none; + -moz-transform: none; + -ms-transform: none; + -o-transform: none; + transform: none; } #container .slide > div { - height: 100%; - overflow: auto; + height: 100%; + overflow: auto; } #container .slide > div > div { - position: relative; - height: auto; - min-height: 100%; - min-height: -webkit-calc(100% - 2px); - min-height: -moz-calc(100% - 2px); - min-height: -ms-calc(100% - 2px); - min-height: -o-calc(100% - 2px); - min-height: calc(100% - 2px); - padding: 1px 0; + position: relative; + height: auto; + min-height: 100%; + min-height: -webkit-calc(100% - 2px); + min-height: -moz-calc(100% - 2px); + min-height: -ms-calc(100% - 2px); + min-height: -o-calc(100% - 2px); + min-height: calc(100% - 2px); + padding: 1px 0; } #container .slide > div form { - margin: 40px 40px 0; + margin: 40px 40px 0; } div.blockUI.blockOverlay { - background-color: #000; - opacity: 0.6; - cursor: wait; + background-color: #000; + opacity: 0.6; + cursor: wait; } div.blockUI.blockMsg { - width: 30%; - top: 40%; - left: 35%; - text-align: center; - padding: 5px; - opacity: 0.6; - cursor: wait; - color: #fff; - background-color: #000; - -webkit-border-radius: 10px; - -moz-border-radius: 10px; - -ms-border-radius: 10px; - -o-border-radius: 10px; - border-radius: 10px; + width: 30%; + top: 40%; + left: 35%; + text-align: center; + padding: 5px; + opacity: 0.6; + cursor: wait; + color: #fff; + background-color: #000; + -webkit-border-radius: 10px; + -moz-border-radius: 10px; + -ms-border-radius: 10px; + -o-border-radius: 10px; + border-radius: 10px; } .errormessage { - color: red; - font-weight: bolder; - font-size: 14px; + color: red; + font-weight: bolder; + font-size: 14px; } table { @@ -188,22 +188,22 @@ table td, table th { } input[type="text"], input[type="password"] { - min-width: 180px; + min-width: 180px; } .values-switch { - color: #808080; + color: #808080; } .values-switch a { - color: #808080; + color: #808080; } .values-switch a:hover { - color: #737373; + color: #737373; } .values-switch a.active { - font-weight: bold; - color: #8C8C8C; + font-weight: bold; + color: #8C8C8C; } .values-switch a.active:hover { - color: #8C8C8C; + color: #8C8C8C; } diff --git a/DBSR_GUI_Resources/img/animated-overlay.gif b/DBSR_GUI_Resources/img/animated-overlay.gif deleted file mode 100644 index d441f75..0000000 Binary files a/DBSR_GUI_Resources/img/animated-overlay.gif and /dev/null differ diff --git a/DBSR_GUI_Resources/img/ui-bg_flat_0_aaaaaa_40x100.png b/DBSR_GUI_Resources/img/ui-bg_flat_0_aaaaaa_40x100.png deleted file mode 100644 index 89fcef3..0000000 Binary files a/DBSR_GUI_Resources/img/ui-bg_flat_0_aaaaaa_40x100.png and /dev/null differ diff --git a/DBSR_GUI_Resources/img/ui-bg_flat_75_ffffff_40x100.png b/DBSR_GUI_Resources/img/ui-bg_flat_75_ffffff_40x100.png deleted file mode 100644 index e3b6767..0000000 Binary files a/DBSR_GUI_Resources/img/ui-bg_flat_75_ffffff_40x100.png and /dev/null differ diff --git a/DBSR_GUI_Resources/img/ui-bg_glass_55_fbf9ee_1x400.png b/DBSR_GUI_Resources/img/ui-bg_glass_55_fbf9ee_1x400.png deleted file mode 100644 index 3b581db..0000000 Binary files a/DBSR_GUI_Resources/img/ui-bg_glass_55_fbf9ee_1x400.png and /dev/null differ diff --git a/DBSR_GUI_Resources/img/ui-bg_glass_65_ffffff_1x400.png b/DBSR_GUI_Resources/img/ui-bg_glass_65_ffffff_1x400.png deleted file mode 100644 index 0e5dbce..0000000 Binary files a/DBSR_GUI_Resources/img/ui-bg_glass_65_ffffff_1x400.png and /dev/null differ diff --git a/DBSR_GUI_Resources/img/ui-bg_glass_75_dadada_1x400.png b/DBSR_GUI_Resources/img/ui-bg_glass_75_dadada_1x400.png deleted file mode 100644 index ba455a0..0000000 Binary files a/DBSR_GUI_Resources/img/ui-bg_glass_75_dadada_1x400.png and /dev/null differ diff --git a/DBSR_GUI_Resources/img/ui-bg_glass_75_e6e6e6_1x400.png b/DBSR_GUI_Resources/img/ui-bg_glass_75_e6e6e6_1x400.png deleted file mode 100644 index bb2042b..0000000 Binary files a/DBSR_GUI_Resources/img/ui-bg_glass_75_e6e6e6_1x400.png and /dev/null differ diff --git a/DBSR_GUI_Resources/img/ui-bg_glass_95_fef1ec_1x400.png b/DBSR_GUI_Resources/img/ui-bg_glass_95_fef1ec_1x400.png deleted file mode 100644 index 63e3e57..0000000 Binary files a/DBSR_GUI_Resources/img/ui-bg_glass_95_fef1ec_1x400.png and /dev/null differ diff --git a/DBSR_GUI_Resources/img/ui-bg_highlight-soft_75_cccccc_1x100.png b/DBSR_GUI_Resources/img/ui-bg_highlight-soft_75_cccccc_1x100.png deleted file mode 100644 index e161d59..0000000 Binary files a/DBSR_GUI_Resources/img/ui-bg_highlight-soft_75_cccccc_1x100.png and /dev/null differ diff --git a/DBSR_GUI_Resources/img/ui-icons_222222_256x240.png b/DBSR_GUI_Resources/img/ui-icons_222222_256x240.png deleted file mode 100644 index c1cb117..0000000 Binary files a/DBSR_GUI_Resources/img/ui-icons_222222_256x240.png and /dev/null differ diff --git a/DBSR_GUI_Resources/img/ui-icons_444444_256x240.png b/DBSR_GUI_Resources/img/ui-icons_444444_256x240.png new file mode 100644 index 0000000..19f664d Binary files /dev/null and b/DBSR_GUI_Resources/img/ui-icons_444444_256x240.png differ diff --git a/DBSR_GUI_Resources/img/ui-icons_454545_256x240.png b/DBSR_GUI_Resources/img/ui-icons_454545_256x240.png deleted file mode 100644 index b6db1ac..0000000 Binary files a/DBSR_GUI_Resources/img/ui-icons_454545_256x240.png and /dev/null differ diff --git a/DBSR_GUI_Resources/img/ui-icons_555555_256x240.png b/DBSR_GUI_Resources/img/ui-icons_555555_256x240.png new file mode 100644 index 0000000..e965f6d Binary files /dev/null and b/DBSR_GUI_Resources/img/ui-icons_555555_256x240.png differ diff --git a/DBSR_GUI_Resources/img/ui-icons_2e83ff_256x240.png b/DBSR_GUI_Resources/img/ui-icons_777620_256x240.png similarity index 90% rename from DBSR_GUI_Resources/img/ui-icons_2e83ff_256x240.png rename to DBSR_GUI_Resources/img/ui-icons_777620_256x240.png index 84b601b..9785948 100644 Binary files a/DBSR_GUI_Resources/img/ui-icons_2e83ff_256x240.png and b/DBSR_GUI_Resources/img/ui-icons_777620_256x240.png differ diff --git a/DBSR_GUI_Resources/img/ui-icons_777777_256x240.png b/DBSR_GUI_Resources/img/ui-icons_777777_256x240.png new file mode 100644 index 0000000..323c456 Binary files /dev/null and b/DBSR_GUI_Resources/img/ui-icons_777777_256x240.png differ diff --git a/DBSR_GUI_Resources/img/ui-icons_888888_256x240.png b/DBSR_GUI_Resources/img/ui-icons_888888_256x240.png deleted file mode 100644 index feea0e2..0000000 Binary files a/DBSR_GUI_Resources/img/ui-icons_888888_256x240.png and /dev/null differ diff --git a/DBSR_GUI_Resources/img/ui-icons_cd0a0a_256x240.png b/DBSR_GUI_Resources/img/ui-icons_cc0000_256x240.png similarity index 88% rename from DBSR_GUI_Resources/img/ui-icons_cd0a0a_256x240.png rename to DBSR_GUI_Resources/img/ui-icons_cc0000_256x240.png index ed5b6b0..45ac778 100644 Binary files a/DBSR_GUI_Resources/img/ui-icons_cd0a0a_256x240.png and b/DBSR_GUI_Resources/img/ui-icons_cc0000_256x240.png differ diff --git a/DBSR_GUI_Resources/img/ui-icons_ffffff_256x240.png b/DBSR_GUI_Resources/img/ui-icons_ffffff_256x240.png new file mode 100644 index 0000000..fe41d2d Binary files /dev/null and b/DBSR_GUI_Resources/img/ui-icons_ffffff_256x240.png differ diff --git a/DBSR_GUI_Resources/js/jquery-2.0.0.min.js b/DBSR_GUI_Resources/js/jquery-2.0.0.min.js deleted file mode 100644 index 1521772..0000000 --- a/DBSR_GUI_Resources/js/jquery-2.0.0.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery v2.0.0 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license */ -(function(e,undefined){var t,n,r=typeof undefined,i=e.location,o=e.document,s=o.documentElement,a=e.jQuery,u=e.$,l={},c=[],f="2.0.0",p=c.concat,h=c.push,d=c.slice,g=c.indexOf,m=l.toString,y=l.hasOwnProperty,v=f.trim,x=function(e,n){return new x.fn.init(e,n,t)},b=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,w=/\S+/g,T=/^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,k=/^-ms-/,N=/-([\da-z])/gi,E=function(e,t){return t.toUpperCase()},S=function(){o.removeEventListener("DOMContentLoaded",S,!1),e.removeEventListener("load",S,!1),x.ready()};x.fn=x.prototype={jquery:f,constructor:x,init:function(e,t,n){var r,i;if(!e)return this;if("string"==typeof e){if(r="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:T.exec(e),!r||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof x?t[0]:t,x.merge(this,x.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:o,!0)),C.test(r[1])&&x.isPlainObject(t))for(r in t)x.isFunction(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return i=o.getElementById(r[2]),i&&i.parentNode&&(this.length=1,this[0]=i),this.context=o,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?n.ready(e):(e.selector!==undefined&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return d.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,t,n,r,i,o,s=arguments[0]||{},a=1,u=arguments.length,l=!1;for("boolean"==typeof s&&(l=s,s=arguments[1]||{},a=2),"object"==typeof s||x.isFunction(s)||(s={}),u===a&&(s=this,--a);u>a;a++)if(null!=(e=arguments[a]))for(t in e)n=s[t],r=e[t],s!==r&&(l&&r&&(x.isPlainObject(r)||(i=x.isArray(r)))?(i?(i=!1,o=n&&x.isArray(n)?n:[]):o=n&&x.isPlainObject(n)?n:{},s[t]=x.extend(l,o,r)):r!==undefined&&(s[t]=r));return s},x.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=a),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){(e===!0?--x.readyWait:x.isReady)||(x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(o,[x]),x.fn.trigger&&x(o).trigger("ready").off("ready")))},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray,isWindow:function(e){return null!=e&&e===e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[m.call(e)]||"object":typeof e},isPlainObject:function(e){if("object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!y.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(t){return!1}return!0},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||o;var r=C.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:JSON.parse,parseXML:function(e){var t,n;if(!e||"string"!=typeof e)return null;try{n=new DOMParser,t=n.parseFromString(e,"text/xml")}catch(r){t=undefined}return(!t||t.getElementsByTagName("parsererror").length)&&x.error("Invalid XML: "+e),t},noop:function(){},globalEval:function(e){var t,n=eval;e=x.trim(e),e&&(1===e.indexOf("use strict")?(t=o.createElement("script"),t.text=e,o.head.appendChild(t).parentNode.removeChild(t)):n(e))},camelCase:function(e){return e.replace(k,"ms-").replace(N,E)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,s=j(e);if(n){if(s){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(s){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:function(e){return null==e?"":v.call(e)},makeArray:function(e,t){var n=t||[];return null!=e&&(j(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:g.call(t,e,n)},merge:function(e,t){var n=t.length,r=e.length,i=0;if("number"==typeof n)for(;n>i;i++)e[r++]=t[i];else while(t[i]!==undefined)e[r++]=t[i++];return e.length=r,e},grep:function(e,t,n){var r,i=[],o=0,s=e.length;for(n=!!n;s>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,s=j(e),a=[];if(s)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(a[a.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(a[a.length]=r);return p.apply([],a)},guid:1,proxy:function(e,t){var n,r,i;return"string"==typeof t&&(n=e[t],t=e,e=n),x.isFunction(e)?(r=d.call(arguments,2),i=function(){return e.apply(t||this,r.concat(d.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):undefined},access:function(e,t,n,r,i,o,s){var a=0,u=e.length,l=null==n;if("object"===x.type(n)){i=!0;for(a in n)x.access(e,t,a,n[a],!0,o,s)}else if(r!==undefined&&(i=!0,x.isFunction(r)||(s=!0),l&&(s?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(x(e),n)})),t))for(;u>a;a++)t(e[a],n,s?r:r.call(e[a],a,t(e[a],n)));return i?e:l?t.call(e):u?t(e[0],n):o},now:Date.now,swap:function(e,t,n,r){var i,o,s={};for(o in t)s[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=s[o];return i}}),x.ready.promise=function(t){return n||(n=x.Deferred(),"complete"===o.readyState?setTimeout(x.ready):(o.addEventListener("DOMContentLoaded",S,!1),e.addEventListener("load",S,!1))),n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){l["[object "+t+"]"]=t.toLowerCase()});function j(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}t=x(o),function(e,undefined){var t,n,r,i,o,s,a,u,l,c,f,p,h,d,g,m,y="sizzle"+-new Date,v=e.document,b={},w=0,T=0,C=ot(),k=ot(),N=ot(),E=!1,S=function(){return 0},j=typeof undefined,D=1<<31,A=[],L=A.pop,q=A.push,H=A.push,O=A.slice,F=A.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},P="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",R="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",W=M.replace("w","w#"),$="\\["+R+"*("+M+")"+R+"*(?:([*^$|!~]?=)"+R+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+W+")|)|)"+R+"*\\]",B=":("+M+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+$.replace(3,8)+")*)|.*)\\)|)",I=RegExp("^"+R+"+|((?:^|[^\\\\])(?:\\\\.)*)"+R+"+$","g"),z=RegExp("^"+R+"*,"+R+"*"),_=RegExp("^"+R+"*([>+~]|"+R+")"+R+"*"),X=RegExp(R+"*[+~]"),U=RegExp("="+R+"*([^\\]'\"]*)"+R+"*\\]","g"),Y=RegExp(B),V=RegExp("^"+W+"$"),G={ID:RegExp("^#("+M+")"),CLASS:RegExp("^\\.("+M+")"),TAG:RegExp("^("+M.replace("w","w*")+")"),ATTR:RegExp("^"+$),PSEUDO:RegExp("^"+B),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+R+"*(even|odd|(([+-]|)(\\d*)n|)"+R+"*(?:([+-]|)"+R+"*(\\d+)|))"+R+"*\\)|)","i"),"boolean":RegExp("^(?:"+P+")$","i"),needsContext:RegExp("^"+R+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+R+"*((?:-\\d)?\\d*)"+R+"*\\)|)(?=[^-]|$)","i")},J=/^[^{]+\{\s*\[native \w/,Q=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,K=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,et=/'|\\/g,tt=/\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g,nt=function(e,t){var n="0x"+t-65536;return n!==n?t:0>n?String.fromCharCode(n+65536):String.fromCharCode(55296|n>>10,56320|1023&n)};try{H.apply(A=O.call(v.childNodes),v.childNodes),A[v.childNodes.length].nodeType}catch(rt){H={apply:A.length?function(e,t){q.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function it(e){return J.test(e+"")}function ot(){var e,t=[];return e=function(n,i){return t.push(n+=" ")>r.cacheLength&&delete e[t.shift()],e[n]=i}}function st(e){return e[y]=!0,e}function at(e){var t=c.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function ut(e,t,n,r){var i,o,s,a,u,f,d,g,x,w;if((t?t.ownerDocument||t:v)!==c&&l(t),t=t||c,n=n||[],!e||"string"!=typeof e)return n;if(1!==(a=t.nodeType)&&9!==a)return[];if(p&&!r){if(i=Q.exec(e))if(s=i[1]){if(9===a){if(o=t.getElementById(s),!o||!o.parentNode)return n;if(o.id===s)return n.push(o),n}else if(t.ownerDocument&&(o=t.ownerDocument.getElementById(s))&&m(t,o)&&o.id===s)return n.push(o),n}else{if(i[2])return H.apply(n,t.getElementsByTagName(e)),n;if((s=i[3])&&b.getElementsByClassName&&t.getElementsByClassName)return H.apply(n,t.getElementsByClassName(s)),n}if(b.qsa&&(!h||!h.test(e))){if(g=d=y,x=t,w=9===a&&e,1===a&&"object"!==t.nodeName.toLowerCase()){f=gt(e),(d=t.getAttribute("id"))?g=d.replace(et,"\\$&"):t.setAttribute("id",g),g="[id='"+g+"'] ",u=f.length;while(u--)f[u]=g+mt(f[u]);x=X.test(e)&&t.parentNode||t,w=f.join(",")}if(w)try{return H.apply(n,x.querySelectorAll(w)),n}catch(T){}finally{d||t.removeAttribute("id")}}}return kt(e.replace(I,"$1"),t,n,r)}o=ut.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},l=ut.setDocument=function(e){var t=e?e.ownerDocument||e:v;return t!==c&&9===t.nodeType&&t.documentElement?(c=t,f=t.documentElement,p=!o(t),b.getElementsByTagName=at(function(e){return e.appendChild(t.createComment("")),!e.getElementsByTagName("*").length}),b.attributes=at(function(e){return e.className="i",!e.getAttribute("className")}),b.getElementsByClassName=at(function(e){return e.innerHTML="
",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),b.sortDetached=at(function(e){return 1&e.compareDocumentPosition(c.createElement("div"))}),b.getById=at(function(e){return f.appendChild(e).id=y,!t.getElementsByName||!t.getElementsByName(y).length}),b.getById?(r.find.ID=function(e,t){if(typeof t.getElementById!==j&&p){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},r.filter.ID=function(e){var t=e.replace(tt,nt);return function(e){return e.getAttribute("id")===t}}):(r.find.ID=function(e,t){if(typeof t.getElementById!==j&&p){var n=t.getElementById(e);return n?n.id===e||typeof n.getAttributeNode!==j&&n.getAttributeNode("id").value===e?[n]:undefined:[]}},r.filter.ID=function(e){var t=e.replace(tt,nt);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),r.find.TAG=b.getElementsByTagName?function(e,t){return typeof t.getElementsByTagName!==j?t.getElementsByTagName(e):undefined}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},r.find.CLASS=b.getElementsByClassName&&function(e,t){return typeof t.getElementsByClassName!==j&&p?t.getElementsByClassName(e):undefined},d=[],h=[],(b.qsa=it(t.querySelectorAll))&&(at(function(e){e.innerHTML="",e.querySelectorAll("[selected]").length||h.push("\\["+R+"*(?:value|"+P+")"),e.querySelectorAll(":checked").length||h.push(":checked")}),at(function(e){var t=c.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&h.push("[*^$]="+R+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||h.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),h.push(",.*:")})),(b.matchesSelector=it(g=f.webkitMatchesSelector||f.mozMatchesSelector||f.oMatchesSelector||f.msMatchesSelector))&&at(function(e){b.disconnectedMatch=g.call(e,"div"),g.call(e,"[s!='']:x"),d.push("!=",B)}),h=h.length&&RegExp(h.join("|")),d=d.length&&RegExp(d.join("|")),m=it(f.contains)||f.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},S=f.compareDocumentPosition?function(e,n){if(e===n)return E=!0,0;var r=n.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(n);return r?1&r||!b.sortDetached&&n.compareDocumentPosition(e)===r?e===t||m(v,e)?-1:n===t||m(v,n)?1:u?F.call(u,e)-F.call(u,n):0:4&r?-1:1:e.compareDocumentPosition?-1:1}:function(e,n){var r,i=0,o=e.parentNode,s=n.parentNode,a=[e],l=[n];if(e===n)return E=!0,0;if(!o||!s)return e===t?-1:n===t?1:o?-1:s?1:u?F.call(u,e)-F.call(u,n):0;if(o===s)return lt(e,n);r=e;while(r=r.parentNode)a.unshift(r);r=n;while(r=r.parentNode)l.unshift(r);while(a[i]===l[i])i++;return i?lt(a[i],l[i]):a[i]===v?-1:l[i]===v?1:0},c):c},ut.matches=function(e,t){return ut(e,null,null,t)},ut.matchesSelector=function(e,t){if((e.ownerDocument||e)!==c&&l(e),t=t.replace(U,"='$1']"),!(!b.matchesSelector||!p||d&&d.test(t)||h&&h.test(t)))try{var n=g.call(e,t);if(n||b.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(r){}return ut(t,c,null,[e]).length>0},ut.contains=function(e,t){return(e.ownerDocument||e)!==c&&l(e),m(e,t)},ut.attr=function(e,t){(e.ownerDocument||e)!==c&&l(e);var n=r.attrHandle[t.toLowerCase()],i=n&&n(e,t,!p);return i===undefined?b.attributes||!p?e.getAttribute(t):(i=e.getAttributeNode(t))&&i.specified?i.value:null:i},ut.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},ut.uniqueSort=function(e){var t,n=[],r=0,i=0;if(E=!b.detectDuplicates,u=!b.sortStable&&e.slice(0),e.sort(S),E){while(t=e[i++])t===e[i]&&(r=n.push(i));while(r--)e.splice(n[r],1)}return e};function lt(e,t){var n=t&&e,r=n&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function ct(e,t,n){var r;return n?undefined:(r=e.getAttributeNode(t))&&r.specified?r.value:e[t]===!0?t.toLowerCase():null}function ft(e,t,n){var r;return n?undefined:r=e.getAttribute(t,"type"===t.toLowerCase()?1:2)}function pt(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function ht(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function dt(e){return st(function(t){return t=+t,st(function(n,r){var i,o=e([],n.length,t),s=o.length;while(s--)n[i=o[s]]&&(n[i]=!(r[i]=n[i]))})})}i=ut.getText=function(e){var t,n="",r=0,o=e.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=i(e)}else if(3===o||4===o)return e.nodeValue}else for(;t=e[r];r++)n+=i(t);return n},r=ut.selectors={cacheLength:50,createPseudo:st,match:G,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(tt,nt),e[3]=(e[4]||e[5]||"").replace(tt,nt),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||ut.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&ut.error(e[0]),e},PSEUDO:function(e){var t,n=!e[5]&&e[2];return G.CHILD.test(e[0])?null:(e[4]?e[2]=e[4]:n&&Y.test(n)&&(t=gt(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(tt,nt).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=C[e+" "];return t||(t=RegExp("(^|"+R+")"+e+"("+R+"|$)"))&&C(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=ut.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),s="last"!==e.slice(-4),a="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,p,h,d,g=o!==s?"nextSibling":"previousSibling",m=t.parentNode,v=a&&t.nodeName.toLowerCase(),x=!u&&!a;if(m){if(o){while(g){f=t;while(f=f[g])if(a?f.nodeName.toLowerCase()===v:1===f.nodeType)return!1;d=g="only"===e&&!d&&"nextSibling"}return!0}if(d=[s?m.firstChild:m.lastChild],s&&x){c=m[y]||(m[y]={}),l=c[e]||[],h=l[0]===w&&l[1],p=l[0]===w&&l[2],f=h&&m.childNodes[h];while(f=++h&&f&&f[g]||(p=h=0)||d.pop())if(1===f.nodeType&&++p&&f===t){c[e]=[w,h,p];break}}else if(x&&(l=(t[y]||(t[y]={}))[e])&&l[0]===w)p=l[1];else while(f=++h&&f&&f[g]||(p=h=0)||d.pop())if((a?f.nodeName.toLowerCase()===v:1===f.nodeType)&&++p&&(x&&((f[y]||(f[y]={}))[e]=[w,p]),f===t))break;return p-=i,p===r||0===p%r&&p/r>=0}}},PSEUDO:function(e,t){var n,i=r.pseudos[e]||r.setFilters[e.toLowerCase()]||ut.error("unsupported pseudo: "+e);return i[y]?i(t):i.length>1?(n=[e,e,"",t],r.setFilters.hasOwnProperty(e.toLowerCase())?st(function(e,n){var r,o=i(e,t),s=o.length;while(s--)r=F.call(e,o[s]),e[r]=!(n[r]=o[s])}):function(e){return i(e,0,n)}):i}},pseudos:{not:st(function(e){var t=[],n=[],r=s(e.replace(I,"$1"));return r[y]?st(function(e,t,n,i){var o,s=r(e,null,i,[]),a=e.length;while(a--)(o=s[a])&&(e[a]=!(t[a]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:st(function(e){return function(t){return ut(e,t).length>0}}),contains:st(function(e){return function(t){return(t.textContent||t.innerText||i(t)).indexOf(e)>-1}}),lang:st(function(e){return V.test(e||"")||ut.error("unsupported lang: "+e),e=e.replace(tt,nt).toLowerCase(),function(t){var n;do if(n=p?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===f},focus:function(e){return e===c.activeElement&&(!c.hasFocus||c.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!r.pseudos.empty(e)},header:function(e){return Z.test(e.nodeName)},input:function(e){return K.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:dt(function(){return[0]}),last:dt(function(e,t){return[t-1]}),eq:dt(function(e,t,n){return[0>n?n+t:n]}),even:dt(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:dt(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:dt(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:dt(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}};for(t in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})r.pseudos[t]=pt(t);for(t in{submit:!0,reset:!0})r.pseudos[t]=ht(t);function gt(e,t){var n,i,o,s,a,u,l,c=k[e+" "];if(c)return t?0:c.slice(0);a=e,u=[],l=r.preFilter;while(a){(!n||(i=z.exec(a)))&&(i&&(a=a.slice(i[0].length)||a),u.push(o=[])),n=!1,(i=_.exec(a))&&(n=i.shift(),o.push({value:n,type:i[0].replace(I," ")}),a=a.slice(n.length));for(s in r.filter)!(i=G[s].exec(a))||l[s]&&!(i=l[s](i))||(n=i.shift(),o.push({value:n,type:s,matches:i}),a=a.slice(n.length));if(!n)break}return t?a.length:a?ut.error(e):k(e,u).slice(0)}function mt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function yt(e,t,r){var i=t.dir,o=r&&"parentNode"===i,s=T++;return t.first?function(t,n,r){while(t=t[i])if(1===t.nodeType||o)return e(t,n,r)}:function(t,r,a){var u,l,c,f=w+" "+s;if(a){while(t=t[i])if((1===t.nodeType||o)&&e(t,r,a))return!0}else while(t=t[i])if(1===t.nodeType||o)if(c=t[y]||(t[y]={}),(l=c[i])&&l[0]===f){if((u=l[1])===!0||u===n)return u===!0}else if(l=c[i]=[f],l[1]=e(t,r,a)||n,l[1]===!0)return!0}}function vt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xt(e,t,n,r,i){var o,s=[],a=0,u=e.length,l=null!=t;for(;u>a;a++)(o=e[a])&&(!n||n(o,r,i))&&(s.push(o),l&&t.push(a));return s}function bt(e,t,n,r,i,o){return r&&!r[y]&&(r=bt(r)),i&&!i[y]&&(i=bt(i,o)),st(function(o,s,a,u){var l,c,f,p=[],h=[],d=s.length,g=o||Ct(t||"*",a.nodeType?[a]:a,[]),m=!e||!o&&t?g:xt(g,p,e,a,u),y=n?i||(o?e:d||r)?[]:s:m;if(n&&n(m,y,a,u),r){l=xt(y,h),r(l,[],a,u),c=l.length;while(c--)(f=l[c])&&(y[h[c]]=!(m[h[c]]=f))}if(o){if(i||e){if(i){l=[],c=y.length;while(c--)(f=y[c])&&l.push(m[c]=f);i(null,y=[],l,u)}c=y.length;while(c--)(f=y[c])&&(l=i?F.call(o,f):p[c])>-1&&(o[l]=!(s[l]=f))}}else y=xt(y===s?y.splice(d,y.length):y),i?i(null,s,y,u):H.apply(s,y)})}function wt(e){var t,n,i,o=e.length,s=r.relative[e[0].type],u=s||r.relative[" "],l=s?1:0,c=yt(function(e){return e===t},u,!0),f=yt(function(e){return F.call(t,e)>-1},u,!0),p=[function(e,n,r){return!s&&(r||n!==a)||((t=n).nodeType?c(e,n,r):f(e,n,r))}];for(;o>l;l++)if(n=r.relative[e[l].type])p=[yt(vt(p),n)];else{if(n=r.filter[e[l].type].apply(null,e[l].matches),n[y]){for(i=++l;o>i;i++)if(r.relative[e[i].type])break;return bt(l>1&&vt(p),l>1&&mt(e.slice(0,l-1)).replace(I,"$1"),n,i>l&&wt(e.slice(l,i)),o>i&&wt(e=e.slice(i)),o>i&&mt(e))}p.push(n)}return vt(p)}function Tt(e,t){var i=0,o=t.length>0,s=e.length>0,u=function(u,l,f,p,h){var d,g,m,y=[],v=0,x="0",b=u&&[],T=null!=h,C=a,k=u||s&&r.find.TAG("*",h&&l.parentNode||l),N=w+=null==C?1:Math.random()||.1;for(T&&(a=l!==c&&l,n=i);null!=(d=k[x]);x++){if(s&&d){g=0;while(m=e[g++])if(m(d,l,f)){p.push(d);break}T&&(w=N,n=++i)}o&&((d=!m&&d)&&v--,u&&b.push(d))}if(v+=x,o&&x!==v){g=0;while(m=t[g++])m(b,y,l,f);if(u){if(v>0)while(x--)b[x]||y[x]||(y[x]=L.call(p));y=xt(y)}H.apply(p,y),T&&!u&&y.length>0&&v+t.length>1&&ut.uniqueSort(p)}return T&&(w=N,a=C),b};return o?st(u):u}s=ut.compile=function(e,t){var n,r=[],i=[],o=N[e+" "];if(!o){t||(t=gt(e)),n=t.length;while(n--)o=wt(t[n]),o[y]?r.push(o):i.push(o);o=N(e,Tt(i,r))}return o};function Ct(e,t,n){var r=0,i=t.length;for(;i>r;r++)ut(e,t[r],n);return n}function kt(e,t,n,i){var o,a,u,l,c,f=gt(e);if(!i&&1===f.length){if(a=f[0]=f[0].slice(0),a.length>2&&"ID"===(u=a[0]).type&&9===t.nodeType&&p&&r.relative[a[1].type]){if(t=(r.find.ID(u.matches[0].replace(tt,nt),t)||[])[0],!t)return n;e=e.slice(a.shift().value.length)}o=G.needsContext.test(e)?0:a.length;while(o--){if(u=a[o],r.relative[l=u.type])break;if((c=r.find[l])&&(i=c(u.matches[0].replace(tt,nt),X.test(a[0].type)&&t.parentNode||t))){if(a.splice(o,1),e=i.length&&mt(a),!e)return H.apply(n,i),n;break}}}return s(e,f)(i,t,!p,n,X.test(e)),n}r.pseudos.nth=r.pseudos.eq;function Nt(){}Nt.prototype=r.filters=r.pseudos,r.setFilters=new Nt,b.sortStable=y.split("").sort(S).join("")===y,l(),[0,0].sort(S),b.detectDuplicates=E,at(function(e){if(e.innerHTML="","#"!==e.firstChild.getAttribute("href")){var t="type|href|height|width".split("|"),n=t.length;while(n--)r.attrHandle[t[n]]=ft}}),at(function(e){if(null!=e.getAttribute("disabled")){var t=P.split("|"),n=t.length;while(n--)r.attrHandle[t[n]]=ct}}),x.find=ut,x.expr=ut.selectors,x.expr[":"]=x.expr.pseudos,x.unique=ut.uniqueSort,x.text=ut.getText,x.isXMLDoc=ut.isXML,x.contains=ut.contains}(e);var D={};function A(e){var t=D[e]={};return x.each(e.match(w)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?D[e]||A(e):x.extend({},e);var t,n,r,i,o,s,a=[],u=!e.once&&[],l=function(f){for(t=e.memory&&f,n=!0,s=i||0,i=0,o=a.length,r=!0;a&&o>s;s++)if(a[s].apply(f[0],f[1])===!1&&e.stopOnFalse){t=!1;break}r=!1,a&&(u?u.length&&l(u.shift()):t?a=[]:c.disable())},c={add:function(){if(a){var n=a.length;(function s(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&c.has(n)||a.push(n):n&&n.length&&"string"!==r&&s(n)})})(arguments),r?o=a.length:t&&(i=n,l(t))}return this},remove:function(){return a&&x.each(arguments,function(e,t){var n;while((n=x.inArray(t,a,n))>-1)a.splice(n,1),r&&(o>=n&&o--,s>=n&&s--)}),this},has:function(e){return e?x.inArray(e,a)>-1:!(!a||!a.length)},empty:function(){return a=[],o=0,this},disable:function(){return a=u=t=undefined,this},disabled:function(){return!a},lock:function(){return u=undefined,t||c.disable(),this},locked:function(){return!u},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],!a||n&&!u||(r?u.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!n}};return c},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var s=o[0],a=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=a&&a.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===r?n.promise():this,a?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var s=o[2],a=o[3];r[o[1]]=s.add,a&&s.add(function(){n=a},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=s.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=d.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),s=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?d.call(arguments):r,n===a?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},a,u,l;if(r>1)for(a=Array(r),u=Array(r),l=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(s(t,l,n)).fail(o.reject).progress(s(t,u,a)):--i;return i||o.resolveWith(l,n),o.promise()}}),x.support=function(t){var n=o.createElement("input"),r=o.createDocumentFragment(),i=o.createElement("div"),s=o.createElement("select"),a=s.appendChild(o.createElement("option"));return n.type?(n.type="checkbox",t.checkOn=""!==n.value,t.optSelected=a.selected,t.reliableMarginRight=!0,t.boxSizingReliable=!0,t.pixelPosition=!1,n.checked=!0,t.noCloneChecked=n.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!a.disabled,n=o.createElement("input"),n.value="t",n.type="radio",t.radioValue="t"===n.value,n.setAttribute("checked","t"),n.setAttribute("name","t"),r.appendChild(n),t.checkClone=r.cloneNode(!0).cloneNode(!0).lastChild.checked,t.focusinBubbles="onfocusin"in e,i.style.backgroundClip="content-box",i.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===i.style.backgroundClip,x(function(){var n,r,s="padding:0;margin:0;border:0;display:block;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box",a=o.getElementsByTagName("body")[0];a&&(n=o.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",a.appendChild(n).appendChild(i),i.innerHTML="",i.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%",x.swap(a,null!=a.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===i.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(i,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(i,null)||{width:"4px"}).width,r=i.appendChild(o.createElement("div")),r.style.cssText=i.style.cssText=s,r.style.marginRight=r.style.width="0",i.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),a.removeChild(n))}),t):t}({});var L,q,H=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,O=/([A-Z])/g;function F(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=x.expando+Math.random()}F.uid=1,F.accepts=function(e){return e.nodeType?1===e.nodeType||9===e.nodeType:!0},F.prototype={key:function(e){if(!F.accepts(e))return 0;var t={},n=e[this.expando];if(!n){n=F.uid++;try{t[this.expando]={value:n},Object.defineProperties(e,t)}catch(r){t[this.expando]=n,x.extend(e,t)}}return this.cache[n]||(this.cache[n]={}),n},set:function(e,t,n){var r,i=this.key(e),o=this.cache[i];if("string"==typeof t)o[t]=n;else if(x.isEmptyObject(o))this.cache[i]=t;else for(r in t)o[r]=t[r]},get:function(e,t){var n=this.cache[this.key(e)];return t===undefined?n:n[t]},access:function(e,t,n){return t===undefined||t&&"string"==typeof t&&n===undefined?this.get(e,t):(this.set(e,t,n),n!==undefined?n:t)},remove:function(e,t){var n,r,i=this.key(e),o=this.cache[i];if(t===undefined)this.cache[i]={};else{x.isArray(t)?r=t.concat(t.map(x.camelCase)):t in o?r=[t]:(r=x.camelCase(t),r=r in o?[r]:r.match(w)||[]),n=r.length;while(n--)delete o[r[n]]}},hasData:function(e){return!x.isEmptyObject(this.cache[e[this.expando]]||{})},discard:function(e){delete this.cache[this.key(e)]}},L=new F,q=new F,x.extend({acceptData:F.accepts,hasData:function(e){return L.hasData(e)||q.hasData(e)},data:function(e,t,n){return L.access(e,t,n)},removeData:function(e,t){L.remove(e,t)},_data:function(e,t,n){return q.access(e,t,n)},_removeData:function(e,t){q.remove(e,t)}}),x.fn.extend({data:function(e,t){var n,r,i=this[0],o=0,s=null;if(e===undefined){if(this.length&&(s=L.get(i),1===i.nodeType&&!q.get(i,"hasDataAttrs"))){for(n=i.attributes;n.length>o;o++)r=n[o].name,0===r.indexOf("data-")&&(r=x.camelCase(r.substring(5)),P(i,r,s[r]));q.set(i,"hasDataAttrs",!0)}return s}return"object"==typeof e?this.each(function(){L.set(this,e)}):x.access(this,function(t){var n,r=x.camelCase(e);if(i&&t===undefined){if(n=L.get(i,e),n!==undefined)return n;if(n=L.get(i,r),n!==undefined)return n;if(n=P(i,r,undefined),n!==undefined)return n}else this.each(function(){var n=L.get(this,r);L.set(this,r,t),-1!==e.indexOf("-")&&n!==undefined&&L.set(this,e,t)})},null,t,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){L.remove(this,e)})}});function P(e,t,n){var r;if(n===undefined&&1===e.nodeType)if(r="data-"+t.replace(O,"-$1").toLowerCase(),n=e.getAttribute(r),"string"==typeof n){try{n="true"===n?!0:"false"===n?!1:"null"===n?null:+n+""===n?+n:H.test(n)?JSON.parse(n):n}catch(i){}L.set(e,t,n)}else n=undefined;return n}x.extend({queue:function(e,t,n){var r;return e?(t=(t||"fx")+"queue",r=q.get(e,t),n&&(!r||x.isArray(n)?r=q.access(e,t,x.makeArray(n)):r.push(n)),r||[]):undefined},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),s=function(){x.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),o.cur=i,i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,s,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return q.get(e,n)||q.access(e,n,{empty:x.Callbacks("once memory").add(function(){q.remove(e,[t+"queue",n])})})}}),x.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),n>arguments.length?x.queue(this[0],e):t===undefined?this:this.each(function(){var n=x.queue(this,e,t); -x._queueHooks(this,e),"fx"===e&&"inprogress"!==n[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,t){var n,r=1,i=x.Deferred(),o=this,s=this.length,a=function(){--r||i.resolveWith(o,[o])};"string"!=typeof e&&(t=e,e=undefined),e=e||"fx";while(s--)n=q.get(o[s],e+"queueHooks"),n&&n.empty&&(r++,n.empty.add(a));return a(),i.promise(t)}});var R,M,W=/[\t\r\n]/g,$=/\r/g,B=/^(?:input|select|textarea|button)$/i;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[x.propFix[e]||e]})},addClass:function(e){var t,n,r,i,o,s=0,a=this.length,u="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];a>s;s++)if(n=this[s],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(W," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,s=0,a=this.length,u=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];a>s;s++)if(n=this[s],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(W," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e,i="boolean"==typeof t;return x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var o,s=0,a=x(this),u=t,l=e.match(w)||[];while(o=l[s++])u=i?u:!a.hasClass(o),a[u?"addClass":"removeClass"](o)}else(n===r||"boolean"===n)&&(this.className&&q.set(this,"__className__",this.className),this.className=this.className||e===!1?"":q.get(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(W," ").indexOf(t)>=0)return!0;return!1},val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=x.isFunction(e),this.each(function(n){var i,o=x(this);1===this.nodeType&&(i=r?e.call(this,n,o.val()):e,null==i?i="":"number"==typeof i?i+="":x.isArray(i)&&(i=x.map(i,function(e){return null==e?"":e+""})),t=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],t&&"set"in t&&t.set(this,i,"value")!==undefined||(this.value=i))});if(i)return t=x.valHooks[i.type]||x.valHooks[i.nodeName.toLowerCase()],t&&"get"in t&&(n=t.get(i,"value"))!==undefined?n:(n=i.value,"string"==typeof n?n.replace($,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,s=o?null:[],a=o?i+1:r.length,u=0>i?a:o?i:0;for(;a>u;u++)if(n=r[u],!(!n.selected&&u!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;s.push(t)}return s},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),s=i.length;while(s--)r=i[s],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,t,n){var i,o,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===r?x.prop(e,t,n):(1===s&&x.isXMLDoc(e)||(t=t.toLowerCase(),i=x.attrHooks[t]||(x.expr.match.boolean.test(t)?M:R)),n===undefined?i&&"get"in i&&null!==(o=i.get(e,t))?o:(o=x.find.attr(e,t),null==o?undefined:o):null!==n?i&&"set"in i&&(o=i.set(e,n,t))!==undefined?o:(e.setAttribute(t,n+""),n):(x.removeAttr(e,t),undefined))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(w);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.boolean.test(n)&&(e[r]=!1),e.removeAttribute(n)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,t,n){var r,i,o,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return o=1!==s||!x.isXMLDoc(e),o&&(t=x.propFix[t]||t,i=x.propHooks[t]),n!==undefined?i&&"set"in i&&(r=i.set(e,n,t))!==undefined?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){return e.hasAttribute("tabindex")||B.test(e.nodeName)||e.href?e.tabIndex:-1}}}}),M={set:function(e,t,n){return t===!1?x.removeAttr(e,n):e.setAttribute(n,n),n}},x.each(x.expr.match.boolean.source.match(/\w+/g),function(e,t){var n=x.expr.attrHandle[t]||x.find.attr;x.expr.attrHandle[t]=function(e,t,r){var i=x.expr.attrHandle[t],o=r?undefined:(x.expr.attrHandle[t]=undefined)!=n(e,t,r)?t.toLowerCase():null;return x.expr.attrHandle[t]=i,o}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,t){return x.isArray(t)?e.checked=x.inArray(x(e).val(),t)>=0:undefined}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var I=/^key/,z=/^(?:mouse|contextmenu)|click/,_=/^(?:focusinfocus|focusoutblur)$/,X=/^([^.]*)(?:\.(.+)|)$/;function U(){return!0}function Y(){return!1}function V(){try{return o.activeElement}catch(e){}}x.event={global:{},add:function(e,t,n,i,o){var s,a,u,l,c,f,p,h,d,g,m,y=q.get(e);if(y){n.handler&&(s=n,n=s.handler,o=s.selector),n.guid||(n.guid=x.guid++),(l=y.events)||(l=y.events={}),(a=y.handle)||(a=y.handle=function(e){return typeof x===r||e&&x.event.triggered===e.type?undefined:x.event.dispatch.apply(a.elem,arguments)},a.elem=e),t=(t||"").match(w)||[""],c=t.length;while(c--)u=X.exec(t[c])||[],d=m=u[1],g=(u[2]||"").split(".").sort(),d&&(p=x.event.special[d]||{},d=(o?p.delegateType:p.bindType)||d,p=x.event.special[d]||{},f=x.extend({type:d,origType:m,data:i,handler:n,guid:n.guid,selector:o,needsContext:o&&x.expr.match.needsContext.test(o),namespace:g.join(".")},s),(h=l[d])||(h=l[d]=[],h.delegateCount=0,p.setup&&p.setup.call(e,i,g,a)!==!1||e.addEventListener&&e.addEventListener(d,a,!1)),p.add&&(p.add.call(e,f),f.handler.guid||(f.handler.guid=n.guid)),o?h.splice(h.delegateCount++,0,f):h.push(f),x.event.global[d]=!0);e=null}},remove:function(e,t,n,r,i){var o,s,a,u,l,c,f,p,h,d,g,m=q.hasData(e)&&q.get(e);if(m&&(u=m.events)){t=(t||"").match(w)||[""],l=t.length;while(l--)if(a=X.exec(t[l])||[],h=g=a[1],d=(a[2]||"").split(".").sort(),h){f=x.event.special[h]||{},h=(r?f.delegateType:f.bindType)||h,p=u[h]||[],a=a[2]&&RegExp("(^|\\.)"+d.join("\\.(?:.*\\.|)")+"(\\.|$)"),s=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||a&&!a.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));s&&!p.length&&(f.teardown&&f.teardown.call(e,d,m.handle)!==!1||x.removeEvent(e,h,m.handle),delete u[h])}else for(h in u)x.event.remove(e,h+t[l],n,r,!0);x.isEmptyObject(u)&&(delete m.handle,q.remove(e,"events"))}},trigger:function(t,n,r,i){var s,a,u,l,c,f,p,h=[r||o],d=y.call(t,"type")?t.type:t,g=y.call(t,"namespace")?t.namespace.split("."):[];if(a=u=r=r||o,3!==r.nodeType&&8!==r.nodeType&&!_.test(d+x.event.triggered)&&(d.indexOf(".")>=0&&(g=d.split("."),d=g.shift(),g.sort()),c=0>d.indexOf(":")&&"on"+d,t=t[x.expando]?t:new x.Event(d,"object"==typeof t&&t),t.isTrigger=i?2:3,t.namespace=g.join("."),t.namespace_re=t.namespace?RegExp("(^|\\.)"+g.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=undefined,t.target||(t.target=r),n=null==n?[t]:x.makeArray(n,[t]),p=x.event.special[d]||{},i||!p.trigger||p.trigger.apply(r,n)!==!1)){if(!i&&!p.noBubble&&!x.isWindow(r)){for(l=p.delegateType||d,_.test(l+d)||(a=a.parentNode);a;a=a.parentNode)h.push(a),u=a;u===(r.ownerDocument||o)&&h.push(u.defaultView||u.parentWindow||e)}s=0;while((a=h[s++])&&!t.isPropagationStopped())t.type=s>1?l:p.bindType||d,f=(q.get(a,"events")||{})[t.type]&&q.get(a,"handle"),f&&f.apply(a,n),f=c&&a[c],f&&x.acceptData(a)&&f.apply&&f.apply(a,n)===!1&&t.preventDefault();return t.type=d,i||t.isDefaultPrevented()||p._default&&p._default.apply(h.pop(),n)!==!1||!x.acceptData(r)||c&&x.isFunction(r[d])&&!x.isWindow(r)&&(u=r[c],u&&(r[c]=null),x.event.triggered=d,r[d](),x.event.triggered=undefined,u&&(r[c]=u)),t.result}},dispatch:function(e){e=x.event.fix(e);var t,n,r,i,o,s=[],a=d.call(arguments),u=(q.get(this,"events")||{})[e.type]||[],l=x.event.special[e.type]||{};if(a[0]=e,e.delegateTarget=this,!l.preDispatch||l.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),t=0;while((i=s[t++])&&!e.isPropagationStopped()){e.currentTarget=i.elem,n=0;while((o=i.handlers[n++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(o.namespace))&&(e.handleObj=o,e.data=o.data,r=((x.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,a),r!==undefined&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return l.postDispatch&&l.postDispatch.call(this,e),e.result}},handlers:function(e,t){var n,r,i,o,s=[],a=t.delegateCount,u=e.target;if(a&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!==this;u=u.parentNode||this)if(u.disabled!==!0||"click"!==e.type){for(r=[],n=0;a>n;n++)o=t[n],i=o.selector+" ",r[i]===undefined&&(r[i]=o.needsContext?x(i,this).index(u)>=0:x.find(i,this,null,[u]).length),r[i]&&r.push(o);r.length&&s.push({elem:u,handlers:r})}return t.length>a&&s.push({elem:this,handlers:t.slice(a)}),s},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,t){var n,r,i,s=t.button;return null==e.pageX&&null!=t.clientX&&(n=e.target.ownerDocument||o,r=n.documentElement,i=n.body,e.pageX=t.clientX+(r&&r.scrollLeft||i&&i.scrollLeft||0)-(r&&r.clientLeft||i&&i.clientLeft||0),e.pageY=t.clientY+(r&&r.scrollTop||i&&i.scrollTop||0)-(r&&r.clientTop||i&&i.clientTop||0)),e.which||s===undefined||(e.which=1&s?1:2&s?3:4&s?2:0),e}},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,o=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=z.test(i)?this.mouseHooks:I.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new x.Event(o),t=r.length;while(t--)n=r[t],e[n]=o[n];return 3===e.target.nodeType&&(e.target=e.target.parentNode),s.filter?s.filter(e,o):e},special:{load:{noBubble:!0},focus:{trigger:function(){return this!==V()&&this.focus?(this.focus(),!1):undefined},delegateType:"focusin"},blur:{trigger:function(){return this===V()&&this.blur?(this.blur(),!1):undefined},delegateType:"focusout"},click:{trigger:function(){return"checkbox"===this.type&&this.click&&x.nodeName(this,"input")?(this.click(),!1):undefined},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==undefined&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)},x.Event=function(e,t){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.getPreventDefault&&e.getPreventDefault()?U:Y):this.type=e,t&&x.extend(this,t),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,undefined):new x.Event(e,t)},x.Event.prototype={isDefaultPrevented:Y,isPropagationStopped:Y,isImmediatePropagationStopped:Y,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=U,e&&e.preventDefault&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=U,e&&e.stopPropagation&&e.stopPropagation()},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=U,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&o.addEventListener(e,r,!0)},teardown:function(){0===--n&&o.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,t,n,r,i){var o,s;if("object"==typeof e){"string"!=typeof t&&(n=n||t,t=undefined);for(s in e)this.on(s,t,n,e[s],i);return this}if(null==n&&null==r?(r=t,n=t=undefined):null==r&&("string"==typeof t?(r=n,n=undefined):(r=n,n=t,t=undefined)),r===!1)r=Y;else if(!r)return this;return 1===i&&(o=r,r=function(e){return x().off(e),o.apply(this,arguments)},r.guid=o.guid||(o.guid=x.guid++)),this.each(function(){x.event.add(this,e,r,n,t)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,x(e.delegateTarget).off(r.namespace?r.origType+"."+r.namespace:r.origType,r.selector,r.handler),this;if("object"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return(t===!1||"function"==typeof t)&&(n=t,t=undefined),n===!1&&(n=Y),this.each(function(){x.event.remove(this,e,n,t)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];return n?x.event.trigger(e,t,n,!0):undefined}});var G=/^.[^:#\[\.,]*$/,J=x.expr.match.needsContext,Q={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n,r,i=this.length;if("string"!=typeof e)return t=this,this.pushStack(x(e).filter(function(){for(r=0;i>r;r++)if(x.contains(t[r],this))return!0}));for(n=[],r=0;i>r;r++)x.find(e,this[r],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=(this.selector?this.selector+" ":"")+e,n},has:function(e){var t=x(e,this),n=t.length;return this.filter(function(){var e=0;for(;n>e;e++)if(x.contains(this,t[e]))return!0})},not:function(e){return this.pushStack(Z(this,e||[],!0))},filter:function(e){return this.pushStack(Z(this,e||[],!1))},is:function(e){return!!e&&("string"==typeof e?J.test(e)?x(e,this.context).index(this[0])>=0:x.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,o=[],s=J.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(s?s.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?g.call(x(e),this[0]):g.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function K(e,t){while((e=e[t])&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return K(e,"nextSibling")},prev:function(e){return K(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return x.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(Q[e]||x.unique(i),"p"===e[0]&&i.reverse()),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,t,n){var r=[],i=n!==undefined;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&x(e).is(n))break;r.push(e)}return r},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function Z(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(G.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return g.call(t,e)>=0!==n})}var et=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,tt=/<([\w:]+)/,nt=/<|&#?\w+;/,rt=/<(?:script|style|link)/i,it=/^(?:checkbox|radio)$/i,ot=/checked\s*(?:[^=]|=\s*.checked.)/i,st=/^$|\/(?:java|ecma)script/i,at=/^true\/(.*)/,ut=/^\s*\s*$/g,lt={option:[1,""],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};lt.optgroup=lt.option,lt.tbody=lt.tfoot=lt.colgroup=lt.caption=lt.col=lt.thead,lt.th=lt.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===undefined?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||o).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=ct(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=ct(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(gt(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&ht(gt(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++)1===e.nodeType&&(x.cleanData(gt(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var t=this[0]||{},n=0,r=this.length;if(e===undefined&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!rt.test(e)&&!lt[(tt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(et,"<$1>");try{for(;r>n;n++)t=this[n]||{},1===t.nodeType&&(x.cleanData(gt(t,!1)),t.innerHTML=e);t=0}catch(i){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=p.apply([],e);var r,i,o,s,a,u,l=0,c=this.length,f=this,h=c-1,d=e[0],g=x.isFunction(d);if(g||!(1>=c||"string"!=typeof d||x.support.checkClone)&&ot.test(d))return this.each(function(r){var i=f.eq(r);g&&(e[0]=d.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(r=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),i=r.firstChild,1===r.childNodes.length&&(r=i),i)){for(o=x.map(gt(r,"script"),ft),s=o.length;c>l;l++)a=r,l!==h&&(a=x.clone(a,!0,!0),s&&x.merge(o,gt(a,"script"))),t.call(this[l],a,l);if(s)for(u=o[o.length-1].ownerDocument,x.map(o,pt),l=0;s>l;l++)a=o[l],st.test(a.type||"")&&!q.access(a,"globalEval")&&x.contains(u,a)&&(a.src?x._evalUrl(a.src):x.globalEval(a.textContent.replace(ut,"")))}return this}}),x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=[],i=x(e),o=i.length-1,s=0;for(;o>=s;s++)n=s===o?this:this.clone(!0),x(i[s])[t](n),h.apply(r,n.get());return this.pushStack(r)}}),x.extend({clone:function(e,t,n){var r,i,o,s,a=e.cloneNode(!0),u=x.contains(e.ownerDocument,e);if(!(x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(s=gt(a),o=gt(e),r=0,i=o.length;i>r;r++)mt(o[r],s[r]);if(t)if(n)for(o=o||gt(e),s=s||gt(a),r=0,i=o.length;i>r;r++)dt(o[r],s[r]);else dt(e,a);return s=gt(a,"script"),s.length>0&&ht(s,!u&>(e,"script")),a},buildFragment:function(e,t,n,r){var i,o,s,a,u,l,c=0,f=e.length,p=t.createDocumentFragment(),h=[];for(;f>c;c++)if(i=e[c],i||0===i)if("object"===x.type(i))x.merge(h,i.nodeType?[i]:i);else if(nt.test(i)){o=o||p.appendChild(t.createElement("div")),s=(tt.exec(i)||["",""])[1].toLowerCase(),a=lt[s]||lt._default,o.innerHTML=a[1]+i.replace(et,"<$1>")+a[2],l=a[0];while(l--)o=o.firstChild;x.merge(h,o.childNodes),o=p.firstChild,o.textContent=""}else h.push(t.createTextNode(i));p.textContent="",c=0;while(i=h[c++])if((!r||-1===x.inArray(i,r))&&(u=x.contains(i.ownerDocument,i),o=gt(p.appendChild(i),"script"),u&&ht(o),n)){l=0;while(i=o[l++])st.test(i.type||"")&&n.push(i)}return p},cleanData:function(e){var t,n,r,i=e.length,o=0,s=x.event.special;for(;i>o;o++){if(n=e[o],x.acceptData(n)&&(t=q.access(n)))for(r in t.events)s[r]?x.event.remove(n,r):x.removeEvent(n,r,t.handle);L.discard(n),q.discard(n)}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"text",async:!1,global:!1,success:x.globalEval})}});function ct(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function ft(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function pt(e){var t=at.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function ht(e,t){var n=e.length,r=0;for(;n>r;r++)q.set(e[r],"globalEval",!t||q.get(t[r],"globalEval"))}function dt(e,t){var n,r,i,o,s,a,u,l;if(1===t.nodeType){if(q.hasData(e)&&(o=q.access(e),s=x.extend({},o),l=o.events,q.set(t,s),l)){delete s.handle,s.events={};for(i in l)for(n=0,r=l[i].length;r>n;n++)x.event.add(t,i,l[i][n])}L.hasData(e)&&(a=L.access(e),u=x.extend({},a),L.set(t,u))}}function gt(e,t){var n=e.getElementsByTagName?e.getElementsByTagName(t||"*"):e.querySelectorAll?e.querySelectorAll(t||"*"):[];return t===undefined||t&&x.nodeName(e,t)?x.merge([e],n):n}function mt(e,t){var n=t.nodeName.toLowerCase();"input"===n&&it.test(e.type)?t.checked=e.checked:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}x.fn.extend({wrapAll:function(e){var t;return x.isFunction(e)?this.each(function(t){x(this).wrapAll(e.call(this,t))}):(this[0]&&(t=x(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this)},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var yt,vt,xt=/^(none|table(?!-c[ea]).+)/,bt=/^margin/,wt=RegExp("^("+b+")(.*)$","i"),Tt=RegExp("^("+b+")(?!px)[a-z%]+$","i"),Ct=RegExp("^([+-])=("+b+")","i"),kt={BODY:"block"},Nt={position:"absolute",visibility:"hidden",display:"block"},Et={letterSpacing:0,fontWeight:400},St=["Top","Right","Bottom","Left"],jt=["Webkit","O","Moz","ms"];function Dt(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=jt.length;while(i--)if(t=jt[i]+n,t in e)return t;return r}function At(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function Lt(t){return e.getComputedStyle(t,null)}function qt(e,t){var n,r,i,o=[],s=0,a=e.length;for(;a>s;s++)r=e[s],r.style&&(o[s]=q.get(r,"olddisplay"),n=r.style.display,t?(o[s]||"none"!==n||(r.style.display=""),""===r.style.display&&At(r)&&(o[s]=q.access(r,"olddisplay",Pt(r.nodeName)))):o[s]||(i=At(r),(n&&"none"!==n||!i)&&q.set(r,"olddisplay",i?n:x.css(r,"display"))));for(s=0;a>s;s++)r=e[s],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[s]||"":"none"));return e}x.fn.extend({css:function(e,t){return x.access(this,function(e,t,n){var r,i,o={},s=0;if(x.isArray(t)){for(r=Lt(e),i=t.length;i>s;s++)o[t[s]]=x.css(e,t[s],!1,r);return o}return n!==undefined?x.style(e,t,n):x.css(e,t)},e,t,arguments.length>1)},show:function(){return qt(this,!0)},hide:function(){return qt(this)},toggle:function(e){var t="boolean"==typeof e;return this.each(function(){(t?e:At(this))?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=yt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,s,a=x.camelCase(t),u=e.style;return t=x.cssProps[a]||(x.cssProps[a]=Dt(u,a)),s=x.cssHooks[t]||x.cssHooks[a],n===undefined?s&&"get"in s&&(i=s.get(e,!1,r))!==undefined?i:u[t]:(o=typeof n,"string"===o&&(i=Ct.exec(n))&&(n=(i[1]+1)*i[2]+parseFloat(x.css(e,t)),o="number"),null==n||"number"===o&&isNaN(n)||("number"!==o||x.cssNumber[a]||(n+="px"),x.support.clearCloneStyle||""!==n||0!==t.indexOf("background")||(u[t]="inherit"),s&&"set"in s&&(n=s.set(e,n,r))===undefined||(u[t]=n)),undefined)}},css:function(e,t,n,r){var i,o,s,a=x.camelCase(t);return t=x.cssProps[a]||(x.cssProps[a]=Dt(e.style,a)),s=x.cssHooks[t]||x.cssHooks[a],s&&"get"in s&&(i=s.get(e,!0,n)),i===undefined&&(i=yt(e,t,r)),"normal"===i&&t in Et&&(i=Et[t]),""===n||n?(o=parseFloat(i),n===!0||x.isNumeric(o)?o||0:i):i}}),yt=function(e,t,n){var r,i,o,s=n||Lt(e),a=s?s.getPropertyValue(t)||s[t]:undefined,u=e.style;return s&&(""!==a||x.contains(e.ownerDocument,e)||(a=x.style(e,t)),Tt.test(a)&&bt.test(t)&&(r=u.width,i=u.minWidth,o=u.maxWidth,u.minWidth=u.maxWidth=u.width=a,a=s.width,u.width=r,u.minWidth=i,u.maxWidth=o)),a};function Ht(e,t,n){var r=wt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function Ot(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,s=0;for(;4>o;o+=2)"margin"===n&&(s+=x.css(e,n+St[o],!0,i)),r?("content"===n&&(s-=x.css(e,"padding"+St[o],!0,i)),"margin"!==n&&(s-=x.css(e,"border"+St[o]+"Width",!0,i))):(s+=x.css(e,"padding"+St[o],!0,i),"padding"!==n&&(s+=x.css(e,"border"+St[o]+"Width",!0,i)));return s}function Ft(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Lt(e),s=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=yt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Tt.test(i))return i;r=s&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+Ot(e,t,n||(s?"border":"content"),r,o)+"px"}function Pt(e){var t=o,n=kt[e];return n||(n=Rt(e,t),"none"!==n&&n||(vt=(vt||x("'):b(''); -j=a.theme?b(''):b('');a.theme&&f?(c=''):a.theme?(c='"):c=f?'':'';c=b(c);g&&(a.theme?(c.css(h),c.addClass("ui-widget-content")):c.css(e));a.theme||j.css(a.overlayCSS);j.css("position",f?"fixed":"absolute");(s||a.forceIframe)&&k.css("opacity",0);e=[k,j,c];var q=f?b("body"):b(d);b.each(e,function(){this.appendTo(q)});a.theme&&(a.draggable&&b.fn.draggable)&&c.draggable({handle:".ui-dialog-titlebar",cancel:"li"}); -h=z&&(!b.support.boxModel||0b)f?c.setExpression("height","Math.max(document.body.scrollHeight, document.body.offsetHeight) - (jQuery.support.boxModel?0:"+ -a.quirksmodeOffsetHack+') + "px"'):c.setExpression("height",'this.parentNode.offsetHeight + "px"'),f?c.setExpression("width",'jQuery.support.boxModel && document.documentElement.clientWidth || document.body.clientWidth + "px"'):c.setExpression("width",'this.parentNode.offsetWidth + "px"'),w&&c.setExpression("left",w),v&&c.setExpression("top",v);else if(a.centerY)f&&c.setExpression("top",'(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"'), -c.marginTop=0;else if(!a.centerY&&f){var e="((document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "+(a.css&&a.css.top?parseInt(a.css.top,10):0)+') + "px"';c.setExpression("top",e)}})}g&&(a.theme?c.find(".ui-widget-content").append(g):c.append(g),(g.jquery||g.nodeType)&&b(g).show());(s||a.forceIframe)&&a.showOverlay&&k.show();if(a.fadeIn)e=a.onBlock?a.onBlock:t,k=a.showOverlay&&!g?e:t,e=g?e:t,a.showOverlay&&j._fadeIn(a.fadeIn,k),g&&c._fadeIn(a.fadeIn, -e);else if(a.showOverlay&&j.show(),g&&c.show(),a.onBlock)a.onBlock();x(1,d,a);f?(l=c[0],m=b(a.focusableElements,l),a.focusInput&&setTimeout(y,20)):(e=c[0],g=a.centerX,j=a.centerY,h=e.parentNode,c=e.style,k=(h.offsetWidth-e.offsetWidth)/2-(parseInt(b.css(h,"borderLeftWidth"),10)||0),e=(h.offsetHeight-e.offsetHeight)/2-(parseInt(b.css(h,"borderTopWidth"),10)||0),g&&(c.left=0 .blockUI"):f.find(">.blockUI");a.cursorReset&&(1');d&&f.append("

"+d+"

");a&&f.append("

"+a+"

");void 0===e&&(e=3E3);b.blockUI({message:f,fadeIn:700,fadeOut:1E3,centerY:!1,timeout:e,showOverlay:!1,onUnblock:h,css:b.blockUI.defaults.growlCSS})};b.fn.block=function(d){if(this[0]===window)return b.blockUI(d),this;var a=b.extend({},b.blockUI.defaults,d||{});this.each(function(){var d=b(this);(!a.ignoreIfBlocked|| -!d.data("blockUI.isBlocked"))&&d.unblock({fadeOut:0})});return this.each(function(){"static"==b.css(this,"position")&&(this.style.position="relative",b(this).data("blockUI.static",!0));this.style.zoom=1;n(this,d)})};b.fn.unblock=function(d){return this[0]===window?(b.unblockUI(d),this):this.each(function(){r(this,d)})};b.blockUI.version=2.6;b.blockUI.defaults={message:"

Please wait...

",title:null,draggable:!0,theme:!1,css:{padding:0,margin:0,width:"30%",top:"40%",left:"35%",textAlign:"center", -color:"#000",border:"3px solid #aaa",backgroundColor:"#fff",cursor:"wait"},themedCSS:{width:"30%",top:"40%",left:"35%"},overlayCSS:{backgroundColor:"#000",opacity:0.6,cursor:"wait"},cursorReset:"default",growlCSS:{width:"350px",top:"10px",left:"",right:"10px",border:"none",padding:"5px",opacity:0.6,cursor:"default",color:"#fff",backgroundColor:"#000","-webkit-border-radius":"10px","-moz-border-radius":"10px","border-radius":"10px"},iframeSrc:/^https/i.test(window.location.href||"")?"javascript:false": -"about:blank",forceIframe:!1,baseZ:1E3,centerX:!0,centerY:!0,allowBodyStretch:!0,bindEvents:!0,constrainTabKey:!0,fadeIn:200,fadeOut:400,timeout:0,showOverlay:!0,focusInput:!0,focusableElements:":input:enabled:visible",onBlock:null,onUnblock:null,onOverlayClick:null,quirksmodeOffsetHack:4,blockMsgClass:"blockMsg",ignoreIfBlocked:!1};var l=null,m=[]}"function"===typeof define&&define.amd&&define.amd.jQuery?define(["jquery"],n):n(jQuery)})(); \ No newline at end of file diff --git a/DBSR_GUI_Resources/js/jquery-blockui-2.70.min.js b/DBSR_GUI_Resources/js/jquery-blockui-2.70.min.js new file mode 100644 index 0000000..90ce5d6 --- /dev/null +++ b/DBSR_GUI_Resources/js/jquery-blockui-2.70.min.js @@ -0,0 +1,620 @@ +/*! + * jQuery blockUI plugin + * Version 2.70.0-2014.11.23 + * Requires jQuery v1.7 or later + * + * Examples at: http://malsup.com/jquery/block/ + * Copyright (c) 2007-2013 M. Alsup + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + * Thanks to Amir-Hossein Sobhi for some excellent contributions! + */ + +;(function() { +/*jshint eqeqeq:false curly:false latedef:false */ +"use strict"; + + function setup($) { + $.fn._fadeIn = $.fn.fadeIn; + + var noOp = $.noop || function() {}; + + // this bit is to ensure we don't call setExpression when we shouldn't (with extra muscle to handle + // confusing userAgent strings on Vista) + var msie = /MSIE/.test(navigator.userAgent); + var ie6 = /MSIE 6.0/.test(navigator.userAgent) && ! /MSIE 8.0/.test(navigator.userAgent); + var mode = document.documentMode || 0; + var setExpr = $.isFunction( document.createElement('div').style.setExpression ); + + // global $ methods for blocking/unblocking the entire page + $.blockUI = function(opts) { install(window, opts); }; + $.unblockUI = function(opts) { remove(window, opts); }; + + // convenience method for quick growl-like notifications (http://www.google.com/search?q=growl) + $.growlUI = function(title, message, timeout, onClose) { + var $m = $('
'); + if (title) $m.append('

'+title+'

'); + if (message) $m.append('

'+message+'

'); + if (timeout === undefined) timeout = 3000; + + // Added by konapun: Set timeout to 30 seconds if this growl is moused over, like normal toast notifications + var callBlock = function(opts) { + opts = opts || {}; + + $.blockUI({ + message: $m, + fadeIn : typeof opts.fadeIn !== 'undefined' ? opts.fadeIn : 700, + fadeOut: typeof opts.fadeOut !== 'undefined' ? opts.fadeOut : 1000, + timeout: typeof opts.timeout !== 'undefined' ? opts.timeout : timeout, + centerY: false, + showOverlay: false, + onUnblock: onClose, + css: $.blockUI.defaults.growlCSS + }); + }; + + callBlock(); + var nonmousedOpacity = $m.css('opacity'); + $m.mouseover(function() { + callBlock({ + fadeIn: 0, + timeout: 30000 + }); + + var displayBlock = $('.blockMsg'); + displayBlock.stop(); // cancel fadeout if it has started + displayBlock.fadeTo(300, 1); // make it easier to read the message by removing transparency + }).mouseout(function() { + $('.blockMsg').fadeOut(1000); + }); + // End konapun additions + }; + + // plugin method for blocking element content + $.fn.block = function(opts) { + if ( this[0] === window ) { + $.blockUI( opts ); + return this; + } + var fullOpts = $.extend({}, $.blockUI.defaults, opts || {}); + this.each(function() { + var $el = $(this); + if (fullOpts.ignoreIfBlocked && $el.data('blockUI.isBlocked')) + return; + $el.unblock({ fadeOut: 0 }); + }); + + return this.each(function() { + if ($.css(this,'position') == 'static') { + this.style.position = 'relative'; + $(this).data('blockUI.static', true); + } + this.style.zoom = 1; // force 'hasLayout' in ie + install(this, opts); + }); + }; + + // plugin method for unblocking element content + $.fn.unblock = function(opts) { + if ( this[0] === window ) { + $.unblockUI( opts ); + return this; + } + return this.each(function() { + remove(this, opts); + }); + }; + + $.blockUI.version = 2.70; // 2nd generation blocking at no extra cost! + + // override these in your code to change the default behavior and style + $.blockUI.defaults = { + // message displayed when blocking (use null for no message) + message: '

Please wait...

', + + title: null, // title string; only used when theme == true + draggable: true, // only used when theme == true (requires jquery-ui.js to be loaded) + + theme: false, // set to true to use with jQuery UI themes + + // styles for the message when blocking; if you wish to disable + // these and use an external stylesheet then do this in your code: + // $.blockUI.defaults.css = {}; + css: { + padding: 0, + margin: 0, + width: '30%', + top: '40%', + left: '35%', + textAlign: 'center', + color: '#000', + border: '3px solid #aaa', + backgroundColor:'#fff', + cursor: 'wait' + }, + + // minimal style set used when themes are used + themedCSS: { + width: '30%', + top: '40%', + left: '35%' + }, + + // styles for the overlay + overlayCSS: { + backgroundColor: '#000', + opacity: 0.6, + cursor: 'wait' + }, + + // style to replace wait cursor before unblocking to correct issue + // of lingering wait cursor + cursorReset: 'default', + + // styles applied when using $.growlUI + growlCSS: { + width: '350px', + top: '10px', + left: '', + right: '10px', + border: 'none', + padding: '5px', + opacity: 0.6, + cursor: 'default', + color: '#fff', + backgroundColor: '#000', + '-webkit-border-radius':'10px', + '-moz-border-radius': '10px', + 'border-radius': '10px' + }, + + // IE issues: 'about:blank' fails on HTTPS and javascript:false is s-l-o-w + // (hat tip to Jorge H. N. de Vasconcelos) + /*jshint scripturl:true */ + iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank', + + // force usage of iframe in non-IE browsers (handy for blocking applets) + forceIframe: false, + + // z-index for the blocking overlay + baseZ: 1000, + + // set these to true to have the message automatically centered + centerX: true, // <-- only effects element blocking (page block controlled via css above) + centerY: true, + + // allow body element to be stetched in ie6; this makes blocking look better + // on "short" pages. disable if you wish to prevent changes to the body height + allowBodyStretch: true, + + // enable if you want key and mouse events to be disabled for content that is blocked + bindEvents: true, + + // be default blockUI will supress tab navigation from leaving blocking content + // (if bindEvents is true) + constrainTabKey: true, + + // fadeIn time in millis; set to 0 to disable fadeIn on block + fadeIn: 200, + + // fadeOut time in millis; set to 0 to disable fadeOut on unblock + fadeOut: 400, + + // time in millis to wait before auto-unblocking; set to 0 to disable auto-unblock + timeout: 0, + + // disable if you don't want to show the overlay + showOverlay: true, + + // if true, focus will be placed in the first available input field when + // page blocking + focusInput: true, + + // elements that can receive focus + focusableElements: ':input:enabled:visible', + + // suppresses the use of overlay styles on FF/Linux (due to performance issues with opacity) + // no longer needed in 2012 + // applyPlatformOpacityRules: true, + + // callback method invoked when fadeIn has completed and blocking message is visible + onBlock: null, + + // callback method invoked when unblocking has completed; the callback is + // passed the element that has been unblocked (which is the window object for page + // blocks) and the options that were passed to the unblock call: + // onUnblock(element, options) + onUnblock: null, + + // callback method invoked when the overlay area is clicked. + // setting this will turn the cursor to a pointer, otherwise cursor defined in overlayCss will be used. + onOverlayClick: null, + + // don't ask; if you really must know: http://groups.google.com/group/jquery-en/browse_thread/thread/36640a8730503595/2f6a79a77a78e493#2f6a79a77a78e493 + quirksmodeOffsetHack: 4, + + // class name of the message block + blockMsgClass: 'blockMsg', + + // if it is already blocked, then ignore it (don't unblock and reblock) + ignoreIfBlocked: false + }; + + // private data and functions follow... + + var pageBlock = null; + var pageBlockEls = []; + + function install(el, opts) { + var css, themedCSS; + var full = (el == window); + var msg = (opts && opts.message !== undefined ? opts.message : undefined); + opts = $.extend({}, $.blockUI.defaults, opts || {}); + + if (opts.ignoreIfBlocked && $(el).data('blockUI.isBlocked')) + return; + + opts.overlayCSS = $.extend({}, $.blockUI.defaults.overlayCSS, opts.overlayCSS || {}); + css = $.extend({}, $.blockUI.defaults.css, opts.css || {}); + if (opts.onOverlayClick) + opts.overlayCSS.cursor = 'pointer'; + + themedCSS = $.extend({}, $.blockUI.defaults.themedCSS, opts.themedCSS || {}); + msg = msg === undefined ? opts.message : msg; + + // remove the current block (if there is one) + if (full && pageBlock) + remove(window, {fadeOut:0}); + + // if an existing element is being used as the blocking content then we capture + // its current place in the DOM (and current display style) so we can restore + // it when we unblock + if (msg && typeof msg != 'string' && (msg.parentNode || msg.jquery)) { + var node = msg.jquery ? msg[0] : msg; + var data = {}; + $(el).data('blockUI.history', data); + data.el = node; + data.parent = node.parentNode; + data.display = node.style.display; + data.position = node.style.position; + if (data.parent) + data.parent.removeChild(node); + } + + $(el).data('blockUI.onUnblock', opts.onUnblock); + var z = opts.baseZ; + + // blockUI uses 3 layers for blocking, for simplicity they are all used on every platform; + // layer1 is the iframe layer which is used to supress bleed through of underlying content + // layer2 is the overlay layer which has opacity and a wait cursor (by default) + // layer3 is the message content that is displayed while blocking + var lyr1, lyr2, lyr3, s; + if (msie || opts.forceIframe) + lyr1 = $(''); + else + lyr1 = $(''); + + if (opts.theme) + lyr2 = $(''); + else + lyr2 = $(''); + + if (opts.theme && full) { + s = ''; + } + else if (opts.theme) { + s = ''; + } + else if (full) { + s = ''; + } + else { + s = ''; + } + lyr3 = $(s); + + // if we have a message, style it + if (msg) { + if (opts.theme) { + lyr3.css(themedCSS); + lyr3.addClass('ui-widget-content'); + } + else + lyr3.css(css); + } + + // style the overlay + if (!opts.theme /*&& (!opts.applyPlatformOpacityRules)*/) + lyr2.css(opts.overlayCSS); + lyr2.css('position', full ? 'fixed' : 'absolute'); + + // make iframe layer transparent in IE + if (msie || opts.forceIframe) + lyr1.css('opacity',0.0); + + //$([lyr1[0],lyr2[0],lyr3[0]]).appendTo(full ? 'body' : el); + var layers = [lyr1,lyr2,lyr3], $par = full ? $('body') : $(el); + $.each(layers, function() { + this.appendTo($par); + }); + + if (opts.theme && opts.draggable && $.fn.draggable) { + lyr3.draggable({ + handle: '.ui-dialog-titlebar', + cancel: 'li' + }); + } + + // ie7 must use absolute positioning in quirks mode and to account for activex issues (when scrolling) + var expr = setExpr && (!$.support.boxModel || $('object,embed', full ? null : el).length > 0); + if (ie6 || expr) { + // give body 100% height + if (full && opts.allowBodyStretch && $.support.boxModel) + $('html,body').css('height','100%'); + + // fix ie6 issue when blocked element has a border width + if ((ie6 || !$.support.boxModel) && !full) { + var t = sz(el,'borderTopWidth'), l = sz(el,'borderLeftWidth'); + var fixT = t ? '(0 - '+t+')' : 0; + var fixL = l ? '(0 - '+l+')' : 0; + } + + // simulate fixed position + $.each(layers, function(i,o) { + var s = o[0].style; + s.position = 'absolute'; + if (i < 2) { + if (full) + s.setExpression('height','Math.max(document.body.scrollHeight, document.body.offsetHeight) - (jQuery.support.boxModel?0:'+opts.quirksmodeOffsetHack+') + "px"'); + else + s.setExpression('height','this.parentNode.offsetHeight + "px"'); + if (full) + s.setExpression('width','jQuery.support.boxModel && document.documentElement.clientWidth || document.body.clientWidth + "px"'); + else + s.setExpression('width','this.parentNode.offsetWidth + "px"'); + if (fixL) s.setExpression('left', fixL); + if (fixT) s.setExpression('top', fixT); + } + else if (opts.centerY) { + if (full) s.setExpression('top','(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"'); + s.marginTop = 0; + } + else if (!opts.centerY && full) { + var top = (opts.css && opts.css.top) ? parseInt(opts.css.top, 10) : 0; + var expression = '((document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + '+top+') + "px"'; + s.setExpression('top',expression); + } + }); + } + + // show the message + if (msg) { + if (opts.theme) + lyr3.find('.ui-widget-content').append(msg); + else + lyr3.append(msg); + if (msg.jquery || msg.nodeType) + $(msg).show(); + } + + if ((msie || opts.forceIframe) && opts.showOverlay) + lyr1.show(); // opacity is zero + if (opts.fadeIn) { + var cb = opts.onBlock ? opts.onBlock : noOp; + var cb1 = (opts.showOverlay && !msg) ? cb : noOp; + var cb2 = msg ? cb : noOp; + if (opts.showOverlay) + lyr2._fadeIn(opts.fadeIn, cb1); + if (msg) + lyr3._fadeIn(opts.fadeIn, cb2); + } + else { + if (opts.showOverlay) + lyr2.show(); + if (msg) + lyr3.show(); + if (opts.onBlock) + opts.onBlock.bind(lyr3)(); + } + + // bind key and mouse events + bind(1, el, opts); + + if (full) { + pageBlock = lyr3[0]; + pageBlockEls = $(opts.focusableElements,pageBlock); + if (opts.focusInput) + setTimeout(focus, 20); + } + else + center(lyr3[0], opts.centerX, opts.centerY); + + if (opts.timeout) { + // auto-unblock + var to = setTimeout(function() { + if (full) + $.unblockUI(opts); + else + $(el).unblock(opts); + }, opts.timeout); + $(el).data('blockUI.timeout', to); + } + } + + // remove the block + function remove(el, opts) { + var count; + var full = (el == window); + var $el = $(el); + var data = $el.data('blockUI.history'); + var to = $el.data('blockUI.timeout'); + if (to) { + clearTimeout(to); + $el.removeData('blockUI.timeout'); + } + opts = $.extend({}, $.blockUI.defaults, opts || {}); + bind(0, el, opts); // unbind events + + if (opts.onUnblock === null) { + opts.onUnblock = $el.data('blockUI.onUnblock'); + $el.removeData('blockUI.onUnblock'); + } + + var els; + if (full) // crazy selector to handle odd field errors in ie6/7 + els = $('body').children().filter('.blockUI').add('body > .blockUI'); + else + els = $el.find('>.blockUI'); + + // fix cursor issue + if ( opts.cursorReset ) { + if ( els.length > 1 ) + els[1].style.cursor = opts.cursorReset; + if ( els.length > 2 ) + els[2].style.cursor = opts.cursorReset; + } + + if (full) + pageBlock = pageBlockEls = null; + + if (opts.fadeOut) { + count = els.length; + els.stop().fadeOut(opts.fadeOut, function() { + if ( --count === 0) + reset(els,data,opts,el); + }); + } + else + reset(els, data, opts, el); + } + + // move blocking element back into the DOM where it started + function reset(els,data,opts,el) { + var $el = $(el); + if ( $el.data('blockUI.isBlocked') ) + return; + + els.each(function(i,o) { + // remove via DOM calls so we don't lose event handlers + if (this.parentNode) + this.parentNode.removeChild(this); + }); + + if (data && data.el) { + data.el.style.display = data.display; + data.el.style.position = data.position; + data.el.style.cursor = 'default'; // #59 + if (data.parent) + data.parent.appendChild(data.el); + $el.removeData('blockUI.history'); + } + + if ($el.data('blockUI.static')) { + $el.css('position', 'static'); // #22 + } + + if (typeof opts.onUnblock == 'function') + opts.onUnblock(el,opts); + + // fix issue in Safari 6 where block artifacts remain until reflow + var body = $(document.body), w = body.width(), cssW = body[0].style.width; + body.width(w-1).width(w); + body[0].style.width = cssW; + } + + // bind/unbind the handler + function bind(b, el, opts) { + var full = el == window, $el = $(el); + + // don't bother unbinding if there is nothing to unbind + if (!b && (full && !pageBlock || !full && !$el.data('blockUI.isBlocked'))) + return; + + $el.data('blockUI.isBlocked', b); + + // don't bind events when overlay is not in use or if bindEvents is false + if (!full || !opts.bindEvents || (b && !opts.showOverlay)) + return; + + // bind anchors and inputs for mouse and key events + var events = 'mousedown mouseup keydown keypress keyup touchstart touchend touchmove'; + if (b) + $(document).bind(events, opts, handler); + else + $(document).unbind(events, handler); + + // former impl... + // var $e = $('a,:input'); + // b ? $e.bind(events, opts, handler) : $e.unbind(events, handler); + } + + // event handler to suppress keyboard/mouse events when blocking + function handler(e) { + // allow tab navigation (conditionally) + if (e.type === 'keydown' && e.keyCode && e.keyCode == 9) { + if (pageBlock && e.data.constrainTabKey) { + var els = pageBlockEls; + var fwd = !e.shiftKey && e.target === els[els.length-1]; + var back = e.shiftKey && e.target === els[0]; + if (fwd || back) { + setTimeout(function(){focus(back);},10); + return false; + } + } + } + var opts = e.data; + var target = $(e.target); + if (target.hasClass('blockOverlay') && opts.onOverlayClick) + opts.onOverlayClick(e); + + // allow events within the message content + if (target.parents('div.' + opts.blockMsgClass).length > 0) + return true; + + // allow events for content that is not being blocked + return target.parents().children().filter('div.blockUI').length === 0; + } + + function focus(back) { + if (!pageBlockEls) + return; + var e = pageBlockEls[back===true ? pageBlockEls.length-1 : 0]; + if (e) + e.focus(); + } + + function center(el, x, y) { + var p = el.parentNode, s = el.style; + var l = ((p.offsetWidth - el.offsetWidth)/2) - sz(p,'borderLeftWidth'); + var t = ((p.offsetHeight - el.offsetHeight)/2) - sz(p,'borderTopWidth'); + if (x) s.left = l > 0 ? (l+'px') : '0'; + if (y) s.top = t > 0 ? (t+'px') : '0'; + } + + function sz(el, p) { + return parseInt($.css(el,p),10)||0; + } + + } + + + /*global define:true */ + if (typeof define === 'function' && define.amd && define.amd.jQuery) { + define(['jquery'], setup); + } else { + setup(jQuery); + } + +})(); diff --git a/DBSR_GUI_Resources/js/jquery-ui-1.10.2.custom.min.js b/DBSR_GUI_Resources/js/jquery-ui-1.10.2.custom.min.js deleted file mode 100644 index 0d7a75a..0000000 --- a/DBSR_GUI_Resources/js/jquery-ui-1.10.2.custom.min.js +++ /dev/null @@ -1,6 +0,0 @@ -/*! jQuery UI - v1.10.2 - 2013-04-04 -* http://jqueryui.com -* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.position.js, jquery.ui.autocomplete.js, jquery.ui.menu.js -* Copyright 2013 jQuery Foundation and other contributors Licensed MIT */ - -(function(e,t){function i(t,i){var a,n,r,o=t.nodeName.toLowerCase();return"area"===o?(a=t.parentNode,n=a.name,t.href&&n&&"map"===a.nodeName.toLowerCase()?(r=e("img[usemap=#"+n+"]")[0],!!r&&s(r)):!1):(/input|select|textarea|button|object/.test(o)?!t.disabled:"a"===o?t.href||i:i)&&s(t)}function s(t){return e.expr.filters.visible(t)&&!e(t).parents().addBack().filter(function(){return"hidden"===e.css(this,"visibility")}).length}var a=0,n=/^ui-id-\d+$/;e.ui=e.ui||{},e.extend(e.ui,{version:"1.10.2",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({focus:function(t){return function(i,s){return"number"==typeof i?this.each(function(){var t=this;setTimeout(function(){e(t).focus(),s&&s.call(t)},i)}):t.apply(this,arguments)}}(e.fn.focus),scrollParent:function(){var t;return t=e.ui.ie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(e.css(this,"position"))&&/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0),/fixed/.test(this.css("position"))||!t.length?e(document):t},zIndex:function(i){if(i!==t)return this.css("zIndex",i);if(this.length)for(var s,a,n=e(this[0]);n.length&&n[0]!==document;){if(s=n.css("position"),("absolute"===s||"relative"===s||"fixed"===s)&&(a=parseInt(n.css("zIndex"),10),!isNaN(a)&&0!==a))return a;n=n.parent()}return 0},uniqueId:function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++a)})},removeUniqueId:function(){return this.each(function(){n.test(this.id)&&e(this).removeAttr("id")})}}),e.extend(e.expr[":"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(i){return!!e.data(i,t)}}):function(t,i,s){return!!e.data(t,s[3])},focusable:function(t){return i(t,!isNaN(e.attr(t,"tabindex")))},tabbable:function(t){var s=e.attr(t,"tabindex"),a=isNaN(s);return(a||s>=0)&&i(t,!a)}}),e("").outerWidth(1).jquery||e.each(["Width","Height"],function(i,s){function a(t,i,s,a){return e.each(n,function(){i-=parseFloat(e.css(t,"padding"+this))||0,s&&(i-=parseFloat(e.css(t,"border"+this+"Width"))||0),a&&(i-=parseFloat(e.css(t,"margin"+this))||0)}),i}var n="Width"===s?["Left","Right"]:["Top","Bottom"],r=s.toLowerCase(),o={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn["inner"+s]=function(i){return i===t?o["inner"+s].call(this):this.each(function(){e(this).css(r,a(this,i)+"px")})},e.fn["outer"+s]=function(t,i){return"number"!=typeof t?o["outer"+s].call(this,t):this.each(function(){e(this).css(r,a(this,t,!0,i)+"px")})}}),e.fn.addBack||(e.fn.addBack=function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}),e("").data("a-b","a").removeData("a-b").data("a-b")&&(e.fn.removeData=function(t){return function(i){return arguments.length?t.call(this,e.camelCase(i)):t.call(this)}}(e.fn.removeData)),e.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase()),e.support.selectstart="onselectstart"in document.createElement("div"),e.fn.extend({disableSelection:function(){return this.bind((e.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(e){e.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),e.extend(e.ui,{plugin:{add:function(t,i,s){var a,n=e.ui[t].prototype;for(a in s)n.plugins[a]=n.plugins[a]||[],n.plugins[a].push([i,s[a]])},call:function(e,t,i){var s,a=e.plugins[t];if(a&&e.element[0].parentNode&&11!==e.element[0].parentNode.nodeType)for(s=0;a.length>s;s++)e.options[a[s][0]]&&a[s][1].apply(e.element,i)}},hasScroll:function(t,i){if("hidden"===e(t).css("overflow"))return!1;var s=i&&"left"===i?"scrollLeft":"scrollTop",a=!1;return t[s]>0?!0:(t[s]=1,a=t[s]>0,t[s]=0,a)}})})(jQuery);(function(e,t){var i=0,s=Array.prototype.slice,n=e.cleanData;e.cleanData=function(t){for(var i,s=0;null!=(i=t[s]);s++)try{e(i).triggerHandler("remove")}catch(a){}n(t)},e.widget=function(i,s,n){var a,r,o,h,l={},u=i.split(".")[0];i=i.split(".")[1],a=u+"-"+i,n||(n=s,s=e.Widget),e.expr[":"][a.toLowerCase()]=function(t){return!!e.data(t,a)},e[u]=e[u]||{},r=e[u][i],o=e[u][i]=function(e,i){return this._createWidget?(arguments.length&&this._createWidget(e,i),t):new o(e,i)},e.extend(o,r,{version:n.version,_proto:e.extend({},n),_childConstructors:[]}),h=new s,h.options=e.widget.extend({},h.options),e.each(n,function(i,n){return e.isFunction(n)?(l[i]=function(){var e=function(){return s.prototype[i].apply(this,arguments)},t=function(e){return s.prototype[i].apply(this,e)};return function(){var i,s=this._super,a=this._superApply;return this._super=e,this._superApply=t,i=n.apply(this,arguments),this._super=s,this._superApply=a,i}}(),t):(l[i]=n,t)}),o.prototype=e.widget.extend(h,{widgetEventPrefix:r?h.widgetEventPrefix:i},l,{constructor:o,namespace:u,widgetName:i,widgetFullName:a}),r?(e.each(r._childConstructors,function(t,i){var s=i.prototype;e.widget(s.namespace+"."+s.widgetName,o,i._proto)}),delete r._childConstructors):s._childConstructors.push(o),e.widget.bridge(i,o)},e.widget.extend=function(i){for(var n,a,r=s.call(arguments,1),o=0,h=r.length;h>o;o++)for(n in r[o])a=r[o][n],r[o].hasOwnProperty(n)&&a!==t&&(i[n]=e.isPlainObject(a)?e.isPlainObject(i[n])?e.widget.extend({},i[n],a):e.widget.extend({},a):a);return i},e.widget.bridge=function(i,n){var a=n.prototype.widgetFullName||i;e.fn[i]=function(r){var o="string"==typeof r,h=s.call(arguments,1),l=this;return r=!o&&h.length?e.widget.extend.apply(null,[r].concat(h)):r,o?this.each(function(){var s,n=e.data(this,a);return n?e.isFunction(n[r])&&"_"!==r.charAt(0)?(s=n[r].apply(n,h),s!==n&&s!==t?(l=s&&s.jquery?l.pushStack(s.get()):s,!1):t):e.error("no such method '"+r+"' for "+i+" widget instance"):e.error("cannot call methods on "+i+" prior to initialization; "+"attempted to call method '"+r+"'")}):this.each(function(){var t=e.data(this,a);t?t.option(r||{})._init():e.data(this,a,new n(r,this))}),l}},e.Widget=function(){},e.Widget._childConstructors=[],e.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"
",options:{disabled:!1,create:null},_createWidget:function(t,s){s=e(s||this.defaultElement||this)[0],this.element=e(s),this.uuid=i++,this.eventNamespace="."+this.widgetName+this.uuid,this.options=e.widget.extend({},this.options,this._getCreateOptions(),t),this.bindings=e(),this.hoverable=e(),this.focusable=e(),s!==this&&(e.data(s,this.widgetFullName,this),this._on(!0,this.element,{remove:function(e){e.target===s&&this.destroy()}}),this.document=e(s.style?s.ownerDocument:s.document||s),this.window=e(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:e.noop,_getCreateEventData:e.noop,_create:e.noop,_init:e.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(e.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:e.noop,widget:function(){return this.element},option:function(i,s){var n,a,r,o=i;if(0===arguments.length)return e.widget.extend({},this.options);if("string"==typeof i)if(o={},n=i.split("."),i=n.shift(),n.length){for(a=o[i]=e.widget.extend({},this.options[i]),r=0;n.length-1>r;r++)a[n[r]]=a[n[r]]||{},a=a[n[r]];if(i=n.pop(),s===t)return a[i]===t?null:a[i];a[i]=s}else{if(s===t)return this.options[i]===t?null:this.options[i];o[i]=s}return this._setOptions(o),this},_setOptions:function(e){var t;for(t in e)this._setOption(t,e[t]);return this},_setOption:function(e,t){return this.options[e]=t,"disabled"===e&&(this.widget().toggleClass(this.widgetFullName+"-disabled ui-state-disabled",!!t).attr("aria-disabled",t),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")),this},enable:function(){return this._setOption("disabled",!1)},disable:function(){return this._setOption("disabled",!0)},_on:function(i,s,n){var a,r=this;"boolean"!=typeof i&&(n=s,s=i,i=!1),n?(s=a=e(s),this.bindings=this.bindings.add(s)):(n=s,s=this.element,a=this.widget()),e.each(n,function(n,o){function h(){return i||r.options.disabled!==!0&&!e(this).hasClass("ui-state-disabled")?("string"==typeof o?r[o]:o).apply(r,arguments):t}"string"!=typeof o&&(h.guid=o.guid=o.guid||h.guid||e.guid++);var l=n.match(/^(\w+)\s*(.*)$/),u=l[1]+r.eventNamespace,c=l[2];c?a.delegate(c,u,h):s.bind(u,h)})},_off:function(e,t){t=(t||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.unbind(t).undelegate(t)},_delay:function(e,t){function i(){return("string"==typeof e?s[e]:e).apply(s,arguments)}var s=this;return setTimeout(i,t||0)},_hoverable:function(t){this.hoverable=this.hoverable.add(t),this._on(t,{mouseenter:function(t){e(t.currentTarget).addClass("ui-state-hover")},mouseleave:function(t){e(t.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(t){this.focusable=this.focusable.add(t),this._on(t,{focusin:function(t){e(t.currentTarget).addClass("ui-state-focus")},focusout:function(t){e(t.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(t,i,s){var n,a,r=this.options[t];if(s=s||{},i=e.Event(i),i.type=(t===this.widgetEventPrefix?t:this.widgetEventPrefix+t).toLowerCase(),i.target=this.element[0],a=i.originalEvent)for(n in a)n in i||(i[n]=a[n]);return this.element.trigger(i,s),!(e.isFunction(r)&&r.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},e.each({show:"fadeIn",hide:"fadeOut"},function(t,i){e.Widget.prototype["_"+t]=function(s,n,a){"string"==typeof n&&(n={effect:n});var r,o=n?n===!0||"number"==typeof n?i:n.effect||i:t;n=n||{},"number"==typeof n&&(n={duration:n}),r=!e.isEmptyObject(n),n.complete=a,n.delay&&s.delay(n.delay),r&&e.effects&&e.effects.effect[o]?s[t](n):o!==t&&s[o]?s[o](n.duration,n.easing,a):s.queue(function(i){e(this)[t](),a&&a.call(s[0]),i()})}})})(jQuery);(function(t,e){function i(t,e,i){return[parseFloat(t[0])*(p.test(t[0])?e/100:1),parseFloat(t[1])*(p.test(t[1])?i/100:1)]}function s(e,i){return parseInt(t.css(e,i),10)||0}function n(e){var i=e[0];return 9===i.nodeType?{width:e.width(),height:e.height(),offset:{top:0,left:0}}:t.isWindow(i)?{width:e.width(),height:e.height(),offset:{top:e.scrollTop(),left:e.scrollLeft()}}:i.preventDefault?{width:0,height:0,offset:{top:i.pageY,left:i.pageX}}:{width:e.outerWidth(),height:e.outerHeight(),offset:e.offset()}}t.ui=t.ui||{};var a,o=Math.max,r=Math.abs,h=Math.round,l=/left|center|right/,c=/top|center|bottom/,u=/[\+\-]\d+(\.[\d]+)?%?/,d=/^\w+/,p=/%$/,f=t.fn.position;t.position={scrollbarWidth:function(){if(a!==e)return a;var i,s,n=t("
"),o=n.children()[0];return t("body").append(n),i=o.offsetWidth,n.css("overflow","scroll"),s=o.offsetWidth,i===s&&(s=n[0].clientWidth),n.remove(),a=i-s},getScrollInfo:function(e){var i=e.isWindow?"":e.element.css("overflow-x"),s=e.isWindow?"":e.element.css("overflow-y"),n="scroll"===i||"auto"===i&&e.widths?"left":i>0?"right":"center",vertical:0>a?"top":n>0?"bottom":"middle"};u>p&&p>r(i+s)&&(h.horizontal="center"),d>m&&m>r(n+a)&&(h.vertical="middle"),h.important=o(r(i),r(s))>o(r(n),r(a))?"horizontal":"vertical",e.using.call(this,t,h)}),c.offset(t.extend(C,{using:l}))})},t.ui.position={fit:{left:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollLeft:s.offset.left,a=s.width,r=t.left-e.collisionPosition.marginLeft,h=n-r,l=r+e.collisionWidth-a-n;e.collisionWidth>a?h>0&&0>=l?(i=t.left+h+e.collisionWidth-a-n,t.left+=h-i):t.left=l>0&&0>=h?n:h>l?n+a-e.collisionWidth:n:h>0?t.left+=h:l>0?t.left-=l:t.left=o(t.left-r,t.left)},top:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollTop:s.offset.top,a=e.within.height,r=t.top-e.collisionPosition.marginTop,h=n-r,l=r+e.collisionHeight-a-n;e.collisionHeight>a?h>0&&0>=l?(i=t.top+h+e.collisionHeight-a-n,t.top+=h-i):t.top=l>0&&0>=h?n:h>l?n+a-e.collisionHeight:n:h>0?t.top+=h:l>0?t.top-=l:t.top=o(t.top-r,t.top)}},flip:{left:function(t,e){var i,s,n=e.within,a=n.offset.left+n.scrollLeft,o=n.width,h=n.isWindow?n.scrollLeft:n.offset.left,l=t.left-e.collisionPosition.marginLeft,c=l-h,u=l+e.collisionWidth-o-h,d="left"===e.my[0]?-e.elemWidth:"right"===e.my[0]?e.elemWidth:0,p="left"===e.at[0]?e.targetWidth:"right"===e.at[0]?-e.targetWidth:0,f=-2*e.offset[0];0>c?(i=t.left+d+p+f+e.collisionWidth-o-a,(0>i||r(c)>i)&&(t.left+=d+p+f)):u>0&&(s=t.left-e.collisionPosition.marginLeft+d+p+f-h,(s>0||u>r(s))&&(t.left+=d+p+f))},top:function(t,e){var i,s,n=e.within,a=n.offset.top+n.scrollTop,o=n.height,h=n.isWindow?n.scrollTop:n.offset.top,l=t.top-e.collisionPosition.marginTop,c=l-h,u=l+e.collisionHeight-o-h,d="top"===e.my[1],p=d?-e.elemHeight:"bottom"===e.my[1]?e.elemHeight:0,f="top"===e.at[1]?e.targetHeight:"bottom"===e.at[1]?-e.targetHeight:0,m=-2*e.offset[1];0>c?(s=t.top+p+f+m+e.collisionHeight-o-a,t.top+p+f+m>c&&(0>s||r(c)>s)&&(t.top+=p+f+m)):u>0&&(i=t.top-e.collisionPosition.marginTop+p+f+m-h,t.top+p+f+m>u&&(i>0||u>r(i))&&(t.top+=p+f+m))}},flipfit:{left:function(){t.ui.position.flip.left.apply(this,arguments),t.ui.position.fit.left.apply(this,arguments)},top:function(){t.ui.position.flip.top.apply(this,arguments),t.ui.position.fit.top.apply(this,arguments)}}},function(){var e,i,s,n,a,o=document.getElementsByTagName("body")[0],r=document.createElement("div");e=document.createElement(o?"div":"body"),s={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},o&&t.extend(s,{position:"absolute",left:"-1000px",top:"-1000px"});for(a in s)e.style[a]=s[a];e.appendChild(r),i=o||document.documentElement,i.insertBefore(e,i.firstChild),r.style.cssText="position: absolute; left: 10.7432222px;",n=t(r).offset().left,t.support.offsetFractions=n>10&&11>n,e.innerHTML="",i.removeChild(e)}()})(jQuery);(function(e){var t=0;e.widget("ui.autocomplete",{version:"1.10.2",defaultElement:"",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},pending:0,_create:function(){var t,i,s,n=this.element[0].nodeName.toLowerCase(),a="textarea"===n,o="input"===n;this.isMultiLine=a?!0:o?!1:this.element.prop("isContentEditable"),this.valueMethod=this.element[a||o?"val":"text"],this.isNewMenu=!0,this.element.addClass("ui-autocomplete-input").attr("autocomplete","off"),this._on(this.element,{keydown:function(n){if(this.element.prop("readOnly"))return t=!0,s=!0,i=!0,undefined;t=!1,s=!1,i=!1;var a=e.ui.keyCode;switch(n.keyCode){case a.PAGE_UP:t=!0,this._move("previousPage",n);break;case a.PAGE_DOWN:t=!0,this._move("nextPage",n);break;case a.UP:t=!0,this._keyEvent("previous",n);break;case a.DOWN:t=!0,this._keyEvent("next",n);break;case a.ENTER:case a.NUMPAD_ENTER:this.menu.active&&(t=!0,n.preventDefault(),this.menu.select(n));break;case a.TAB:this.menu.active&&this.menu.select(n);break;case a.ESCAPE:this.menu.element.is(":visible")&&(this._value(this.term),this.close(n),n.preventDefault());break;default:i=!0,this._searchTimeout(n)}},keypress:function(s){if(t)return t=!1,s.preventDefault(),undefined;if(!i){var n=e.ui.keyCode;switch(s.keyCode){case n.PAGE_UP:this._move("previousPage",s);break;case n.PAGE_DOWN:this._move("nextPage",s);break;case n.UP:this._keyEvent("previous",s);break;case n.DOWN:this._keyEvent("next",s)}}},input:function(e){return s?(s=!1,e.preventDefault(),undefined):(this._searchTimeout(e),undefined)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(e){return this.cancelBlur?(delete this.cancelBlur,undefined):(clearTimeout(this.searching),this.close(e),this._change(e),undefined)}}),this._initSource(),this.menu=e("