diff options
author | Bharat Mediratta <bharat@menalto.com> | 2009-05-18 00:14:07 +0000 |
---|---|---|
committer | Bharat Mediratta <bharat@menalto.com> | 2009-05-18 00:14:07 +0000 |
commit | fd0c0a608a5de2b26c29d3c44a7929e5a3b2c042 (patch) | |
tree | 387f2628cae3580b7f09682de6e908d002c5fbb0 | |
parent | 5a6aef9c23930b1de609c9914297e0f97bc49a11 (diff) |
Updated kohana and modules/unit_test to upstream r4356
25 files changed, 430 insertions, 372 deletions
diff --git a/kohana/config/sql_types.php b/kohana/config/sql_types.php index 69a0a631..a4a44bda 100644 --- a/kohana/config/sql_types.php +++ b/kohana/config/sql_types.php @@ -32,7 +32,8 @@ $config = array ); // DOUBLE -$config['double'] = $config['double unsigned'] = $config['decimal'] = $config['real'] = $config['numeric'] = $config['float']; +$config['double'] = $config['double precision'] = $config['decimal'] = $config['real'] = $config['numeric'] = $config['float']; +$config['double unsigned'] = $config['float unsigned']; // BIT $config['bit'] = $config['boolean']; diff --git a/kohana/core/Benchmark.php b/kohana/core/Benchmark.php index 09909126..3bb6d04f 100644 --- a/kohana/core/Benchmark.php +++ b/kohana/core/Benchmark.php @@ -109,7 +109,7 @@ final class Benchmark { * * @return integer */ - private function memory_usage() + private static function memory_usage() { static $func; diff --git a/kohana/core/Kohana.php b/kohana/core/Kohana.php index 0ee6f631..ed87902e 100644 --- a/kohana/core/Kohana.php +++ b/kohana/core/Kohana.php @@ -209,12 +209,6 @@ final class Kohana { { Benchmark::start(SYSTEM_BENCHMARK.'_controller_setup'); - if (Router::$method[0] === '_') - { - // Do not allow access to hidden methods - Event::run('system.404'); - } - // Include the Controller file require Router::$controller_path; @@ -249,6 +243,13 @@ final class Kohana { // Load the controller method $method = $class->getMethod(Router::$method); + // Method exists + if (Router::$method[0] === '_') + { + // Do not allow access to hidden methods + Event::run('system.404'); + } + if ($method->isProtected() or $method->isPrivate()) { // Do not attempt to invoke protected methods @@ -806,126 +807,140 @@ final class Kohana { */ public static function exception_handler($exception, $message = NULL, $file = NULL, $line = NULL) { - // PHP errors have 5 args, always - $PHP_ERROR = (func_num_args() === 5); - - // Test to see if errors should be displayed - if ($PHP_ERROR AND (error_reporting() & $exception) === 0) - return; - - // This is useful for hooks to determine if a page has an error - self::$has_error = TRUE; - - // Error handling will use exactly 5 args, every time - if ($PHP_ERROR) - { - $code = $exception; - $type = 'PHP Error'; - $template = 'kohana_error_page'; - } - else - { - $code = $exception->getCode(); - $type = get_class($exception); - $message = $exception->getMessage(); - $file = $exception->getFile(); - $line = $exception->getLine(); - $template = ($exception instanceof Kohana_Exception) ? $exception->getTemplate() : 'kohana_error_page'; - } - - if (is_numeric($code)) - { - $codes = self::lang('errors'); - - if ( ! empty($codes[$code])) + try + { + // PHP errors have 5 args, always + $PHP_ERROR = (func_num_args() === 5); + + // Test to see if errors should be displayed + if ($PHP_ERROR AND (error_reporting() & $exception) === 0) + return; + + // This is useful for hooks to determine if a page has an error + self::$has_error = TRUE; + + // Error handling will use exactly 5 args, every time + if ($PHP_ERROR) + { + $code = $exception; + $type = 'PHP Error'; + $template = 'kohana_error_page'; + } + else { - list($level, $error, $description) = $codes[$code]; + $code = $exception->getCode(); + $type = get_class($exception); + $message = $exception->getMessage(); + $file = $exception->getFile(); + $line = $exception->getLine(); + $template = ($exception instanceof Kohana_Exception) ? $exception->getTemplate() : 'kohana_error_page'; + } + + if (is_numeric($code)) + { + $codes = self::lang('errors'); + + if ( ! empty($codes[$code])) + { + list($level, $error, $description) = $codes[$code]; + } + else + { + $level = 1; + $error = $PHP_ERROR ? 'Unknown Error' : get_class($exception); + $description = ''; + } } else { - $level = 1; - $error = $PHP_ERROR ? 'Unknown Error' : get_class($exception); + // Custom error message, this will never be logged + $level = 5; + $error = $code; $description = ''; } - } - else - { - // Custom error message, this will never be logged - $level = 5; - $error = $code; - $description = ''; - } - - // Remove the DOCROOT from the path, as a security precaution - $file = str_replace('\\', '/', realpath($file)); - $file = preg_replace('|^'.preg_quote(DOCROOT).'|', '', $file); - - if ($level <= self::$configuration['core']['log_threshold']) - { - // Log the error - self::log('error', self::lang('core.uncaught_exception', $type, $message, $file, $line)); - } - - if ($PHP_ERROR) - { - $description = self::lang('errors.'.E_RECOVERABLE_ERROR); - $description = is_array($description) ? $description[2] : ''; - - if ( ! headers_sent()) + + // Remove the DOCROOT from the path, as a security precaution + $file = str_replace('\\', '/', realpath($file)); + $file = preg_replace('|^'.preg_quote(DOCROOT).'|', '', $file); + + if ($level <= self::$configuration['core']['log_threshold']) { - // Send the 500 header - header('HTTP/1.1 500 Internal Server Error'); + // Log the error + self::log('error', self::lang('core.uncaught_exception', $type, $message, $file, $line)); } - } - else - { - if (method_exists($exception, 'sendHeaders') AND ! headers_sent()) + + if ($PHP_ERROR) { - // Send the headers if they have not already been sent - $exception->sendHeaders(); + $description = self::lang('errors.'.E_RECOVERABLE_ERROR); + $description = is_array($description) ? $description[2] : ''; + + if ( ! headers_sent()) + { + // Send the 500 header + header('HTTP/1.1 500 Internal Server Error'); + } } - } - - while (ob_get_level() > self::$buffer_level) - { - // Close open buffers - ob_end_clean(); - } - - // Test if display_errors is on - if (self::$configuration['core']['display_errors'] === TRUE) - { - if ( ! IN_PRODUCTION AND $line != FALSE) + else { - // Remove the first entry of debug_backtrace(), it is the exception_handler call - $trace = $PHP_ERROR ? array_slice(debug_backtrace(), 1) : $exception->getTrace(); - - // Beautify backtrace - $trace = self::backtrace($trace); + if (method_exists($exception, 'sendHeaders') AND ! headers_sent()) + { + // Send the headers if they have not already been sent + $exception->sendHeaders(); + } } - - // Load the error - require self::find_file('views', empty($template) ? 'kohana_error_page' : $template); - } - else - { - // Get the i18n messages - $error = self::lang('core.generic_error'); - $message = self::lang('core.errors_disabled', url::site(), url::site(Router::$current_uri)); - - // Load the errors_disabled view - require self::find_file('views', 'kohana_error_disabled'); + + while (ob_get_level() > self::$buffer_level) + { + // Close open buffers + ob_end_clean(); + } + + // Test if display_errors is on + if (self::$configuration['core']['display_errors'] === TRUE) + { + if ( ! IN_PRODUCTION AND $line != FALSE) + { + // Remove the first entry of debug_backtrace(), it is the exception_handler call + $trace = $PHP_ERROR ? array_slice(debug_backtrace(), 1) : $exception->getTrace(); + + // Beautify backtrace + $trace = self::backtrace($trace); + } + + // Load the error + require self::find_file('views', empty($template) ? 'kohana_error_page' : $template); + } + else + { + // Get the i18n messages + $error = self::lang('core.generic_error'); + $message = self::lang('core.errors_disabled', url::site(), url::site(Router::$current_uri)); + + // Load the errors_disabled view + require self::find_file('views', 'kohana_error_disabled'); + } + + if ( ! Event::has_run('system.shutdown')) + { + // Run the shutdown even to ensure a clean exit + Event::run('system.shutdown'); + } + + // Turn off error reporting + error_reporting(0); + exit; } - - if ( ! Event::has_run('system.shutdown')) + catch (Exception $e) { - // Run the shutdown even to ensure a clean exit - Event::run('system.shutdown'); + if (IN_PRODUCTION) + { + die('Fatal Error'); + } + else + { + die('Fatal Error: '.$e->getMessage().' File: '.$e->getFile().' Line: '.$e->getLine()); + } } - - // Turn off error reporting - error_reporting(0); - exit; } /** @@ -1027,7 +1042,7 @@ final class Kohana { * * @throws Kohana_Exception if file is required and not found * @param string directory to search in - * @param string filename to look for (including extension only if 4th parameter is TRUE) + * @param string filename to look for (without extension) * @param boolean file required * @param string file extension * @return array if the type is config, i18n or l10n diff --git a/kohana/helpers/arr.php b/kohana/helpers/arr.php index 87f2be1f..9f0dc097 100644 --- a/kohana/helpers/arr.php +++ b/kohana/helpers/arr.php @@ -152,52 +152,43 @@ class arr_Core { } /** - * Binary search algorithm. - * - * @param mixed the value to search for - * @param array an array of values to search in - * @param boolean return false, or the nearest value - * @param mixed sort the array before searching it - * @return integer + * @param mixed $needle the value to search for + * @param array $haystack an array of values to search in + * @param boolean $sort sort the array now + * @return integer|FALSE the index of the match or FALSE when not found */ - public static function binary_search($needle, $haystack, $nearest = FALSE, $sort = FALSE) + public static function binary_search($needle, $haystack, $sort = FALSE) { - if ($sort === TRUE) + if ($sort) { sort($haystack); } - $high = count($haystack); + $high = count($haystack) - 1; $low = 0; - while ($high - $low > 1) + while ($low <= $high) { - $probe = ($high + $low) / 2; - if ($haystack[$probe] < $needle) + $mid = ($low + $high) >> 1; + + if ($haystack[$mid] < $needle) { - $low = $probe; + $low = $mid + 1; + } + elseif ($haystack[$mid] > $needle) + { + $high = $mid - 1; } else { - $high = $probe; + return $mid; } } - if ($high == count($haystack) OR $haystack[$high] != $needle) - { - if ($nearest === FALSE) - return FALSE; - - // return the nearest value - $high_distance = $haystack[ceil($low)] - $needle; - $low_distance = $needle - $haystack[floor($low)]; - - return ($high_distance >= $low_distance) ? $haystack[ceil($low)] : $haystack[floor($low)]; - } - - return $high; + return FALSE; } + /** * Emulates array_merge_recursive, but appends numeric keys and replaces * associative keys, instead of appending all keys. @@ -318,4 +309,4 @@ class arr_Core { return $object; } -} // End arr
\ No newline at end of file +} // End arr diff --git a/kohana/helpers/date.php b/kohana/helpers/date.php index b926e626..1a5bbacb 100644 --- a/kohana/helpers/date.php +++ b/kohana/helpers/date.php @@ -374,7 +374,7 @@ class date_Core { $span = array(); foreach ($difference as $name => $amount) { - if ($name !== $last AND $amount === 0) + if ($amount === 0) { // Skip empty amounts continue; @@ -384,6 +384,12 @@ class date_Core { $span[] = ($name === $last ? ' and ' : ', ').$amount.' '.($amount === 1 ? inflector::singular($name) : $name); } + // If the difference is less than 60 seconds, remove the preceding and. + if (count($span) === 1) + { + $span[0] = ltrim($span[0], 'and '); + } + // Replace difference by making the span into a string $difference = trim(implode('', $span), ','); } diff --git a/kohana/helpers/expires.php b/kohana/helpers/expires.php index 454cf12a..8d8ef0f8 100644 --- a/kohana/helpers/expires.php +++ b/kohana/helpers/expires.php @@ -88,7 +88,8 @@ class expires_Core { { foreach (headers_list() as $header) { - if (stripos($header, 'Last-Modified:') === 0 OR stripos($header, 'Expires:') === 0) + if ((session_cache_limiter() == '' AND stripos($header, 'Last-Modified:') === 0) + OR stripos($header, 'Expires:') === 0) { return FALSE; } diff --git a/kohana/helpers/feed.php b/kohana/helpers/feed.php index c1e0b81f..a84ec512 100644 --- a/kohana/helpers/feed.php +++ b/kohana/helpers/feed.php @@ -65,13 +65,15 @@ class feed_Core { * * @param array feed information * @param array items to add to the feed + * @param string define which format to use + * @param string define which encoding to use * @return string */ - public static function create($info, $items, $format = 'rss2') + public static function create($info, $items, $format = 'rss2', $encoding = 'UTF-8') { $info += array('title' => 'Generated Feed', 'link' => '', 'generator' => 'KohanaPHP'); - $feed = '<?xml version="1.0"?><rss version="2.0"><channel></channel></rss>'; + $feed = '<?xml version="1.0" encoding="'.$encoding.'"?><rss version="2.0"><channel></channel></rss>'; $feed = simplexml_load_string($feed); foreach ($info as $name => $value) diff --git a/kohana/helpers/form.php b/kohana/helpers/form.php index 0eaec0dc..70b98167 100644 --- a/kohana/helpers/form.php +++ b/kohana/helpers/form.php @@ -233,7 +233,7 @@ class form_Core { * * @param string|array input name or an array of HTML attributes * @param array select options, when using a name - * @param string option key that should be selected by default + * @param string|array option key(s) that should be selected by default * @param string a string to be attached to the end of the attributes * @return string */ diff --git a/kohana/helpers/request.php b/kohana/helpers/request.php index 625f9226..091a9d68 100644 --- a/kohana/helpers/request.php +++ b/kohana/helpers/request.php @@ -33,7 +33,7 @@ class request_Core { if (strpos($ref, url::base(FALSE)) === 0) { // Remove the base URL from the referrer - $ref = substr($ref, strlen(url::base(TRUE))); + $ref = substr($ref, strlen(url::base(FALSE))); } } diff --git a/kohana/helpers/valid.php b/kohana/helpers/valid.php index 88cca584..25f90f68 100644 --- a/kohana/helpers/valid.php +++ b/kohana/helpers/valid.php @@ -277,7 +277,7 @@ class valid_Core { { // Use localeconv to set the decimal_point value: Usually a comma or period. $locale = localeconv(); - return (preg_match('/^-?[0-9'.$locale['decimal_point'].']++$/D', (string) $str)); + return (bool) preg_match('/^-?[0-9'.$locale['decimal_point'].']++$/D', (string) $str); } /** diff --git a/kohana/libraries/Cache.php b/kohana/libraries/Cache.php index 3fb5a31c..3dcf0312 100644 --- a/kohana/libraries/Cache.php +++ b/kohana/libraries/Cache.php @@ -135,13 +135,13 @@ class Cache_Core { * Set a cache item by id. Tags may also be added and a custom lifetime * can be set. Non-string data is automatically serialized. * - * @param string unique cache id - * @param mixed data to cache - * @param array tags for this item - * @param integer number of seconds until the cache expires + * @param string unique cache id + * @param mixed data to cache + * @param array|string tags for this item + * @param integer number of seconds until the cache expires * @return boolean */ - function set($id, $data, array $tags = NULL, $lifetime = NULL) + function set($id, $data, $tags = NULL, $lifetime = NULL) { if (is_resource($data)) throw new Kohana_Exception('cache.resources'); @@ -155,7 +155,7 @@ class Cache_Core { $lifetime = $this->config['lifetime']; } - return $this->driver->set($id, $data, $tags, $lifetime); + return $this->driver->set($id, $data, (array) $tags, $lifetime); } /** diff --git a/kohana/libraries/Database.php b/kohana/libraries/Database.php index 49241675..59e47621 100644 --- a/kohana/libraries/Database.php +++ b/kohana/libraries/Database.php @@ -341,10 +341,10 @@ class Database_Core { foreach ($sql as $val) { - if (($val = trim($val)) === '') continue; - if (is_string($val)) { + if (($val = trim($val)) === '') continue; + // TODO: Temporary solution, this should be moved to database driver (AS is checked for twice) if (stripos($val, ' AS ') !== FALSE) { @@ -1187,7 +1187,7 @@ class Database_Core { { $this->link or $this->connect(); - return $this->driver->list_tables($this); + return $this->driver->list_tables(); } /** diff --git a/kohana/libraries/ORM.php b/kohana/libraries/ORM.php index 4cbec50a..ae214649 100644 --- a/kohana/libraries/ORM.php +++ b/kohana/libraries/ORM.php @@ -319,8 +319,9 @@ class ORM_Core { elseif (in_array($column, $this->has_many)) { // one<>many relationship - return $this->related[$column] = ORM::factory(inflector::singular($column)) - ->where($this->foreign_key($column, $column), $this->object[$this->primary_key]) + $model = ORM::factory(inflector::singular($column)); + return $this->related[$column] = $model + ->where($this->foreign_key($column, $model->table_name), $this->object[$this->primary_key]) ->find_all(); } elseif (in_array($column, $this->has_and_belongs_to_many)) @@ -638,10 +639,10 @@ class ORM_Core { */ public function validate(Validation $array, $save = FALSE) { + $safe_array = $array->safe_array(); + if ( ! $array->submitted()) { - $safe_array = $array->safe_array(); - foreach ($safe_array as $key => $value) { // Get the value from this object @@ -661,12 +662,16 @@ class ORM_Core { // Validate the array if ($status = $array->validate()) { - $safe_array = $array->safe_array(); + // Grab only set fields (excludes missing data, unlike safe_array) + $fields = $array->as_array(); - foreach ($safe_array as $key => $value) + foreach ($fields as $key => $value) { - // Set new data - $this->$key = $value; + if (isset($safe_array[$key])) + { + // Set new data, ignoring any missing fields or fields without rules + $this->$key = $value; + } } if ($save === TRUE OR is_string($save)) @@ -840,7 +845,7 @@ class ORM_Core { elseif (is_null($ids)) { // Delete all records - $this->db->where(TRUE); + $this->db->where('1=1'); } else { @@ -902,7 +907,7 @@ class ORM_Core { else { // Load table columns - ORM::$column_cache[$this->object_name] = $this->table_columns = $this->db->list_fields($this->table_name, TRUE); + ORM::$column_cache[$this->object_name] = $this->table_columns = $this->list_fields(); } } @@ -1021,11 +1026,16 @@ class ORM_Core { /** * Proxy method to Database list_fields. * - * @param string table name + * @param string table name or NULL to use this table * @return array */ - public function list_fields($table) + public function list_fields($table = NULL) { + if ($table === NULL) + { + $table = $this->table_name; + } + // Proxy to database return $this->db->list_fields($table); } diff --git a/kohana/libraries/Router.php b/kohana/libraries/Router.php index 172c5f92..a47a9993 100644 --- a/kohana/libraries/Router.php +++ b/kohana/libraries/Router.php @@ -213,15 +213,12 @@ class Router_Core { elseif (isset($_SERVER['PHP_SELF']) AND $_SERVER['PHP_SELF']) { Router::$current_uri = $_SERVER['PHP_SELF']; - } - - // The front controller directory and filename - $fc = substr(realpath($_SERVER['SCRIPT_FILENAME']), strlen(DOCROOT)); - if (($strpos_fc = strpos(Router::$current_uri, $fc)) !== FALSE) - { - // Remove the front controller from the current uri - Router::$current_uri = substr(Router::$current_uri, $strpos_fc + strlen($fc)); + if (($strpos_fc = strpos(Router::$current_uri, KOHANA)) !== FALSE) + { + // Remove the front controller from the current uri + Router::$current_uri = substr(Router::$current_uri, $strpos_fc + strlen(KOHANA)); + } } // Remove slashes from the start and end of the URI diff --git a/kohana/libraries/drivers/Database.php b/kohana/libraries/drivers/Database.php index 96562240..f5adf924 100644 --- a/kohana/libraries/drivers/Database.php +++ b/kohana/libraries/drivers/Database.php @@ -11,7 +11,7 @@ */ abstract class Database_Driver { - static $query_cache; + protected $query_cache; /** * Connect to our database. @@ -124,7 +124,7 @@ abstract class Database_Driver { } else { - if ( ! $this->has_operator($key)) + if ( ! $this->has_operator($key) AND ! empty($key)) { $key = $this->escape_column($key).' ='; } @@ -290,7 +290,7 @@ abstract class Database_Driver { */ public function has_operator($str) { - return (bool) preg_match('/[<>!=]|\sIS(?:\s+NOT\s+)?\b/i', trim($str)); + return (bool) preg_match('/[<>!=]|\sIS(?:\s+NOT\s+)?\b|BETWEEN/i', trim($str)); } /** @@ -337,7 +337,7 @@ abstract class Database_Driver { * * @return array */ - abstract public function list_tables(Database $db); + abstract public function list_tables(); /** * Lists all fields in a table. @@ -431,11 +431,11 @@ abstract class Database_Driver { { if (empty($sql)) { - self::$query_cache = array(); + $this->query_cache = array(); } else { - unset(self::$query_cache[$this->query_hash($sql)]); + unset($this->query_cache[$this->query_hash($sql)]); } Kohana::log('debug', 'Database cache cleared: '.get_class($this)); @@ -633,4 +633,4 @@ abstract class Database_Result implements ArrayAccess, Iterator, Countable { return $this->offsetExists($this->current_row); } -} // End Database Result Interface
\ No newline at end of file +} // End Database Result Interface diff --git a/kohana/libraries/drivers/Database/Mssql.php b/kohana/libraries/drivers/Database/Mssql.php index 3e89faba..6947679a 100644 --- a/kohana/libraries/drivers/Database/Mssql.php +++ b/kohana/libraries/drivers/Database/Mssql.php @@ -89,19 +89,19 @@ class Database_Mssql_Driver extends Database_Driver { $hash = $this->query_hash($sql); - if ( ! isset(self::$query_cache[$hash])) + if ( ! isset($this->query_cache[$hash])) { // Set the cached object - self::$query_cache[$hash] = new Mssql_Result(mssql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql); + $this->query_cache[$hash] = new Mssql_Result(mssql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql); } else { // Rewind cached result - self::$query_cache[$hash]->rewind(); + $this->query_cache[$hash]->rewind(); } // Return the cached query - return self::$query_cache[$hash]; + return $this->query_cache[$hash]; } return new Mssql_Result(mssql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql); @@ -128,9 +128,22 @@ class Database_Mssql_Driver extends Database_Driver if (!$this->db_config['escape']) return $column; - if (strtolower($column) == 'count(*)' OR $column == '*') + if ($column == '*') return $column; + // This matches any functions we support to SELECT. + if ( preg_match('/(avg|count|sum|max|min)\(\s*(.*)\s*\)(\s*as\s*(.+)?)?/i', $column, $matches)) + { + if ( count($matches) == 3) + { + return $matches[1].'('.$this->escape_column($matches[2]).')'; + } + else if ( count($matches) == 5) + { + return $matches[1].'('.$this->escape_column($matches[2]).') AS '.$this->escape_column($matches[2]); + } + } + // This matches any modifiers we support to SELECT. if ( ! preg_match('/\b(?:rand|all|distinct(?:row)?|high_priority|sql_(?:small_result|b(?:ig_result|uffer_result)|no_cache|ca(?:che|lc_found_rows)))\s/i', $column)) { @@ -251,7 +264,7 @@ class Database_Mssql_Driver extends Database_Driver return preg_replace($characters, $replace, $str); } - public function list_tables(Database $db) + public function list_tables() { $sql = 'SHOW TABLES FROM ['.$this->db_config['connection']['database'].']'; $result = $this->query($sql)->result(FALSE, MSSQL_ASSOC); @@ -272,36 +285,22 @@ class Database_Mssql_Driver extends Database_Driver public function list_fields($table) { - static $tables; + $result = array(); - if (empty($tables[$table])) + foreach ($this->field_data($table) as $row) { - foreach ($this->field_data($table) as $row) - { - // Make an associative array - $tables[$table][$row->Field] = $this->sql_type($row->Type); - } + // Make an associative array + $result[$row->Field] = $this->sql_type($row->Type); } - return $tables[$table]; + return $result; } public function field_data($table) { - $columns = array(); - - if ($query = MSSQL_query('SHOW COLUMNS FROM '.$this->escape_table($table), $this->link)) - { - if (MSSQL_num_rows($query) > 0) - { - while ($row = MSSQL_fetch_object($query)) - { - $columns[] = $row; - } - } - } + $query = $this->query('SHOW COLUMNS FROM '.$this->escape_table($table), $this->link); - return $columns; + return $query->result_array(TRUE); } } @@ -460,4 +459,4 @@ class Mssql_Result extends Database_Result { return mssql_data_seek($this->result, $offset); } -} // End mssql_Result Class
\ No newline at end of file +} // End mssql_Result Class diff --git a/kohana/libraries/drivers/Database/Mysql.php b/kohana/libraries/drivers/Database/Mysql.php index 9315ed1f..978de459 100644 --- a/kohana/libraries/drivers/Database/Mysql.php +++ b/kohana/libraries/drivers/Database/Mysql.php @@ -22,12 +22,6 @@ class Database_Mysql_Driver extends Database_Driver { protected $db_config; /** - * Performance caches. - */ - private $tables_cache; - private $fields_cache; - - /** * Sets the config for the class. * * @param array database configuration @@ -35,8 +29,6 @@ class Database_Mysql_Driver extends Database_Driver { public function __construct($config) { $this->db_config = $config; - $this->tables_cache = array(); - $this->fields_cache = array(); Kohana::log('debug', 'MySQL Database Driver Initialized'); } @@ -85,23 +77,23 @@ class Database_Mysql_Driver extends Database_Driver { public function query($sql) { // Only cache if it's turned on, and only cache if it's not a write statement - if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|REPLACE|SET)\b#i', $sql)) + if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|REPLACE|SET|DELETE|TRUNCATE)\b#i', $sql)) { $hash = $this->query_hash($sql); - if ( ! isset(self::$query_cache[$hash])) + if ( ! isset($this->query_cache[$hash])) { // Set the cached object - self::$query_cache[$hash] = new Mysql_Result(mysql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql); + $this->query_cache[$hash] = new Mysql_Result(mysql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql); } else { // Rewind cached result - self::$query_cache[$hash]->rewind(); + $this->query_cache[$hash]->rewind(); } // Return the cached query - return self::$query_cache[$hash]; + return $this->query_cache[$hash]; } return new Mysql_Result(mysql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql); @@ -136,9 +128,22 @@ class Database_Mysql_Driver extends Database_Driver { if (!$this->db_config['escape']) return $column; - if (strtolower($column) == 'count(*)' OR $column == '*') + if ($column == '*') return $column; + // This matches any functions we support to SELECT. + if ( preg_match('/(avg|count|sum|max|min)\(\s*(.*)\s*\)(\s*as\s*(.+)?)?/i', $column, $matches)) + { + if ( count($matches) == 3) + { + return $matches[1].'('.$this->escape_column($matches[2]).')'; + } + else if ( count($matches) == 5) + { + return $matches[1].'('.$this->escape_column($matches[2]).') AS '.$this->escape_column($matches[2]); + } + } + // This matches any modifiers we support to SELECT. if ( ! preg_match('/\b(?:rand|all|distinct(?:row)?|high_priority|sql_(?:small_result|b(?:ig_result|uffer_result)|no_cache|ca(?:che|lc_found_rows)))\s/i', $column)) { @@ -217,8 +222,8 @@ class Database_Mysql_Driver extends Database_Driver { { $froms[] = $this->escape_column($from); } - $sql .= "\nFROM "; - $sql .= implode(', ', $froms); + $sql .= "\nFROM ("; + $sql .= implode(', ', $froms).")"; } if (count($database['join']) > 0) @@ -273,11 +278,11 @@ class Database_Mysql_Driver extends Database_Driver { return mysql_real_escape_string($str, $this->link); } - public function list_tables(Database $db) + public function list_tables() { - $tables =& $this->tables_cache; + $tables = array(); - if (empty($tables) AND $query = $db->query('SHOW TABLES FROM '.$this->escape_table($this->db_config['connection']['database']))) + if ($query = $this->query('SHOW TABLES FROM '.$this->escape_table($this->db_config['connection']['database']))) { foreach ($query->result(FALSE) as $row) { @@ -295,63 +300,37 @@ class Database_Mysql_Driver extends Database_Driver { public function list_fields($table) { - $tables =& $this->fields_cache; + $result = NULL; - if (empty($tables[$table])) + foreach ($this->field_data($table) as $row) { - foreach ($this->field_data($table) as $row) + // Make an associative array + $result[$row->Field] = $this->sql_type($row->Type); + + if ($row->Key === 'PRI' AND $row->Extra === 'auto_increment') + { + // For sequenced (AUTO_INCREMENT) tables + $result[$row->Field]['sequenced'] = TRUE; + } + + if ($row->Null === 'YES') { - // Make an associative array - $tables[$table][$row->Field] = $this->sql_type($row->Type); - - if ($row->Key === 'PRI' AND $row->Extra === 'auto_increment') - { - // For sequenced (AUTO_INCREMENT) tables - $tables[$table][$row->Field]['sequenced'] = TRUE; - } - - if ($row->Null === 'YES') - { - // Set NULL status - $tables[$table][$row->Field]['null'] = TRUE; - } + // Set NULL status + $result[$row->Field]['null'] = TRUE; } } - if (!isset($tables[$table])) + if (!isset($result)) throw new Kohana_Database_Exception('database.table_not_found', $table); - return $tables[$table]; + return $result; } public function field_data($table) { - $columns = array(); - - if ($query = mysql_query('SHOW COLUMNS FROM '.$this->escape_table($table), $this->link)) - { - if (mysql_num_rows($query)) - { - while ($row = mysql_fetch_object($query)) - { - $columns[] = $row; - } - } - } + $result = $this->query('SHOW COLUMNS FROM '.$this->escape_table($table)); - return $columns; - } - - /** - * Clears the internal query cache. - * - * @param string SQL query - */ - public function clear_cache($sql = NULL) - { - parent::clear_cache($sql); - $this->tables_cache = array(); - $this->fields_cache = array(); + return $result->result_array(TRUE); } } // End Database_Mysql_Driver Class @@ -514,4 +493,4 @@ class Mysql_Result extends Database_Result { } } -} // End Mysql_Result Class
\ No newline at end of file +} // End Mysql_Result Class diff --git a/kohana/libraries/drivers/Database/Mysqli.php b/kohana/libraries/drivers/Database/Mysqli.php index 13a81281..f15e4283 100644 --- a/kohana/libraries/drivers/Database/Mysqli.php +++ b/kohana/libraries/drivers/Database/Mysqli.php @@ -68,23 +68,23 @@ class Database_Mysqli_Driver extends Database_Mysql_Driver { public function query($sql) { // Only cache if it's turned on, and only cache if it's not a write statement - if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|REPLACE|SET)\b#i', $sql)) + if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|REPLACE|SET|DELETE|TRUNCATE)\b#i', $sql)) { $hash = $this->query_hash($sql); - if ( ! isset(self::$query_cache[$hash])) + if ( ! isset($this->query_cache[$hash])) { // Set the cached object - self::$query_cache[$hash] = new Kohana_Mysqli_Result($this->link, $this->db_config['object'], $sql); + $this->query_cache[$hash] = new Kohana_Mysqli_Result($this->link, $this->db_config['object'], $sql); } else { // Rewind cached result - self::$query_cache[$hash]->rewind(); + $this->query_cache[$hash]->rewind(); } // Return the cached query - return self::$query_cache[$hash]; + return $this->query_cache[$hash]; } return new Kohana_Mysqli_Result($this->link, $this->db_config['object'], $sql); @@ -111,22 +111,6 @@ class Database_Mysqli_Driver extends Database_Mysql_Driver { return $this->link->error; } - public function field_data($table) - { - $columns = array(); - $query = $this->link->query('SHOW COLUMNS FROM '.$this->escape_table($table)); - - if (is_object($query)) - { - while ($row = $query->fetch_object()) - { - $columns[] = $row; - } - } - - return $columns; - } - } // End Database_Mysqli_Driver Class /** @@ -299,12 +283,15 @@ class Kohana_Mysqli_Result extends Database_Result { public function seek($offset) { - if ( ! $this->offsetExists($offset)) - return FALSE; + if ($this->offsetExists($offset) AND $this->result->data_seek($offset)) + { + // Set the current row to the offset + $this->current_row = $offset; - $this->result->data_seek($offset); + return TRUE; + } - return TRUE; + return FALSE; } public function offsetGet($offset) @@ -368,4 +355,4 @@ class Kohana_Mysqli_Statement { $this->stmt->execute(); return $this->stmt; } -}
\ No newline at end of file +} diff --git a/kohana/libraries/drivers/Database/Pdosqlite.php b/kohana/libraries/drivers/Database/Pdosqlite.php index 5a512877..c2d1bb21 100644 --- a/kohana/libraries/drivers/Database/Pdosqlite.php +++ b/kohana/libraries/drivers/Database/Pdosqlite.php @@ -43,7 +43,7 @@ class Database_Pdosqlite_Driver extends Database_Driver { array(PDO::ATTR_PERSISTENT => $this->db_config['persistent'])); $this->link->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL); - $this->link->query('PRAGMA count_changes=1;'); + //$this->link->query('PRAGMA count_changes=1;'); if ($charset = $this->db_config['character_set']) { @@ -63,7 +63,7 @@ class Database_Pdosqlite_Driver extends Database_Driver { public function query($sql) { - try + try { $sth = $this->link->prepare($sql); } @@ -92,9 +92,22 @@ class Database_Pdosqlite_Driver extends Database_Driver { if ( ! $this->db_config['escape']) return $column; - if (strtolower($column) == 'count(*)' OR $column == '*') + if ($column == '*') return $column; + // This matches any functions we support to SELECT. + if ( preg_match('/(avg|count|sum|max|min)\(\s*(.*)\s*\)(\s*as\s*(.+)?)?/i', $column, $matches)) + { + if ( count($matches) == 3) + { + return $matches[1].'('.$this->escape_column($matches[2]).')'; + } + else if ( count($matches) == 5) + { + return $matches[1].'('.$this->escape_column($matches[2]).') AS '.$this->escape_column($matches[2]); + } + } + // This matches any modifiers we support to SELECT. if ( ! preg_match('/\b(?:rand|all|distinct(?:row)?|high_priority|sql_(?:small_result|b(?:ig_result|uffer_result)|no_cache|ca(?:che|lc_found_rows)))\s/i', $column)) { @@ -205,12 +218,12 @@ class Database_Pdosqlite_Driver extends Database_Driver { return $res; } - public function list_tables(Database $db) + public function list_tables() { $sql = "SELECT `name` FROM `sqlite_master` WHERE `type`='table' ORDER BY `name`;"; try { - $result = $db->query($sql)->result(FALSE, PDO::FETCH_ASSOC); + $result = $this->query($sql)->result(FALSE, PDO::FETCH_ASSOC); $tables = array(); foreach ($result as $row) { @@ -298,14 +311,12 @@ class Pdosqlite_Result extends Database_Result { { if (is_object($result) OR $result = $link->prepare($sql)) { - // run the query - try + // run the query. Return true if success, false otherwise + if( ! $result->execute()) { - $result->execute(); - } - catch (PDOException $e) - { - throw new Kohana_Database_Exception('database.error', $e->getMessage()); + // Throw Kohana Exception with error message. See PDOStatement errorInfo() method + $arr_infos = $result->errorInfo(); + throw new Kohana_Database_Exception('database.error', $arr_infos[2]); } if (preg_match('/^SELECT|PRAGMA|EXPLAIN/i', $sql)) @@ -320,6 +331,8 @@ class Pdosqlite_Result extends Database_Result { elseif (preg_match('/^DELETE|INSERT|UPDATE/i', $sql)) { $this->insert_id = $link->lastInsertId(); + + $this->total_rows = $result->rowCount(); } } else diff --git a/kohana/libraries/drivers/Database/Pgsql.php b/kohana/libraries/drivers/Database/Pgsql.php index 62a33ad6..c8a7d819 100644 --- a/kohana/libraries/drivers/Database/Pgsql.php +++ b/kohana/libraries/drivers/Database/Pgsql.php @@ -68,18 +68,18 @@ class Database_Pgsql_Driver extends Database_Driver { { $hash = $this->query_hash($sql); - if ( ! isset(self::$query_cache[$hash])) + if ( ! isset($this->query_cache[$hash])) { // Set the cached object - self::$query_cache[$hash] = new Pgsql_Result(pg_query($this->link, $sql), $this->link, $this->db_config['object'], $sql); + $this->query_cache[$hash] = new Pgsql_Result(pg_query($this->link, $sql), $this->link, $this->db_config['object'], $sql); } else { // Rewind cached result - self::$query_cache[$hash]->rewind(); + $this->query_cache[$hash]->rewind(); } - return self::$query_cache[$hash]; + return $this->query_cache[$hash]; } // Suppress warning triggered when a database error occurs (e.g., a constraint violation) @@ -104,9 +104,22 @@ class Database_Pgsql_Driver extends Database_Driver { if (!$this->db_config['escape']) return $column; - if (strtolower($column) == 'count(*)' OR $column == '*') + if ($column == '*') return $column; + // This matches any functions we support to SELECT. + if ( preg_match('/(avg|count|sum|max|min)\(\s*(.*)\s*\)(\s*as\s*(.+)?)?/i', $column, $matches)) + { + if ( count($matches) == 3) + { + return $matches[1].'('.$this->escape_column($matches[2]).')'; + } + else if ( count($matches) == 5) + { + return $matches[1].'('.$this->escape_column($matches[2]).') AS '.$this->escape_column($matches[2]); + } + } + // This matches any modifiers we support to SELECT. if ( ! preg_match('/\b(?:all|distinct)\s/i', $column)) { @@ -147,14 +160,14 @@ class Database_Pgsql_Driver extends Database_Driver { { $prefix = ($num_regexs == 0) ? '' : $type; - return $prefix.' '.$this->escape_column($field).' REGEXP \''.$this->escape_str($match).'\''; + return $prefix.' '.$this->escape_column($field).' ~* \''.$this->escape_str($match).'\''; } public function notregex($field, $match, $type, $num_regexs) { $prefix = $num_regexs == 0 ? '' : $type; - return $prefix.' '.$this->escape_column($field).' NOT REGEXP \''.$this->escape_str($match) . '\''; + return $prefix.' '.$this->escape_column($field).' !~* \''.$this->escape_str($match) . '\''; } public function limit($limit, $offset = 0) @@ -225,10 +238,10 @@ class Database_Pgsql_Driver extends Database_Driver { return pg_escape_string($this->link, $str); } - public function list_tables(Database $db) + public function list_tables() { $sql = 'SELECT table_schema || \'.\' || table_name FROM information_schema.tables WHERE table_schema NOT IN (\'pg_catalog\', \'information_schema\')'; - $result = $db->query($sql)->result(FALSE, PGSQL_ASSOC); + $result = $this->query($sql)->result(FALSE, PGSQL_ASSOC); $retval = array(); foreach ($result as $row) @@ -246,39 +259,34 @@ class Database_Pgsql_Driver extends Database_Driver { public function list_fields($table) { - static $tables; + $result = NULL; - if (empty($tables[$table])) + foreach ($this->field_data($table) as $row) { - foreach ($this->field_data($table) as $row) + // Make an associative array + $result[$row->column_name] = $this->sql_type($row->data_type); + + if (!strncmp($row->column_default, 'nextval(', 8)) + { + $result[$row->column_name]['sequenced'] = TRUE; + } + + if ($row->is_nullable === 'YES') { - // Make an associative array - $tables[$table][$row->column_name] = $this->sql_type($row->data_type); - - if (!strncmp($row->column_default, 'nextval(', 8)) - { - $tables[$table][$row->column_name]['sequenced'] = TRUE; - } - - if ($row->is_nullable === 'YES') - { - $tables[$table][$row->column_name]['null'] = TRUE; - } + $result[$row->column_name]['null'] = TRUE; } } - if (!isset($tables[$table])) + if (!isset($result)) throw new Kohana_Database_Exception('database.table_not_found', $table); - return $tables[$table]; + return $result; } public function field_data($table) { - $columns = array(); - // http://www.postgresql.org/docs/8.3/static/infoschema-columns.html - $result = pg_query($this->link, ' + $result = $this->query(' SELECT column_name, column_default, is_nullable, data_type, udt_name, character_maximum_length, numeric_precision, numeric_precision_radix, numeric_scale FROM information_schema.columns @@ -286,15 +294,7 @@ class Database_Pgsql_Driver extends Database_Driver { ORDER BY ordinal_position '); - if ($result) - { - while ($row = pg_fetch_object($result)) - { - $columns[] = $row; - } - } - - return $columns; + return $result->result_array(TRUE); } } // End Database_Pgsql_Driver Class @@ -318,6 +318,7 @@ class Pgsql_Result extends Database_Result { */ public function __construct($result, $link, $object = TRUE, $sql) { + $this->link = $link; $this->result = $result; // If the query is a resource, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query @@ -418,9 +419,14 @@ class Pgsql_Result extends Database_Result { } } - while ($row = $fetch($this->result, NULL, $type)) + if ($this->total_rows) { - $rows[] = $row; + pg_result_seek($this->result, 0); + + while ($row = $fetch($this->result, NULL, $type)) + { + $rows[] = $row; + } } return $rows; @@ -450,10 +456,15 @@ class Pgsql_Result extends Database_Result { public function seek($offset) { - if ( ! $this->offsetExists($offset)) - return FALSE; + if ($this->offsetExists($offset) AND pg_result_seek($this->result, $offset)) + { + // Set the current row to the offset + $this->current_row = $offset; - return pg_result_seek($this->result, $offset); + return TRUE; + } + + return FALSE; } public function list_fields() @@ -524,4 +535,4 @@ class Kohana_Pgsql_Statement { { return $this; } -}
\ No newline at end of file +} diff --git a/kohana/views/kohana_error_disabled.php b/kohana/views/kohana_error_disabled.php index ed44b396..cd911328 100644 --- a/kohana/views/kohana_error_disabled.php +++ b/kohana/views/kohana_error_disabled.php @@ -2,13 +2,13 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> +<style type="text/css"> +<?php include Kohana::find_file('views', 'kohana_errors', FALSE, 'css') ?> +</style> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title><?php echo $error ?></title> </head> <body> -<style type="text/css"> -<?php include Kohana::find_file('views', 'kohana_errors', FALSE, 'css') ?> -</style> <div id="framework_error" style="width:24em;margin:50px auto;"> <h3><?php echo html::specialchars($error) ?></h3> <p style="text-align:center"><?php echo $message ?></p> diff --git a/kohana/views/kohana_error_page.php b/kohana/views/kohana_error_page.php index 572e057d..944064cc 100644 --- a/kohana/views/kohana_error_page.php +++ b/kohana/views/kohana_error_page.php @@ -2,14 +2,14 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> +<style type="text/css"> +<?php include Kohana::find_file('views', 'kohana_errors', FALSE, 'css') ?> +</style> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title><?php echo $error ?></title> <base href="http://php.net/" /> </head> <body> -<style type="text/css"> -<?php include Kohana::find_file('views', 'kohana_errors', FALSE, 'css') ?> -</style> <div id="framework_error" style="width:42em;margin:20px auto;"> <h3><?php echo html::specialchars($error) ?></h3> <p><?php echo html::specialchars($description) ?></p> diff --git a/modules/unit_test/i18n/en_US/unit_test.php b/modules/unit_test/i18n/en_US/unit_test.php index 34ff1c11..a4ec7c57 100644 --- a/modules/unit_test/i18n/en_US/unit_test.php +++ b/modules/unit_test/i18n/en_US/unit_test.php @@ -2,6 +2,8 @@ $lang = array ( + 'class' => 'Class', + 'method' => 'Method', 'invalid_test_path' => 'Failed to open test path: %s.', 'duplicate_test_class' => 'Duplicate test class named %s found in %s.', 'test_class_not_found' => 'No test class by the name of %s found in %s.', diff --git a/modules/unit_test/libraries/Unit_Test.php b/modules/unit_test/libraries/Unit_Test.php index cf8b85f0..debf53ab 100644 --- a/modules/unit_test/libraries/Unit_Test.php +++ b/modules/unit_test/libraries/Unit_Test.php @@ -211,13 +211,21 @@ class Unit_Test_Core { // Hide passed tests from the report? $hide_passed = (bool) (($hide_passed !== NULL) ? $hide_passed : Kohana::config('unit_test.hide_passed', FALSE, FALSE)); - + + + if (PHP_SAPI == 'cli') + { + $report = View::factory('kohana_unit_test_cli'); + } + else + { + $report = View::factory('kohana_unit_test'); + } // Render unit_test report - return View::factory('kohana_unit_test') - ->set('results', $this->results) - ->set('stats', $this->stats) - ->set('hide_passed', $hide_passed) - ->render(); + return $report->set('results', $this->results) + ->set('stats', $this->stats) + ->set('hide_passed', $hide_passed) + ->render(); } /** diff --git a/modules/unit_test/views/kohana_unit_test_cli.php b/modules/unit_test/views/kohana_unit_test_cli.php new file mode 100644 index 00000000..b0a9b6d4 --- /dev/null +++ b/modules/unit_test/views/kohana_unit_test_cli.php @@ -0,0 +1,36 @@ +<?php defined('SYSPATH') OR die('No direct access allowed.'); + +foreach ($results as $class => $methods) +{ + echo "\n\n" . Kohana::lang('unit_test.class') . ': ' . $class . "\n\n"; + printf('%s: %.2f%%', Kohana::lang('unit_test.score'), $stats[$class]['score']); + echo ",\n" . Kohana::lang('unit_test.total'), ': ', $stats[$class]['total'] . ",\n"; + echo Kohana::lang('unit_test.passed'), ': ', $stats[$class]['passed'] . ",\n"; + echo Kohana::lang('unit_test.failed'), ': ', $stats[$class]['failed'] . ",\n"; + echo Kohana::lang('unit_test.errors'), ': ', $stats[$class]['errors'] . "\n\n"; + + if (empty($methods)) + { + echo Kohana::lang('unit_test.no_tests_found'); + } + else + { + foreach ($methods as $method => $result) + { + // Hide passed tests from report + if ($result === TRUE AND $hide_passed === TRUE) + continue; + + echo Kohana::lang('unit_test.method') . ': ' . $method . ': '; + + if ($result === TRUE) + { + echo Kohana::lang('unit_test.passed') . "\n"; + } + else + { + echo Kohana::lang('unit_test.failed') . "\n\t" . $result->getMessage() . "\n"; + } + } + } +}
\ No newline at end of file |