diff --git a/README.md b/README.md index e38062eef..703508eb4 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,6 @@ Get Support! [Lighthouse](https://cakephp.lighthouseapp.com/) - Got issues? Please tell us! -[![Bake Status](https://secure.travis-ci.org/cakephp/cakephp.png?branch=master)](http://travis-ci.org/cakephp/cakephp) +[![Bake Status](https://secure.travis-ci.org/cakephp/cakephp.png?branch=2.4)](http://travis-ci.org/cakephp/cakephp) ![Cake Power](https://raw.github.com/cakephp/cakephp/master/lib/Cake/Console/Templates/skel/webroot/img/cake.power.gif) diff --git a/app/Config/Schema/db_acl.php b/app/Config/Schema/db_acl.php index 5bece0430..13c7b12f1 100644 --- a/app/Config/Schema/db_acl.php +++ b/app/Config/Schema/db_acl.php @@ -28,8 +28,6 @@ */ class DbAclSchema extends CakeSchema { - public $name = 'DbAcl'; - public function before($event = array()) { return true; } diff --git a/app/Config/core.php b/app/Config/core.php index 181ac15ed..ba7a9d0ea 100644 --- a/app/Config/core.php +++ b/app/Config/core.php @@ -70,6 +70,9 @@ * - `renderer` - string - The class responsible for rendering uncaught exceptions. If you choose a custom class you * should place the file for that class in app/Lib/Error. This class needs to implement a render method. * - `log` - boolean - Should Exceptions be logged? + * - `skipLog` - array - list of exceptions to skip for logging. Exceptions that + * extend one of the listed exceptions will also be skipped for logging. + * Example: `'skipLog' => array('NotFoundException', 'UnauthorizedException')` * * @see ErrorHandler for more information on exception handling and configuration. */ @@ -105,6 +108,33 @@ */ //Configure::write('App.baseUrl', env('SCRIPT_NAME')); +/** + * To configure CakePHP to use a particular domain URL + * for any URL generation inside the application, set the following + * configuration variable to the http(s) address to your domain. This + * will override the automatic detection of full base URL and can be + * useful when generating links from the CLI (e.g. sending emails) + */ + //Configure::write('App.fullBaseUrl', 'http://example.com'); + +/** + * Web path to the public images directory under webroot. + * If not set defaults to 'img/' + */ + //Configure::write('App.imageBaseUrl', 'img/'); + +/** + * Web path to the CSS files directory under webroot. + * If not set defaults to 'css/' + */ + //Configure::write('App.cssBaseUrl', 'css/'); + +/** + * Web path to the js files directory under webroot. + * If not set defaults to 'js/' + */ + //Configure::write('App.jsBaseUrl', 'js/'); + /** * Uncomment the define below to use CakePHP prefix routes. * diff --git a/app/Config/database.php.default b/app/Config/database.php.default index e3f5985b5..684fecdf9 100644 --- a/app/Config/database.php.default +++ b/app/Config/database.php.default @@ -52,6 +52,12 @@ * * unix_socket => * For MySQL to connect via socket specify the `unix_socket` parameter instead of `host` and `port` + * + * settings => + * Array of key/value pairs, on connection it executes SET statements for each pair + * For MySQL : http://dev.mysql.com/doc/refman/5.6/en/set-statement.html + * For Postgres : http://www.postgresql.org/docs/9.2/static/sql-set.html + * For Sql Server : http://msdn.microsoft.com/en-us/library/ms190356.aspx */ class DATABASE_CONFIG { diff --git a/app/Controller/PagesController.php b/app/Controller/PagesController.php index dd723c539..7c428d827 100644 --- a/app/Controller/PagesController.php +++ b/app/Controller/PagesController.php @@ -31,13 +31,6 @@ App::uses('AppController', 'Controller'); */ class PagesController extends AppController { -/** - * Controller name - * - * @var string - */ - public $name = 'Pages'; - /** * This controller does not use a model * @@ -50,6 +43,8 @@ class PagesController extends AppController { * * @param mixed What page to display * @return void + * @throws NotFoundException When the view file could not be found + * or MissingViewException in debug mode. */ public function display() { $path = func_get_args(); @@ -70,6 +65,14 @@ class PagesController extends AppController { $title_for_layout = Inflector::humanize($path[$count - 1]); } $this->set(compact('page', 'subpage', 'title_for_layout')); - $this->render(implode('/', $path)); + + try { + $this->render(implode('/', $path)); + } catch (MissingViewException $e) { + if (Configure::read('debug')) { + throw $e; + } + throw new NotFoundException(); + } } } diff --git a/app/webroot/.htaccess b/app/webroot/.htaccess index 48a63f014..1f19e4c06 100644 --- a/app/webroot/.htaccess +++ b/app/webroot/.htaccess @@ -2,5 +2,5 @@ RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f - RewriteRule ^(.*)$ index.php [QSA,L] + RewriteRule ^ index.php [L] diff --git a/lib/Cake/Cache/Cache.php b/lib/Cake/Cache/Cache.php index e696ffab6..c7c593916 100644 --- a/lib/Cake/Cache/Cache.php +++ b/lib/Cake/Cache/Cache.php @@ -51,6 +51,13 @@ class Cache { */ protected static $_config = array(); +/** + * Group to Config mapping + * + * @var array + */ + protected static $_groups = array(); + /** * Whether to reset the settings with the next call to Cache::set(); * @@ -130,6 +137,14 @@ class Cache { return false; } + if (!empty(self::$_config[$name]['groups'])) { + foreach (self::$_config[$name]['groups'] as $group) { + self::$_groups[$group][] = $name; + sort(self::$_groups[$group]); + self::$_groups[$group] = array_unique(self::$_groups[$group]); + } + } + $engine = self::$_config[$name]['engine']; if (!isset(self::$_engines[$name])) { @@ -159,7 +174,7 @@ class Cache { } $cacheClass = $class . 'Engine'; if (!is_subclass_of($cacheClass, 'CacheEngine')) { - throw new CacheException(__d('cake_dev', 'Cache engines must use CacheEngine as a base class.')); + throw new CacheException(__d('cake_dev', 'Cache engines must use %s as a base class.', 'CacheEngine')); } self::$_engines[$name] = new $cacheClass(); if (!self::$_engines[$name]->init($config)) { @@ -498,4 +513,33 @@ class Cache { return array(); } +/** + * Retrieve group names to config mapping. + * + * {{{ + * Cache::config('daily', array( + * 'duration' => '1 day', 'groups' => array('posts') + * )); + * Cache::config('weekly', array( + * 'duration' => '1 week', 'groups' => array('posts', 'archive') + * )); + * $configs = Cache::groupConfigs('posts'); + * }}} + * + * $config will equal to `array('posts' => array('daily', 'weekly'))` + * + * @param string $group group name or null to retrieve all group mappings + * @return array map of group and all configuration that has the same group + * @throws CacheException + */ + public static function groupConfigs($group = null) { + if ($group == null) { + return self::$_groups; + } + if (isset(self::$_groups[$group])) { + return array($group => self::$_groups[$group]); + } + throw new CacheException(__d('cake_dev', 'Invalid cache group %s', $group)); + } + } diff --git a/lib/Cake/Cache/Engine/FileEngine.php b/lib/Cake/Cache/Engine/FileEngine.php index 4f88ca3b6..47e0487d3 100644 --- a/lib/Cake/Cache/Engine/FileEngine.php +++ b/lib/Cake/Cache/Engine/FileEngine.php @@ -336,7 +336,7 @@ class FileEngine extends CacheEngine { $dir = $this->settings['path'] . $groups; if (!is_dir($dir)) { - mkdir($dir, 0777, true); + mkdir($dir, 0775, true); } $path = new SplFileInfo($dir . $key); @@ -369,6 +369,12 @@ class FileEngine extends CacheEngine { */ protected function _active() { $dir = new SplFileInfo($this->settings['path']); + if (Configure::read('debug')) { + $path = $dir->getPathname(); + if (!is_dir($path)) { + mkdir($path, 0775, true); + } + } if ($this->_init && !($dir->isDir() && $dir->isWritable())) { $this->_init = false; trigger_error(__d('cake_dev', '%s is not writable', $this->settings['path']), E_USER_WARNING); diff --git a/lib/Cake/Cache/Engine/MemcacheEngine.php b/lib/Cake/Cache/Engine/MemcacheEngine.php index 76b3b4c36..98b91d63b 100644 --- a/lib/Cake/Cache/Engine/MemcacheEngine.php +++ b/lib/Cake/Cache/Engine/MemcacheEngine.php @@ -165,7 +165,7 @@ class MemcacheEngine extends CacheEngine { public function increment($key, $offset = 1) { if ($this->settings['compress']) { throw new CacheException( - __d('cake_dev', 'Method increment() not implemented for compressed cache in %s', __CLASS__) + __d('cake_dev', 'Method %s not implemented for compressed cache in %s', 'increment()', __CLASS__) ); } return $this->_Memcache->increment($key, $offset); @@ -182,7 +182,7 @@ class MemcacheEngine extends CacheEngine { public function decrement($key, $offset = 1) { if ($this->settings['compress']) { throw new CacheException( - __d('cake_dev', 'Method decrement() not implemented for compressed cache in %s', __CLASS__) + __d('cake_dev', 'Method %s not implemented for compressed cache in %s', 'decrement()', __CLASS__) ); } return $this->_Memcache->decrement($key, $offset); diff --git a/lib/Cake/Configure/PhpReader.php b/lib/Cake/Configure/PhpReader.php index 100fffa74..b3446247c 100644 --- a/lib/Cake/Configure/PhpReader.php +++ b/lib/Cake/Configure/PhpReader.php @@ -71,7 +71,7 @@ class PhpReader implements ConfigReaderInterface { include $file; if (!isset($config)) { - throw new ConfigureException(__d('cake_dev', 'No variable $config found in %s', $file)); + throw new ConfigureException(__d('cake_dev', 'No variable %s found in %s', '$config', $file)); } return $config; } diff --git a/lib/Cake/Console/Command/BakeShell.php b/lib/Cake/Console/Command/BakeShell.php index 844f257bc..4c06c74f4 100644 --- a/lib/Cake/Console/Command/BakeShell.php +++ b/lib/Cake/Console/Command/BakeShell.php @@ -245,6 +245,9 @@ class BakeShell extends AppShell { 'help' => __d('cake_console', 'Database connection to use in conjunction with `bake all`.'), 'short' => 'c', 'default' => 'default' + ))->addOption('theme', array( + 'short' => 't', + 'help' => __d('cake_console', 'Theme to use when baking code.') )); } diff --git a/lib/Cake/Console/Command/ConsoleShell.php b/lib/Cake/Console/Command/ConsoleShell.php index c5638446e..d1659c16b 100644 --- a/lib/Cake/Console/Command/ConsoleShell.php +++ b/lib/Cake/Console/Command/ConsoleShell.php @@ -19,6 +19,7 @@ App::uses('AppShell', 'Console/Command'); * Provides a very basic 'interactive' console for CakePHP apps. * * @package Cake.Console.Command + * @deprecated Deprecated since version 2.4, will be removed in 3.0 */ class ConsoleShell extends AppShell { @@ -43,6 +44,35 @@ class ConsoleShell extends AppShell { */ public $models = array(); +/** + * _finished + * + * This shell is perpetual, setting this property to true exits the process + * + * @var mixed + */ + protected $_finished = false; + +/** + * _methodPatterns + * + * @var array + */ + protected $_methodPatterns = array( + 'help' => '/^(help|\?)/', + '_exit' => '/^(quit|exit)/', + '_models' => '/^models/i', + '_bind' => '/^(\w+) bind (\w+) (\w+)/', + '_unbind' => '/^(\w+) unbind (\w+) (\w+)/', + '_find' => '/.+->find/', + '_save' => '/.+->save/', + '_columns' => '/^(\w+) columns/', + '_routesReload' => '/^routes\s+reload/i', + '_routesShow' => '/^routes\s+show/i', + '_routeToString' => '/^route\s+(\(.*\))$/i', + '_routeToArray' => '/^route\s+(.*)$/i', + ); + /** * Override startup of the Shell * @@ -74,6 +104,11 @@ class ConsoleShell extends AppShell { } } +/** + * getOptionParser + * + * @return void + */ public function getOptionParser() { $description = array( 'The interactive console is a tool for testing parts of your', @@ -163,191 +198,289 @@ class ConsoleShell extends AppShell { * @return void */ public function main($command = null) { - while (true) { + $this->_finished = false; + while (!$this->_finished) { if (empty($command)) { $command = trim($this->in('')); } - switch (true) { - case $command == 'help': - $this->help(); - break; - case $command == 'quit': - case $command == 'exit': - return true; - case $command == 'models': - $this->out(__d('cake_console', 'Model classes:')); - $this->hr(); - foreach ($this->models as $model) { - $this->out(" - {$model}"); - } - break; - case preg_match("/^(\w+) bind (\w+) (\w+)/", $command, $tmp): - foreach ($tmp as $data) { - $data = strip_tags($data); - $data = str_replace($this->badCommandChars, "", $data); - } - $modelA = $tmp[1]; - $association = $tmp[2]; - $modelB = $tmp[3]; + $method = $this->_method($command); - if ($this->_isValidModel($modelA) && $this->_isValidModel($modelB) && in_array($association, $this->associations)) { - $this->{$modelA}->bindModel(array($association => array($modelB => array('className' => $modelB))), false); - $this->out(__d('cake_console', "Created %s association between %s and %s", - $association, $modelA, $modelB)); - } else { - $this->out(__d('cake_console', "Please verify you are using valid models and association types")); - } - break; - case preg_match("/^(\w+) unbind (\w+) (\w+)/", $command, $tmp): - foreach ($tmp as $data) { - $data = strip_tags($data); - $data = str_replace($this->badCommandChars, "", $data); - } - - $modelA = $tmp[1]; - $association = $tmp[2]; - $modelB = $tmp[3]; - - // Verify that there is actually an association to unbind - $currentAssociations = $this->{$modelA}->getAssociated(); - $validCurrentAssociation = false; - - foreach ($currentAssociations as $model => $currentAssociation) { - if ($model == $modelB && $association == $currentAssociation) { - $validCurrentAssociation = true; - } - } - - if ($this->_isValidModel($modelA) && $this->_isValidModel($modelB) && in_array($association, $this->associations) && $validCurrentAssociation) { - $this->{$modelA}->unbindModel(array($association => array($modelB))); - $this->out(__d('cake_console', "Removed %s association between %s and %s", - $association, $modelA, $modelB)); - } else { - $this->out(__d('cake_console', "Please verify you are using valid models, valid current association, and valid association types")); - } - break; - case (strpos($command, "->find") > 0): - // Remove any bad info - $command = strip_tags($command); - $command = str_replace($this->badCommandChars, "", $command); - - // Do we have a valid model? - list($modelToCheck, $tmp) = explode('->', $command); - - if ($this->_isValidModel($modelToCheck)) { - $findCommand = "\$data = \$this->$command;"; - //@codingStandardsIgnoreStart - @eval($findCommand); - //@codingStandardsIgnoreEnd - - if (is_array($data)) { - foreach ($data as $idx => $results) { - if (is_numeric($idx)) { // findAll() output - foreach ($results as $modelName => $result) { - $this->out("$modelName"); - - foreach ($result as $field => $value) { - if (is_array($value)) { - foreach ($value as $field2 => $value2) { - $this->out("\t$field2: $value2"); - } - - $this->out(); - } else { - $this->out("\t$field: $value"); - } - } - } - } else { // find() output - $this->out($idx); - - foreach ($results as $field => $value) { - if (is_array($value)) { - foreach ($value as $field2 => $value2) { - $this->out("\t$field2: $value2"); - } - - $this->out(); - } else { - $this->out("\t$field: $value"); - } - } - } - } - } else { - $this->out(); - $this->out(__d('cake_console', "No result set found")); - } - } else { - $this->out(__d('cake_console', "%s is not a valid model", $modelToCheck)); - } - - break; - case (strpos($command, '->save') > 0): - // Validate the model we're trying to save here - $command = strip_tags($command); - $command = str_replace($this->badCommandChars, "", $command); - list($modelToSave, $tmp) = explode("->", $command); - - if ($this->_isValidModel($modelToSave)) { - // Extract the array of data we are trying to build - list(, $data) = explode("->save", $command); - $data = preg_replace('/^\(*(array)?\(*(.+?)\)*$/i', '\\2', $data); - $saveCommand = "\$this->{$modelToSave}->save(array('{$modelToSave}' => array({$data})));"; - //@codingStandardsIgnoreStart - @eval($saveCommand); - //@codingStandardsIgnoreEnd - $this->out(__d('cake_console', 'Saved record for %s', $modelToSave)); - } - break; - case preg_match("/^(\w+) columns/", $command, $tmp): - $modelToCheck = strip_tags(str_replace($this->badCommandChars, "", $tmp[1])); - - if ($this->_isValidModel($modelToCheck)) { - // Get the column info for this model - $fieldsCommand = "\$data = \$this->{$modelToCheck}->getColumnTypes();"; - //@codingStandardsIgnoreStart - @eval($fieldsCommand); - //@codingStandardsIgnoreEnd - - if (is_array($data)) { - foreach ($data as $field => $type) { - $this->out("\t{$field}: {$type}"); - } - } - } else { - $this->out(__d('cake_console', "Please verify that you selected a valid model")); - } - break; - case preg_match("/^routes\s+reload/i", $command, $tmp): - if (!$this->_loadRoutes()) { - $this->err(__d('cake_console', "There was an error loading the routes config. Please check that the file exists and is free of parse errors.")); - break; - } - $this->out(__d('cake_console', "Routes configuration reloaded, %d routes connected", count(Router::$routes))); - break; - case preg_match("/^routes\s+show/i", $command, $tmp): - $this->out(print_r(Hash::combine(Router::$routes, '{n}.template', '{n}.defaults'), true)); - break; - case (preg_match("/^route\s+(\(.*\))$/i", $command, $tmp) == true): - //@codingStandardsIgnoreStart - if ($url = eval('return array' . $tmp[1] . ';')) { - //@codingStandardsIgnoreEnd - $this->out(Router::url($url)); - } - break; - case preg_match("/^route\s+(.*)/i", $command, $tmp): - $this->out(var_export(Router::parse($tmp[1]), true)); - break; - default: - $this->out(__d('cake_console', "Invalid command")); - $this->out(); + if ($method) { + $this->$method($command); + } else { + $this->out(__d('cake_console', "Invalid command")); + $this->out(); } $command = ''; } } +/** + * Determine the method to process the current command + * + * @param string $command + * @return string or false + */ + protected function _method($command) { + foreach ($this->_methodPatterns as $method => $pattern) { + if (preg_match($pattern, $command)) { + return $method; + } + } + + return false; + } + +/** + * Set the finiished property so that the loop in main method ends + * + * @return void + */ + protected function _exit() { + $this->_finished = true; + } + +/** + * List all models + * + * @return void + */ + protected function _models() { + $this->out(__d('cake_console', 'Model classes:')); + $this->hr(); + foreach ($this->models as $model) { + $this->out(" - {$model}"); + } + } + +/** + * Bind an association + * + * @param mixed $command + * @return void + */ + protected function _bind($command) { + preg_match($this->_methodPatterns[__FUNCTION__], $command, $tmp); + + foreach ($tmp as $data) { + $data = strip_tags($data); + $data = str_replace($this->badCommandChars, "", $data); + } + + $modelA = $tmp[1]; + $association = $tmp[2]; + $modelB = $tmp[3]; + + if ($this->_isValidModel($modelA) && $this->_isValidModel($modelB) && in_array($association, $this->associations)) { + $this->{$modelA}->bindModel(array($association => array($modelB => array('className' => $modelB))), false); + $this->out(__d('cake_console', "Created %s association between %s and %s", + $association, $modelA, $modelB)); + } else { + $this->out(__d('cake_console', "Please verify you are using valid models and association types")); + } + } + +/** + * Unbind an association + * + * @param mixed $command + * @return void + */ + protected function _unbind($command) { + preg_match($this->_methodPatterns[__FUNCTION__], $command, $tmp); + + foreach ($tmp as $data) { + $data = strip_tags($data); + $data = str_replace($this->badCommandChars, "", $data); + } + + $modelA = $tmp[1]; + $association = $tmp[2]; + $modelB = $tmp[3]; + + // Verify that there is actually an association to unbind + $currentAssociations = $this->{$modelA}->getAssociated(); + $validCurrentAssociation = false; + + foreach ($currentAssociations as $model => $currentAssociation) { + if ($model == $modelB && $association == $currentAssociation) { + $validCurrentAssociation = true; + } + } + + if ($this->_isValidModel($modelA) && $this->_isValidModel($modelB) && in_array($association, $this->associations) && $validCurrentAssociation) { + $this->{$modelA}->unbindModel(array($association => array($modelB))); + $this->out(__d('cake_console', "Removed %s association between %s and %s", + $association, $modelA, $modelB)); + } else { + $this->out(__d('cake_console', "Please verify you are using valid models, valid current association, and valid association types")); + } + } + +/** + * Perform a find + * + * @param mixed $command + * @return void + */ + protected function _find($command) { + $command = strip_tags($command); + $command = str_replace($this->badCommandChars, "", $command); + + // Do we have a valid model? + list($modelToCheck, $tmp) = explode('->', $command); + + if ($this->_isValidModel($modelToCheck)) { + $findCommand = "\$data = \$this->$command;"; + //@codingStandardsIgnoreStart + @eval($findCommand); + //@codingStandardsIgnoreEnd + + if (is_array($data)) { + foreach ($data as $idx => $results) { + if (is_numeric($idx)) { // findAll() output + foreach ($results as $modelName => $result) { + $this->out("$modelName"); + + foreach ($result as $field => $value) { + if (is_array($value)) { + foreach ($value as $field2 => $value2) { + $this->out("\t$field2: $value2"); + } + + $this->out(); + } else { + $this->out("\t$field: $value"); + } + } + } + } else { // find() output + $this->out($idx); + + foreach ($results as $field => $value) { + if (is_array($value)) { + foreach ($value as $field2 => $value2) { + $this->out("\t$field2: $value2"); + } + + $this->out(); + } else { + $this->out("\t$field: $value"); + } + } + } + } + } else { + $this->out(); + $this->out(__d('cake_console', "No result set found")); + } + } else { + $this->out(__d('cake_console', "%s is not a valid model", $modelToCheck)); + } + } + +/** + * Save a record + * + * @param mixed $command + * @return void + */ + protected function _save($command) { + // Validate the model we're trying to save here + $command = strip_tags($command); + $command = str_replace($this->badCommandChars, "", $command); + list($modelToSave, $tmp) = explode("->", $command); + + if ($this->_isValidModel($modelToSave)) { + // Extract the array of data we are trying to build + list(, $data) = explode("->save", $command); + $data = preg_replace('/^\(*(array)?\(*(.+?)\)*$/i', '\\2', $data); + $saveCommand = "\$this->{$modelToSave}->save(array('{$modelToSave}' => array({$data})));"; + //@codingStandardsIgnoreStart + @eval($saveCommand); + //@codingStandardsIgnoreEnd + $this->out(__d('cake_console', 'Saved record for %s', $modelToSave)); + } + } + +/** + * Show the columns for a model + * + * @param mixed $command + * @return void + */ + protected function _columns($command) { + preg_match($this->_methodPatterns[__FUNCTION__], $command, $tmp); + + $modelToCheck = strip_tags(str_replace($this->badCommandChars, "", $tmp[1])); + + if ($this->_isValidModel($modelToCheck)) { + // Get the column info for this model + $fieldsCommand = "\$data = \$this->{$modelToCheck}->getColumnTypes();"; + //@codingStandardsIgnoreStart + @eval($fieldsCommand); + //@codingStandardsIgnoreEnd + + if (is_array($data)) { + foreach ($data as $field => $type) { + $this->out("\t{$field}: {$type}"); + } + } + } else { + $this->out(__d('cake_console', "Please verify that you selected a valid model")); + } + } + +/** + * Reload route definitions + * + * @return void + */ + protected function _routesReload() { + if (!$this->_loadRoutes()) { + return $this->err(__d('cake_console', "There was an error loading the routes config. Please check that the file exists and is free of parse errors.")); + } + $this->out(__d('cake_console', "Routes configuration reloaded, %d routes connected", count(Router::$routes))); + } + +/** + * Show all routes + * + * @return void + */ + protected function _routesShow() { + $this->out(print_r(Hash::combine(Router::$routes, '{n}.template', '{n}.defaults'), true)); + } + +/** + * Parse an array url and show the equivalent url as a string + * + * @param mixed $command + * @return void + */ + protected function _routeToString($command) { + preg_match($this->_methodPatterns[__FUNCTION__], $command, $tmp); + + //@codingStandardsIgnoreStart + if ($url = eval('return array' . $tmp[1] . ';')) { + //@codingStandardsIgnoreEnd + $this->out(Router::url($url)); + } + } + +/** + * Parse a string url and show as an array + * + * @param mixed $command + * @return void + */ + protected function _routeToArray($command) { + preg_match($this->_methodPatterns[__FUNCTION__], $command, $tmp); + + $this->out(var_export(Router::parse($tmp[1]), true)); + } + /** * Tells if the specified model is included in the list of available models * diff --git a/lib/Cake/Console/Command/SchemaShell.php b/lib/Cake/Console/Command/SchemaShell.php index 314502af4..223b1179b 100644 --- a/lib/Cake/Console/Command/SchemaShell.php +++ b/lib/Cake/Console/Command/SchemaShell.php @@ -153,6 +153,13 @@ class SchemaShell extends AppShell { Configure::write('Cache.disable', $cacheDisable); + if (!empty($this->params['exclude']) && !empty($content)) { + $excluded = String::tokenize($this->params['exclude']); + foreach ($excluded as $table) { + unset($content['tables'][$table]); + } + } + if ($snapshot === true) { $fileName = rtrim($this->params['file'], '.php'); $Folder = new Folder($this->Schema->path); @@ -228,10 +235,9 @@ class SchemaShell extends AppShell { if ($File->write($contents)) { $this->out(__d('cake_console', 'SQL dump file created in %s', $File->pwd())); return $this->_stop(); - } else { - $this->err(__d('cake_console', 'SQL dump could not be created')); - return $this->_stop(); } + $this->err(__d('cake_console', 'SQL dump could not be created')); + return $this->_stop(); } $this->out($contents); return $contents; @@ -365,10 +371,18 @@ class SchemaShell extends AppShell { if (empty($table)) { foreach ($compare as $table => $changes) { - $contents[$table] = $db->alterSchema(array($table => $changes), $table); + if (isset($compare[$table]['create'])) { + $contents[$table] = $db->createSchema($Schema, $table); + } else { + $contents[$table] = $db->alterSchema(array($table => $compare[$table]), $table); + } } } elseif (isset($compare[$table])) { - $contents[$table] = $db->alterSchema(array($table => $compare[$table]), $table); + if (isset($compare[$table]['create'])) { + $contents[$table] = $db->createSchema($Schema, $table); + } else { + $contents[$table] = $db->alterSchema(array($table => $compare[$table]), $table); + } } if (empty($contents)) { @@ -479,6 +493,9 @@ class SchemaShell extends AppShell { $write = array( 'help' => __d('cake_console', 'Write the dumped SQL to a file.') ); + $exclude = array( + 'help' => __d('cake_console', 'Tables to exclude as comma separated list.') + ); $parser = parent::getOptionParser(); $parser->description( @@ -492,7 +509,7 @@ class SchemaShell extends AppShell { ))->addSubcommand('generate', array( 'help' => __d('cake_console', 'Reads from --connection and writes to --path. Generate snapshots with -s'), 'parser' => array( - 'options' => compact('plugin', 'path', 'file', 'name', 'connection', 'snapshot', 'force', 'models'), + 'options' => compact('plugin', 'path', 'file', 'name', 'connection', 'snapshot', 'force', 'models', 'exclude'), 'arguments' => array( 'snapshot' => array('help' => __d('cake_console', 'Generate a snapshot.')) ) diff --git a/lib/Cake/Console/Command/ServerShell.php b/lib/Cake/Console/Command/ServerShell.php index 492b7d48b..f9c7a58ff 100644 --- a/lib/Cake/Console/Command/ServerShell.php +++ b/lib/Cake/Console/Command/ServerShell.php @@ -122,7 +122,7 @@ class ServerShell extends AppShell { */ public function main() { if (version_compare(PHP_VERSION, '5.4.0') < 0) { - $this->out(__d('cake_console', 'This command is available on PHP5.4 or above')); + $this->out(__d('cake_console', 'This command is available on %s or above', 'PHP5.4')); return; } diff --git a/lib/Cake/Console/Command/Task/ControllerTask.php b/lib/Cake/Console/Command/Task/ControllerTask.php index 46e294dff..7176e40ed 100644 --- a/lib/Cake/Console/Command/Task/ControllerTask.php +++ b/lib/Cake/Console/Command/Task/ControllerTask.php @@ -480,6 +480,12 @@ class ControllerTask extends BakeTask { ))->addOption('connection', array( 'short' => 'c', 'help' => __d('cake_console', 'The connection the controller\'s model is on.') + ))->addOption('theme', array( + 'short' => 't', + 'help' => __d('cake_console', 'Theme to use when baking code.') + ))->addOption('force', array( + 'short' => 'f', + 'help' => __d('cake_console', 'Force overwriting existing files without prompting.') ))->addSubcommand('all', array( 'help' => __d('cake_console', 'Bake all controllers with CRUD methods.') ))->epilog(__d('cake_console', 'Omitting all arguments and options will enter into an interactive mode.')); diff --git a/lib/Cake/Console/Command/Task/FixtureTask.php b/lib/Cake/Console/Command/Task/FixtureTask.php index 21b8389a1..a5f708c25 100644 --- a/lib/Cake/Console/Command/Task/FixtureTask.php +++ b/lib/Cake/Console/Command/Task/FixtureTask.php @@ -83,8 +83,18 @@ class FixtureTask extends BakeTask { ))->addOption('plugin', array( 'help' => __d('cake_console', 'CamelCased name of the plugin to bake fixtures for.'), 'short' => 'p', + ))->addOption('schema', array( + 'help' => __d('cake_console', 'Importing schema for fixtures rather than hardcoding it.'), + 'short' => 's', + 'boolean' => true + ))->addOption('theme', array( + 'short' => 't', + 'help' => __d('cake_console', 'Theme to use when baking code.') + ))->addOption('force', array( + 'short' => 'f', + 'help' => __d('cake_console', 'Force overwriting existing files without prompting.') ))->addOption('records', array( - 'help' => __d('cake_console', 'Used with --count and /all commands to pull [n] records from the live tables, where [n] is either --count or the default of 10'), + 'help' => __d('cake_console', 'Used with --count and /all commands to pull [n] records from the live tables, where [n] is either --count or the default of 10.'), 'short' => 'r', 'boolean' => true ))->epilog(__d('cake_console', 'Omitting all arguments and options will enter into an interactive mode.')); @@ -124,9 +134,14 @@ class FixtureTask extends BakeTask { $this->interactive = false; $this->Model->interactive = false; $tables = $this->Model->listAll($this->connection, false); + foreach ($tables as $table) { $model = $this->_modelName($table); - $this->bake($model); + $importOptions = array(); + if (!empty($this->params['schema'])) { + $importOptions['schema'] = $model; + } + $this->bake($model, false, $importOptions); } } @@ -158,11 +173,20 @@ class FixtureTask extends BakeTask { */ public function importOptions($modelName) { $options = array(); - $doSchema = $this->in(__d('cake_console', 'Would you like to import schema for this fixture?'), array('y', 'n'), 'n'); - if ($doSchema === 'y') { + + if (!empty($this->params['schema'])) { $options['schema'] = $modelName; + } else { + $doSchema = $this->in(__d('cake_console', 'Would you like to import schema for this fixture?'), array('y', 'n'), 'n'); + if ($doSchema === 'y') { + $options['schema'] = $modelName; + } + } + if (!empty($this->params['records'])) { + $doRecords = 'y'; + } else { + $doRecords = $this->in(__d('cake_console', 'Would you like to use record importing for this fixture?'), array('y', 'n'), 'n'); } - $doRecords = $this->in(__d('cake_console', 'Would you like to use record importing for this fixture?'), array('y', 'n'), 'n'); if ($doRecords === 'y') { $options['records'] = true; } diff --git a/lib/Cake/Console/Command/Task/ModelTask.php b/lib/Cake/Console/Command/Task/ModelTask.php index 525149398..1ae4fcca9 100644 --- a/lib/Cake/Console/Command/Task/ModelTask.php +++ b/lib/Cake/Console/Command/Task/ModelTask.php @@ -164,7 +164,7 @@ class ModelTask extends BakeTask { * @param array $options Array of options to use for the selections. indexes must start at 0 * @param string $prompt Prompt to use for options list. * @param integer $default The default option for the given prompt. - * @return integer result of user choice. + * @return integer Result of user choice. */ public function inOptions($options, $prompt = null, $default = null) { $valid = false; @@ -347,7 +347,7 @@ class ModelTask extends BakeTask { * @return array $validate Array of user selected validations. */ public function doValidation($model) { - if (!is_object($model)) { + if (!$model instanceof Model) { return false; } $fields = $model->schema(); @@ -490,10 +490,10 @@ class ModelTask extends BakeTask { * Handles associations * * @param Model $model - * @return array $associations + * @return array Associations */ public function doAssociations($model) { - if (!is_object($model)) { + if (!$model instanceof Model) { return false; } if ($this->interactive === true) { @@ -538,12 +538,36 @@ class ModelTask extends BakeTask { return $associations; } +/** + * Handles behaviors + * + * @param Model $model + * @return array Behaviors + */ + public function doActsAs($model) { + if (!$model instanceof Model) { + return false; + } + $behaviors = array(); + $fields = $model->schema(true); + if (empty($fields)) { + return array(); + } + + if (isset($fields['lft']) && $fields['lft']['type'] === 'integer' && + isset($fields['rght']) && $fields['rght']['type'] === 'integer' && + isset($fields['parent_id'])) { + $behaviors[] = 'Tree'; + } + return $behaviors; + } + /** * Find belongsTo relations and add them to the associations list. * * @param Model $model Model instance of model being generated. * @param array $associations Array of in progress associations - * @return array $associations with belongsTo added in. + * @return array Associations with belongsTo added in. */ public function findBelongsTo(Model $model, $associations) { $fieldNames = array_keys($model->schema(true)); @@ -572,7 +596,7 @@ class ModelTask extends BakeTask { * * @param Model $model Model instance being generated * @param array $associations Array of in progress associations - * @return array $associations with hasOne and hasMany added in. + * @return array Associations with hasOne and hasMany added in. */ public function findHasOneAndMany(Model $model, $associations) { $foreignKey = $this->_modelKey($model->name); @@ -615,7 +639,7 @@ class ModelTask extends BakeTask { * * @param Model $model Model instance being generated * @param array $associations Array of in-progress associations - * @return array $associations with hasAndBelongsToMany added in. + * @return array Associations with hasAndBelongsToMany added in. */ public function findHasAndBelongsToMany(Model $model, $associations) { $foreignKey = $this->_modelKey($model->name); @@ -747,7 +771,7 @@ class ModelTask extends BakeTask { /** * Finds all possible keys to use on custom associations. * - * @return array array of tables and possible keys + * @return array Array of tables and possible keys */ protected function _generatePossibleKeys() { $possible = array(); @@ -771,11 +795,12 @@ class ModelTask extends BakeTask { * @return string */ public function bake($name, $data = array()) { - if (is_object($name)) { + if ($name instanceof Model) { if (!$data) { $data = array(); $data['associations'] = $this->doAssociations($name); $data['validate'] = $this->doValidation($name); + $data['actsAs'] = $this->doActsAs($name); } $data['primaryKey'] = $name->primaryKey; $data['useTable'] = $name->table; @@ -784,8 +809,10 @@ class ModelTask extends BakeTask { } else { $data['name'] = $name; } + $defaults = array( 'associations' => array(), + 'actsAs' => array(), 'validate' => array(), 'primaryKey' => 'id', 'useTable' => null, @@ -920,7 +947,7 @@ class ModelTask extends BakeTask { * Forces the user to specify the model he wants to bake, and returns the selected model name. * * @param string $useDbConfig Database config name - * @return string the model name + * @return string The model name */ public function getName($useDbConfig = null) { $this->listAll($useDbConfig); @@ -965,9 +992,15 @@ class ModelTask extends BakeTask { ))->addOption('plugin', array( 'short' => 'p', 'help' => __d('cake_console', 'Plugin to bake the model into.') + ))->addOption('theme', array( + 'short' => 't', + 'help' => __d('cake_console', 'Theme to use when baking code.') ))->addOption('connection', array( 'short' => 'c', 'help' => __d('cake_console', 'The connection the model table is on.') + ))->addOption('force', array( + 'short' => 'f', + 'help' => __d('cake_console', 'Force overwriting existing files without prompting.') ))->epilog(__d('cake_console', 'Omitting all arguments and options will enter into an interactive mode.')); } diff --git a/lib/Cake/Console/Command/Task/ProjectTask.php b/lib/Cake/Console/Command/Task/ProjectTask.php index d8fb25cb5..8a987d67f 100644 --- a/lib/Cake/Console/Command/Task/ProjectTask.php +++ b/lib/Cake/Console/Command/Task/ProjectTask.php @@ -117,8 +117,8 @@ class ProjectTask extends AppShell { } $success = $this->corePath($path, $hardCode) === true; if ($success) { - $this->out(__d('cake_console', ' * CAKE_CORE_INCLUDE_PATH set to %s in webroot/index.php', CAKE_CORE_INCLUDE_PATH)); - $this->out(__d('cake_console', ' * CAKE_CORE_INCLUDE_PATH set to %s in webroot/test.php', CAKE_CORE_INCLUDE_PATH)); + $this->out(__d('cake_console', ' * CAKE_CORE_INCLUDE_PATH set to %s in %s', CAKE_CORE_INCLUDE_PATH, 'webroot/index.php')); + $this->out(__d('cake_console', ' * CAKE_CORE_INCLUDE_PATH set to %s in %s', CAKE_CORE_INCLUDE_PATH, 'webroot/test.php')); } else { $this->err(__d('cake_console', 'Unable to set CAKE_CORE_INCLUDE_PATH, you should change it in %s', $path . 'webroot' . DS . 'index.php')); $success = false; @@ -435,6 +435,9 @@ class ProjectTask extends AppShell { ))->addOption('empty', array( 'boolean' => true, 'help' => __d('cake_console', 'Create empty files in each of the directories. Good if you are using git') + ))->addOption('theme', array( + 'short' => 't', + 'help' => __d('cake_console', 'Theme to use when baking code.') ))->addOption('skel', array( 'default' => current(App::core('Console')) . 'Templates' . DS . 'skel', 'help' => __d('cake_console', 'The directory layout to use for the new application skeleton. Defaults to cake/Console/Templates/skel of CakePHP used to create the project.') diff --git a/lib/Cake/Console/Command/Task/TestTask.php b/lib/Cake/Console/Command/Task/TestTask.php index c0643cf77..5350918ed 100644 --- a/lib/Cake/Console/Command/Task/TestTask.php +++ b/lib/Cake/Console/Command/Task/TestTask.php @@ -563,9 +563,15 @@ class TestTask extends BakeTask { ) ))->addArgument('name', array( 'help' => __d('cake_console', 'An existing class to bake tests for.') + ))->addOption('theme', array( + 'short' => 't', + 'help' => __d('cake_console', 'Theme to use when baking code.') ))->addOption('plugin', array( 'short' => 'p', 'help' => __d('cake_console', 'CamelCased name of the plugin to bake tests for.') + ))->addOption('force', array( + 'short' => 'f', + 'help' => __d('cake_console', 'Force overwriting existing files without prompting.') ))->epilog(__d('cake_console', 'Omitting all arguments and options will enter into an interactive mode.')); } diff --git a/lib/Cake/Console/Command/Task/ViewTask.php b/lib/Cake/Console/Command/Task/ViewTask.php index e38fd2ab0..9307621a3 100644 --- a/lib/Cake/Console/Command/Task/ViewTask.php +++ b/lib/Cake/Console/Command/Task/ViewTask.php @@ -434,9 +434,15 @@ class ViewTask extends BakeTask { ))->addOption('admin', array( 'help' => __d('cake_console', 'Set to only bake views for a prefix in Routing.prefixes'), 'boolean' => true + ))->addOption('theme', array( + 'short' => 't', + 'help' => __d('cake_console', 'Theme to use when baking code.') ))->addOption('connection', array( 'short' => 'c', 'help' => __d('cake_console', 'The connection the connected model is on.') + ))->addOption('force', array( + 'short' => 'f', + 'help' => __d('cake_console', 'Force overwriting existing files without prompting.') ))->addSubcommand('all', array( 'help' => __d('cake_console', 'Bake all CRUD action views for all controllers. Requires models and controllers to exist.') ))->epilog(__d('cake_console', 'Omitting all arguments and options will enter into an interactive mode.')); diff --git a/lib/Cake/Console/Command/TestsuiteShell.php b/lib/Cake/Console/Command/TestsuiteShell.php index 860662b9e..05572c91c 100644 --- a/lib/Cake/Console/Command/TestsuiteShell.php +++ b/lib/Cake/Console/Command/TestsuiteShell.php @@ -42,8 +42,7 @@ class TestsuiteShell extends TestShell { $parser = parent::getOptionParser(); $parser->description(array( __d('cake_console', 'The CakePHP Testsuite allows you to run test cases from the command line'), - __d('cake_console', 'This shell is for backwards-compatibility only'), - __d('cake_console', 'use the test shell instead') + __d('cake_console', "This shell is for backwards-compatibility only\nuse the test shell instead"), )); return $parser; diff --git a/lib/Cake/Console/ConsoleErrorHandler.php b/lib/Cake/Console/ConsoleErrorHandler.php index e249ccfe3..4dcfcf1eb 100644 --- a/lib/Cake/Console/ConsoleErrorHandler.php +++ b/lib/Cake/Console/ConsoleErrorHandler.php @@ -95,7 +95,7 @@ class ConsoleErrorHandler { /** * Wrapper for exit(), used for testing. * - * @param int $code The exit code. + * @param integer $code The exit code. * @return void */ protected function _stop($code = 0) { diff --git a/lib/Cake/Console/ConsoleInput.php b/lib/Cake/Console/ConsoleInput.php index fc76a764c..8d7201d83 100644 --- a/lib/Cake/Console/ConsoleInput.php +++ b/lib/Cake/Console/ConsoleInput.php @@ -50,4 +50,16 @@ class ConsoleInput { return fgets($this->_input); } +/** + * Checks if data is available on the stream + * + * @param integer $timeout An optional time to wait for data + * @return bool True for data available, false otherwise + */ + public function dataAvailable($timeout = 0) { + $readFds = array($this->_input); + $readyFds = stream_select($readFds, $writeFds, $errorFds, $timeout); + return ($readyFds > 0); + } + } diff --git a/lib/Cake/Console/ConsoleOutput.php b/lib/Cake/Console/ConsoleOutput.php index 466b08544..b270fce29 100644 --- a/lib/Cake/Console/ConsoleOutput.php +++ b/lib/Cake/Console/ConsoleOutput.php @@ -141,6 +141,7 @@ class ConsoleOutput { 'success' => array('text' => 'green'), 'comment' => array('text' => 'blue'), 'question' => array('text' => 'magenta'), + 'notice' => array('text' => 'cyan') ); /** diff --git a/lib/Cake/Console/Shell.php b/lib/Cake/Console/Shell.php index e129b78f6..5f52ecd69 100644 --- a/lib/Cake/Console/Shell.php +++ b/lib/Cake/Console/Shell.php @@ -24,6 +24,7 @@ App::uses('ConsoleInputSubcommand', 'Console'); App::uses('ConsoleOptionParser', 'Console'); App::uses('ClassRegistry', 'Utility'); App::uses('File', 'Utility'); +App::uses('ClassRegistry', 'Utility'); /** * Base class for command-line utilities for automating programmer chores. @@ -120,6 +121,13 @@ class Shell extends Object { */ public $uses = array(); +/** + * This shell's primary model class name, the first model in the $uses property + * + * @var string + */ + public $modelClass = null; + /** * Task Collection for the command, used to create Tasks. * @@ -178,7 +186,7 @@ class Shell extends Object { if ($this->tasks !== null && $this->tasks !== false) { $this->_mergeVars(array('tasks'), $parent, true); } - if ($this->uses !== null && $this->uses !== false) { + if (!empty($this->uses)) { $this->_mergeVars(array('uses'), $parent, false); } } @@ -224,31 +232,66 @@ class Shell extends Object { } /** - * If $uses = true - * Loads AppModel file and constructs AppModel class - * makes $this->AppModel available to subclasses - * If public $uses is an array of models will load those models + * If $uses is an array load each of the models in the array * * @return boolean */ protected function _loadModels() { - if (empty($this->uses)) { - return false; + if (is_array($this->uses)) { + list(, $this->modelClass) = pluginSplit(current($this->uses)); + foreach ($this->uses as $modelClass) { + $this->loadModel($modelClass); + } + } + return true; + } + +/** + * Lazy loads models using the loadModel() method if declared in $uses + * + * @param string $name + * @return void + */ + public function __isset($name) { + if (is_array($this->uses)) { + foreach ($this->uses as $modelClass) { + list(, $class) = pluginSplit($modelClass); + if ($name === $class) { + return $this->loadModel($modelClass); + } + } + } + } + +/** + * Loads and instantiates models required by this shell. + * + * @param string $modelClass Name of model class to load + * @param mixed $id Initial ID the instanced model class should have + * @return mixed true when single model found and instance created, error returned if model not found. + * @throws MissingModelException if the model class cannot be found. + */ + public function loadModel($modelClass = null, $id = null) { + if ($modelClass === null) { + $modelClass = $this->modelClass; } - $uses = is_array($this->uses) ? $this->uses : array($this->uses); - - $modelClassName = $uses[0]; - if (strpos($uses[0], '.') !== false) { - list($plugin, $modelClassName) = explode('.', $uses[0]); - } - $this->modelClass = $modelClassName; - - foreach ($uses as $modelClass) { - list($plugin, $modelClass) = pluginSplit($modelClass, true); - $this->{$modelClass} = ClassRegistry::init($plugin . $modelClass); + $this->uses = ($this->uses) ? (array)$this->uses : array(); + if (!in_array($modelClass, $this->uses)) { + $this->uses[] = $modelClass; } + list($plugin, $modelClass) = pluginSplit($modelClass, true); + if (!isset($this->modelClass)) { + $this->modelClass = $modelClass; + } + + $this->{$modelClass} = ClassRegistry::init(array( + 'class' => $plugin . $modelClass, 'alias' => $modelClass, 'id' => $id + )); + if (!$this->{$modelClass}) { + throw new MissingModelException($modelClass); + } return true; } @@ -650,7 +693,7 @@ class Shell extends Object { $this->out(); - if (is_file($path) && $this->interactive === true) { + if (is_file($path) && empty($this->params['force']) && $this->interactive === true) { $this->out(__d('cake_console', 'File `%s` exists', $path)); $key = $this->in(__d('cake_console', 'Do you want to overwrite?'), array('y', 'n', 'q'), 'n'); @@ -835,12 +878,12 @@ class Shell extends Object { return; } CakeLog::config('stdout', array( - 'engine' => 'ConsoleLog', + 'engine' => 'Console', 'types' => array('notice', 'info'), 'stream' => $this->stdout, )); CakeLog::config('stderr', array( - 'engine' => 'ConsoleLog', + 'engine' => 'Console', 'types' => array('emergency', 'alert', 'critical', 'error', 'warning', 'debug'), 'stream' => $this->stderr, )); diff --git a/lib/Cake/Console/ShellDispatcher.php b/lib/Cake/Console/ShellDispatcher.php index cb6711e90..ab1709131 100644 --- a/lib/Cake/Console/ShellDispatcher.php +++ b/lib/Cake/Console/ShellDispatcher.php @@ -145,7 +145,9 @@ class ShellDispatcher { $this->setErrorHandlers(); if (!defined('FULL_BASE_URL')) { - define('FULL_BASE_URL', 'http://localhost'); + $url = Configure::read('App.fullBaseUrl'); + define('FULL_BASE_URL', $url ? $url : 'http://localhost'); + Configure::write('App.fullBaseUrl', FULL_BASE_URL); } return true; diff --git a/lib/Cake/Console/TaskCollection.php b/lib/Cake/Console/TaskCollection.php index 1f4dcfcd2..ff1ddee67 100644 --- a/lib/Cake/Console/TaskCollection.php +++ b/lib/Cake/Console/TaskCollection.php @@ -50,8 +50,17 @@ class TaskCollection extends ObjectCollection { } /** - * Loads/constructs a task. Will return the instance in the collection - * if it already exists. + * Loads/constructs a task. Will return the instance in the registry if it already exists. + * + * You can alias your task as an existing task by setting the 'className' key, i.e., + * {{{ + * public $tasks = array( + * 'DbConfig' => array( + * 'className' => 'Bakeplus.DbConfigure' + * ); + * ); + * }}} + * All calls to the `DbConfig` task would use `DbConfigure` found in the `Bakeplus` plugin instead. * * @param string $task Task name to load * @param array $settings Settings for the task. @@ -59,26 +68,33 @@ class TaskCollection extends ObjectCollection { * @throws MissingTaskException when the task could not be found */ public function load($task, $settings = array()) { + if (is_array($settings) && isset($settings['className'])) { + $alias = $task; + $task = $settings['className']; + } list($plugin, $name) = pluginSplit($task, true); - - if (isset($this->_loaded[$name])) { - return $this->_loaded[$name]; + if (!isset($alias)) { + $alias = $name; } + if (isset($this->_loaded[$alias])) { + return $this->_loaded[$alias]; + } $taskClass = $name . 'Task'; App::uses($taskClass, $plugin . 'Console/Command/Task'); $exists = class_exists($taskClass); if (!$exists) { throw new MissingTaskException(array( - 'class' => $taskClass + 'class' => $taskClass, + 'plugin' => substr($plugin, 0, -1) )); } - $this->_loaded[$name] = new $taskClass( + $this->_loaded[$alias] = new $taskClass( $this->_Shell->stdout, $this->_Shell->stderr, $this->_Shell->stdin ); - return $this->_loaded[$name]; + return $this->_loaded[$alias]; } } diff --git a/lib/Cake/Console/Templates/default/classes/model.ctp b/lib/Cake/Console/Templates/default/classes/model.ctp index 28f55b91c..7668b1767 100644 --- a/lib/Cake/Console/Templates/default/classes/model.ctp +++ b/lib/Cake/Console/Templates/default/classes/model.ctp @@ -74,6 +74,16 @@ if ($displayField): ?> +/** + * Behaviors + * + * @var array + */ + public $actsAs = array(); + + 'FileLog', + 'engine' => 'File', 'types' => array('notice', 'info', 'debug'), 'file' => 'debug', )); CakeLog::config('error', array( - 'engine' => 'FileLog', + 'engine' => 'File', 'types' => array('warning', 'error', 'critical', 'alert', 'emergency'), 'file' => 'error', )); diff --git a/lib/Cake/Console/Templates/skel/Config/core.php b/lib/Cake/Console/Templates/skel/Config/core.php index a8673f633..4f91b05e6 100644 --- a/lib/Cake/Console/Templates/skel/Config/core.php +++ b/lib/Cake/Console/Templates/skel/Config/core.php @@ -6,18 +6,9 @@ * * PHP 5 * - * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) - * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) - * - * Licensed under The MIT License - * For full copyright and license information, please see the LICENSE.txt - * Redistributions of files must retain the above copyright notice. - * - * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) * @link http://cakephp.org CakePHP(tm) Project * @package app.Config * @since CakePHP(tm) v 0.2.9 - * @license http://www.opensource.org/licenses/mit-license.php MIT License */ /** @@ -70,6 +61,9 @@ * - `renderer` - string - The class responsible for rendering uncaught exceptions. If you choose a custom class you * should place the file for that class in app/Lib/Error. This class needs to implement a render method. * - `log` - boolean - Should Exceptions be logged? + * - `skipLog` - array - list of exceptions to skip for logging. Exceptions that + * extend one of the listed exceptions will also be skipped for logging. + * Example: `'skipLog' => array('NotFoundException', 'UnauthorizedException')` * * @see ErrorHandler for more information on exception handling and configuration. */ @@ -105,6 +99,33 @@ */ //Configure::write('App.baseUrl', env('SCRIPT_NAME')); +/** + * To configure CakePHP to use a particular domain URL + * for any URL generation inside the application, set the following + * configuration variable to the http(s) address to your domain. This + * will override the automatic detection of full base URL and can be + * useful when generating links from the CLI (e.g. sending emails) + */ + //Configure::write('App.fullBaseUrl', 'http://example.com'); + +/** + * Web path to the public images directory under webroot. + * If not set defaults to 'img/' + */ + //Configure::write('App.imageBaseUrl', 'img/'); + +/** + * Web path to the CSS files directory under webroot. + * If not set defaults to 'css/' + */ + //Configure::write('App.cssBaseUrl', 'css/'); + +/** + * Web path to the js files directory under webroot. + * If not set defaults to 'js/' + */ + //Configure::write('App.jsBaseUrl', 'js/'); + /** * Uncomment the define below to use CakePHP prefix routes. * diff --git a/lib/Cake/Console/Templates/skel/Config/database.php.default b/lib/Cake/Console/Templates/skel/Config/database.php.default index 68493edec..a124e9ddd 100644 --- a/lib/Cake/Console/Templates/skel/Config/database.php.default +++ b/lib/Cake/Console/Templates/skel/Config/database.php.default @@ -6,18 +6,9 @@ * * PHP 5 * - * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) - * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) - * - * Licensed under The MIT License - * For full copyright and license information, please see the LICENSE.txt - * Redistributions of files must retain the above copyright notice. - * - * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) * @link http://cakephp.org CakePHP(tm) Project * @package app.Config * @since CakePHP(tm) v 0.2.9 - * @license http://www.opensource.org/licenses/mit-license.php MIT License */ /** @@ -25,7 +16,7 @@ * You can specify multiple configurations for production, development and testing. * * datasource => The name of a supported datasource; valid options are as follows: - * Database/Mysql - MySQL 4 & 5, + * Database/Mysql - MySQL 4 & 5, * Database/Sqlite - SQLite (PHP5 only), * Database/Postgres - PostgreSQL 7 and higher, * Database/Sqlserver - Microsoft SQL Server 2005 and higher diff --git a/lib/Cake/Console/Templates/skel/Config/email.php.default b/lib/Cake/Console/Templates/skel/Config/email.php.default index a8d5ea990..749ac2bdf 100644 --- a/lib/Cake/Console/Templates/skel/Config/email.php.default +++ b/lib/Cake/Console/Templates/skel/Config/email.php.default @@ -6,18 +6,9 @@ * * PHP 5 * - * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) - * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) - * - * Licensed under The MIT License - * For full copyright and license information, please see the LICENSE.txt - * Redistributions of files must retain the above copyright notice. - * - * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) * @link http://cakephp.org CakePHP(tm) Project * @package app.Config * @since CakePHP(tm) v 2.0.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License */ /** @@ -25,7 +16,7 @@ * You can specify multiple configurations for production, development and testing. * * transport => The name of a supported transport; valid options are as follows: - * Mail - Send using PHP mail function + * Mail - Send using PHP mail function * Smtp - Send using SMTP * Debug - Do not send the email, just return the result * diff --git a/lib/Cake/Console/Templates/skel/Config/routes.php b/lib/Cake/Console/Templates/skel/Config/routes.php index c72f166a1..d42ac984e 100644 --- a/lib/Cake/Console/Templates/skel/Config/routes.php +++ b/lib/Cake/Console/Templates/skel/Config/routes.php @@ -8,18 +8,9 @@ * * PHP 5 * - * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) - * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) - * - * Licensed under The MIT License - * For full copyright and license information, please see the LICENSE.txt - * Redistributions of files must retain the above copyright notice. - * - * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) * @link http://cakephp.org CakePHP(tm) Project * @package app.Config * @since CakePHP(tm) v 0.2.9 - * @license http://www.opensource.org/licenses/mit-license.php MIT License */ /** diff --git a/lib/Cake/Console/Templates/skel/Console/Command/AppShell.php b/lib/Cake/Console/Templates/skel/Console/Command/AppShell.php index 4196280a2..ac6ae94c3 100644 --- a/lib/Cake/Console/Templates/skel/Console/Command/AppShell.php +++ b/lib/Cake/Console/Templates/skel/Console/Command/AppShell.php @@ -4,17 +4,8 @@ * * PHP 5 * - * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) - * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) - * - * Licensed under The MIT License - * For full copyright and license information, please see the LICENSE.txt - * Redistributions of files must retain the above copyright notice. - * - * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) * @link http://cakephp.org CakePHP(tm) Project * @since CakePHP(tm) v 2.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License */ App::uses('Shell', 'Console'); diff --git a/lib/Cake/Console/Templates/skel/Console/cake.bat b/lib/Cake/Console/Templates/skel/Console/cake.bat index 9d6428c23..eea16747e 100644 --- a/lib/Cake/Console/Templates/skel/Console/cake.bat +++ b/lib/Cake/Console/Templates/skel/Console/cake.bat @@ -13,7 +13,6 @@ :: @link http://cakephp.org CakePHP(tm) Project :: @package app.Console :: @since CakePHP(tm) v 2.0 -:: @license http://www.opensource.org/licenses/mit-license.php MIT License :: :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: diff --git a/lib/Cake/Console/Templates/skel/Console/cake.php b/lib/Cake/Console/Templates/skel/Console/cake.php index 709a7c29c..dc48cb9e9 100644 --- a/lib/Cake/Console/Templates/skel/Console/cake.php +++ b/lib/Cake/Console/Templates/skel/Console/cake.php @@ -16,7 +16,6 @@ * @link http://cakephp.org CakePHP(tm) Project * @package app.Console * @since CakePHP(tm) v 2.0 - * @license http://www.opensource.org/licenses/mit-license.php MIT License */ $ds = DIRECTORY_SEPARATOR; diff --git a/lib/Cake/Console/Templates/skel/Controller/AppController.php b/lib/Cake/Console/Templates/skel/Controller/AppController.php index c1999b330..a2f9ca82d 100644 --- a/lib/Cake/Console/Templates/skel/Controller/AppController.php +++ b/lib/Cake/Console/Templates/skel/Controller/AppController.php @@ -7,18 +7,9 @@ * * PHP 5 * - * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) - * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) - * - * Licensed under The MIT License - * For full copyright and license information, please see the LICENSE.txt - * Redistributions of files must retain the above copyright notice. - * - * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) * @link http://cakephp.org CakePHP(tm) Project * @package app.Controller * @since CakePHP(tm) v 0.2.9 - * @license http://www.opensource.org/licenses/mit-license.php MIT License */ App::uses('Controller', 'Controller'); diff --git a/lib/Cake/Console/Templates/skel/Controller/PagesController.php b/lib/Cake/Console/Templates/skel/Controller/PagesController.php index 7b4e45a8f..e55902c7f 100644 --- a/lib/Cake/Console/Templates/skel/Controller/PagesController.php +++ b/lib/Cake/Console/Templates/skel/Controller/PagesController.php @@ -6,18 +6,9 @@ * * PHP 5 * - * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) - * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) - * - * Licensed under The MIT License - * For full copyright and license information, please see the LICENSE.txt - * Redistributions of files must retain the above copyright notice. - * - * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) * @link http://cakephp.org CakePHP(tm) Project * @package app.Controller * @since CakePHP(tm) v 0.2.9 - * @license http://www.opensource.org/licenses/mit-license.php MIT License */ App::uses('AppController', 'Controller'); @@ -32,13 +23,6 @@ App::uses('AppController', 'Controller'); */ class PagesController extends AppController { -/** - * Controller name - * - * @var string - */ - public $name = 'Pages'; - /** * This controller does not use a model * @@ -51,6 +35,8 @@ class PagesController extends AppController { * * @param mixed What page to display * @return void + * @throws NotFoundException When the view file could not be found + * or MissingViewException in debug mode. */ public function display() { $path = func_get_args(); @@ -71,6 +57,14 @@ class PagesController extends AppController { $title_for_layout = Inflector::humanize($path[$count - 1]); } $this->set(compact('page', 'subpage', 'title_for_layout')); - $this->render(implode('/', $path)); + + try { + $this->render(implode('/', $path)); + } catch (MissingViewException $e) { + if (Configure::read('debug')) { + throw $e; + } + throw new NotFoundException(); + } } } diff --git a/lib/Cake/Console/Templates/skel/Model/AppModel.php b/lib/Cake/Console/Templates/skel/Model/AppModel.php index c47d72922..e1beb3844 100644 --- a/lib/Cake/Console/Templates/skel/Model/AppModel.php +++ b/lib/Cake/Console/Templates/skel/Model/AppModel.php @@ -7,18 +7,9 @@ * * PHP 5 * - * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) - * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) - * - * Licensed under The MIT License - * For full copyright and license information, please see the LICENSE.txt - * Redistributions of files must retain the above copyright notice. - * - * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) * @link http://cakephp.org CakePHP(tm) Project * @package app.Model * @since CakePHP(tm) v 0.2.9 - * @license http://www.opensource.org/licenses/mit-license.php MIT License */ App::uses('Model', 'Model'); diff --git a/lib/Cake/Console/Templates/skel/View/Errors/error400.ctp b/lib/Cake/Console/Templates/skel/View/Errors/error400.ctp index fc0633dbf..34ba7bd70 100644 --- a/lib/Cake/Console/Templates/skel/View/Errors/error400.ctp +++ b/lib/Cake/Console/Templates/skel/View/Errors/error400.ctp @@ -3,18 +3,9 @@ * * PHP 5 * - * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) - * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) - * - * Licensed under The MIT License - * For full copyright and license information, please see the LICENSE.txt - * Redistributions of files must retain the above copyright notice. - * - * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) * @link http://cakephp.org CakePHP(tm) Project * @package app.View.Errors * @since CakePHP(tm) v 0.10.0.1076 - * @license http://www.opensource.org/licenses/mit-license.php MIT License */ ?>

diff --git a/lib/Cake/Console/Templates/skel/View/Errors/error500.ctp b/lib/Cake/Console/Templates/skel/View/Errors/error500.ctp index 32914c582..9d0161d2e 100644 --- a/lib/Cake/Console/Templates/skel/View/Errors/error500.ctp +++ b/lib/Cake/Console/Templates/skel/View/Errors/error500.ctp @@ -3,18 +3,9 @@ * * PHP 5 * - * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) - * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) - * - * Licensed under The MIT License - * For full copyright and license information, please see the LICENSE.txt - * Redistributions of files must retain the above copyright notice. - * - * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) * @link http://cakephp.org CakePHP(tm) Project * @package app.View.Errors * @since CakePHP(tm) v 0.10.0.1076 - * @license http://www.opensource.org/licenses/mit-license.php MIT License */ ?>

diff --git a/lib/Cake/Console/Templates/skel/View/Helper/AppHelper.php b/lib/Cake/Console/Templates/skel/View/Helper/AppHelper.php index 43b9f560b..7b7342471 100644 --- a/lib/Cake/Console/Templates/skel/View/Helper/AppHelper.php +++ b/lib/Cake/Console/Templates/skel/View/Helper/AppHelper.php @@ -7,18 +7,9 @@ * * PHP 5 * - * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) - * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) - * - * Licensed under The MIT License - * For full copyright and license information, please see the LICENSE.txt - * Redistributions of files must retain the above copyright notice. - * - * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) * @link http://cakephp.org CakePHP(tm) Project * @package app.View.Helper * @since CakePHP(tm) v 0.2.9 - * @license http://www.opensource.org/licenses/mit-license.php MIT License */ App::uses('Helper', 'View'); diff --git a/lib/Cake/Console/Templates/skel/View/Layouts/Emails/html/default.ctp b/lib/Cake/Console/Templates/skel/View/Layouts/Emails/html/default.ctp index 212fbb612..b872750a3 100644 --- a/lib/Cake/Console/Templates/skel/View/Layouts/Emails/html/default.ctp +++ b/lib/Cake/Console/Templates/skel/View/Layouts/Emails/html/default.ctp @@ -3,18 +3,9 @@ * * PHP 5 * - * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) - * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) - * - * Licensed under The MIT License - * For full copyright and license information, please see the LICENSE.txt - * Redistributions of files must retain the above copyright notice. - * - * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) * @link http://cakephp.org CakePHP(tm) Project * @package app.View.Layouts.Email.html * @since CakePHP(tm) v 0.10.0.1076 - * @license http://www.opensource.org/licenses/mit-license.php MIT License */ ?> diff --git a/lib/Cake/Console/Templates/skel/View/Layouts/ajax.ctp b/lib/Cake/Console/Templates/skel/View/Layouts/ajax.ctp index 5850cbc9e..8b06e4dce 100644 --- a/lib/Cake/Console/Templates/skel/View/Layouts/ajax.ctp +++ b/lib/Cake/Console/Templates/skel/View/Layouts/ajax.ctp @@ -3,18 +3,9 @@ * * PHP 5 * - * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) - * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) - * - * Licensed under The MIT License - * For full copyright and license information, please see the LICENSE.txt - * Redistributions of files must retain the above copyright notice. - * - * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) * @link http://cakephp.org CakePHP(tm) Project * @package app.View.Layouts * @since CakePHP(tm) v 0.10.0.1076 - * @license http://www.opensource.org/licenses/mit-license.php MIT License */ ?> fetch('content'); ?> diff --git a/lib/Cake/Console/Templates/skel/View/Layouts/default.ctp b/lib/Cake/Console/Templates/skel/View/Layouts/default.ctp index c612f5c7b..98a67ab1c 100644 --- a/lib/Cake/Console/Templates/skel/View/Layouts/default.ctp +++ b/lib/Cake/Console/Templates/skel/View/Layouts/default.ctp @@ -3,18 +3,9 @@ * * PHP 5 * - * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) - * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) - * - * Licensed under The MIT License - * For full copyright and license information, please see the LICENSE.txt - * Redistributions of files must retain the above copyright notice. - * - * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) * @link http://cakephp.org CakePHP(tm) Project * @package app.View.Layouts * @since CakePHP(tm) v 0.10.0.1076 - * @license http://www.opensource.org/licenses/mit-license.php MIT License */ $cakeDescription = __d('cake_dev', 'CakePHP: the rapid development php framework'); diff --git a/lib/Cake/Console/Templates/skel/View/Layouts/error.ctp b/lib/Cake/Console/Templates/skel/View/Layouts/error.ctp index c612f5c7b..98a67ab1c 100644 --- a/lib/Cake/Console/Templates/skel/View/Layouts/error.ctp +++ b/lib/Cake/Console/Templates/skel/View/Layouts/error.ctp @@ -3,18 +3,9 @@ * * PHP 5 * - * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) - * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) - * - * Licensed under The MIT License - * For full copyright and license information, please see the LICENSE.txt - * Redistributions of files must retain the above copyright notice. - * - * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) * @link http://cakephp.org CakePHP(tm) Project * @package app.View.Layouts * @since CakePHP(tm) v 0.10.0.1076 - * @license http://www.opensource.org/licenses/mit-license.php MIT License */ $cakeDescription = __d('cake_dev', 'CakePHP: the rapid development php framework'); diff --git a/lib/Cake/Console/Templates/skel/View/Layouts/flash.ctp b/lib/Cake/Console/Templates/skel/View/Layouts/flash.ctp index dd92d5674..72047d40f 100644 --- a/lib/Cake/Console/Templates/skel/View/Layouts/flash.ctp +++ b/lib/Cake/Console/Templates/skel/View/Layouts/flash.ctp @@ -3,18 +3,9 @@ * * PHP 5 * - * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) - * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) - * - * Licensed under The MIT License - * For full copyright and license information, please see the LICENSE.txt - * Redistributions of files must retain the above copyright notice. - * - * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) * @link http://cakephp.org CakePHP(tm) Project * @package app.View.Layouts * @since CakePHP(tm) v 0.10.0.1076 - * @license http://www.opensource.org/licenses/mit-license.php MIT License */ ?> diff --git a/lib/Cake/Console/Templates/skel/View/Pages/home.ctp b/lib/Cake/Console/Templates/skel/View/Pages/home.ctp index f1e66c337..6374131f6 100644 --- a/lib/Cake/Console/Templates/skel/View/Pages/home.ctp +++ b/lib/Cake/Console/Templates/skel/View/Pages/home.ctp @@ -3,18 +3,9 @@ * * PHP 5 * - * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) - * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) - * - * Licensed under The MIT License - * For full copyright and license information, please see the LICENSE.txt - * Redistributions of files must retain the above copyright notice. - * - * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) * @link http://cakephp.org CakePHP(tm) Project * @package app.View.Pages * @since CakePHP(tm) v 0.10.0.1076 - * @license http://www.opensource.org/licenses/mit-license.php MIT License */ if (!Configure::read('debug')): @@ -68,11 +59,11 @@ endif; $settings = Cache::settings(); if (!empty($settings)): echo ''; - echo __d('cake_dev', 'The %s is being used for core caching. To change the config edit APP/Config/core.php ', ''. $settings['engine'] . 'Engine'); + echo __d('cake_dev', 'The %s is being used for core caching. To change the config edit %s', ''. $settings['engine'] . 'Engine', 'APP/Config/core.php'); echo ''; else: echo ''; - echo __d('cake_dev', 'Your cache is NOT working. Please check the settings in APP/Config/core.php'); + echo __d('cake_dev', 'Your cache is NOT working. Please check the settings in %s', 'APP/Config/core.php'); echo ''; endif; ?> @@ -89,7 +80,7 @@ endif; echo ''; echo __d('cake_dev', 'Your database configuration file is NOT present.'); echo '
'; - echo __d('cake_dev', 'Rename APP/Config/database.php.default to APP/Config/database.php'); + echo __d('cake_dev', 'Rename %s to %s', 'APP/Config/database.php.default', 'APP/Config/database.php'); echo '
'; endif; ?> @@ -132,7 +123,7 @@ if (isset($filePresent)): echo '

'; echo __d('cake_dev', 'PCRE has not been compiled with Unicode support.'); echo '
'; - echo __d('cake_dev', 'Recompile PCRE with Unicode support by adding --enable-unicode-properties when configuring'); + echo __d('cake_dev', 'Recompile PCRE with Unicode support by adding %s when configuring', '--enable-unicode-properties'); echo '

'; } ?> @@ -156,9 +147,10 @@ if (isset($filePresent)):

-To change its layout, edit: APP/View/Layouts/default.ctp.
-You can also add some CSS styles for your pages at: APP/webroot/css.'); +echo __d('cake_dev', 'To change the content of this page, edit: %s.
+To change its layout, edit: %s.
+You can also add some CSS styles for your pages at: %s.', + 'APP/View/Pages/home.ctp', 'APP/View/Layouts/default.ctp', 'APP/webroot/css'); ?>

diff --git a/lib/Cake/Console/Templates/skel/index.php b/lib/Cake/Console/Templates/skel/index.php index 8b4c13b60..b598dbbe4 100644 --- a/lib/Cake/Console/Templates/skel/index.php +++ b/lib/Cake/Console/Templates/skel/index.php @@ -2,18 +2,9 @@ /** * PHP 5 * - * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) - * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) - * - * Licensed under The MIT License - * For full copyright and license information, please see the LICENSE.txt - * Redistributions of files must retain the above copyright notice. - * - * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) * @link http://cakephp.org CakePHP(tm) Project * @package app * @since CakePHP(tm) v 0.10.0.1076 - * @license http://www.opensource.org/licenses/mit-license.php MIT License */ require 'webroot' . DIRECTORY_SEPARATOR . 'index.php'; diff --git a/lib/Cake/Console/Templates/skel/webroot/.htaccess b/lib/Cake/Console/Templates/skel/webroot/.htaccess index 48a63f014..1f19e4c06 100644 --- a/lib/Cake/Console/Templates/skel/webroot/.htaccess +++ b/lib/Cake/Console/Templates/skel/webroot/.htaccess @@ -2,5 +2,5 @@ RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f - RewriteRule ^(.*)$ index.php [QSA,L] + RewriteRule ^ index.php [L] diff --git a/lib/Cake/Console/Templates/skel/webroot/index.php b/lib/Cake/Console/Templates/skel/webroot/index.php index be88b567d..c17e95625 100644 --- a/lib/Cake/Console/Templates/skel/webroot/index.php +++ b/lib/Cake/Console/Templates/skel/webroot/index.php @@ -6,18 +6,9 @@ * * PHP 5 * - * CakePHP(tm) : Rapid Development Framework (http://cakephp.org) - * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) - * - * Licensed under The MIT License - * For full copyright and license information, please see the LICENSE.txt - * Redistributions of files must retain the above copyright notice. - * - * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) * @link http://cakephp.org CakePHP(tm) Project * @package app.webroot * @since CakePHP(tm) v 0.2.9 - * @license http://www.opensource.org/licenses/mit-license.php MIT License */ /** diff --git a/lib/Cake/Console/Templates/skel/webroot/test.php b/lib/Cake/Console/Templates/skel/webroot/test.php index 531292be8..c158fc4d6 100644 --- a/lib/Cake/Console/Templates/skel/webroot/test.php +++ b/lib/Cake/Console/Templates/skel/webroot/test.php @@ -4,18 +4,9 @@ * * PHP 5 * - * CakePHP(tm) Tests - * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) - * - * Licensed under The MIT License - * For full copyright and license information, please see the LICENSE.txt - * Redistributions of files must retain the above copyright notice - * - * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) * @link http://book.cakephp.org/2.0/en/development/testing.html * @package app.webroot * @since CakePHP(tm) v 1.2.0.4433 - * @license http://www.opensource.org/licenses/mit-license.php MIT License */ set_time_limit(0); diff --git a/lib/Cake/Controller/CakeErrorController.php b/lib/Cake/Controller/CakeErrorController.php index aa065f2e4..1755f3741 100644 --- a/lib/Cake/Controller/CakeErrorController.php +++ b/lib/Cake/Controller/CakeErrorController.php @@ -31,13 +31,6 @@ App::uses('AppController', 'Controller'); */ class CakeErrorController extends AppController { -/** - * Controller name - * - * @var string - */ - public $name = 'CakeError'; - /** * Uses Property * diff --git a/lib/Cake/Controller/Component/AclComponent.php b/lib/Cake/Controller/Component/AclComponent.php index c15b0ad16..6d0f3b8b4 100644 --- a/lib/Cake/Controller/Component/AclComponent.php +++ b/lib/Cake/Controller/Component/AclComponent.php @@ -156,10 +156,10 @@ class AclComponent extends Component { * @param array|string|Model $aco ACO The controlled object identifier. See `AclNode::node()` for possible formats * @param string $action Action (defaults to *) * @return boolean Success - * @deprecated + * @deprecated Will be removed in 3.0. */ public function grant($aro, $aco, $action = "*") { - trigger_error(__d('cake_dev', 'AclComponent::grant() is deprecated, use allow() instead'), E_USER_WARNING); + trigger_error(__d('cake_dev', '%s is deprecated, use %s instead', 'AclComponent::grant()', 'allow()'), E_USER_WARNING); return $this->_Instance->allow($aro, $aco, $action); } @@ -170,10 +170,10 @@ class AclComponent extends Component { * @param array|string|Model $aco ACO The controlled object identifier. See `AclNode::node()` for possible formats * @param string $action Action (defaults to *) * @return boolean Success - * @deprecated + * @deprecated Will be removed in 3.0. */ public function revoke($aro, $aco, $action = "*") { - trigger_error(__d('cake_dev', 'AclComponent::revoke() is deprecated, use deny() instead'), E_USER_WARNING); + trigger_error(__d('cake_dev', '%s is deprecated, use %s instead', 'AclComponent::revoke()', 'deny()'), E_USER_WARNING); return $this->_Instance->deny($aro, $aco, $action); } diff --git a/lib/Cake/Controller/Component/Auth/AbstractPasswordHasher.php b/lib/Cake/Controller/Component/Auth/AbstractPasswordHasher.php new file mode 100644 index 000000000..a590a4546 --- /dev/null +++ b/lib/Cake/Controller/Component/Auth/AbstractPasswordHasher.php @@ -0,0 +1,74 @@ +config($config); + } + +/** + * Get/Set the config + * + * @param array $config Sets config, if null returns existing config + * @return array Returns configs + */ + public function config($config = null) { + if (is_array($config)) { + $this->_config = array_merge($this->_config, $config); + } + return $this->_config; + } + +/** + * Generates password hash. + * + * @param string|array $password Plain text password to hash or array of data + * required to generate password hash. + * @return string Password hash + */ + abstract public function hash($password); + +/** + * Check hash. Generate hash from user provided password string or data array + * and check against existing hash. + * + * @param string|array $password Plain text password to hash or data array. + * @param string Existing hashed password. + * @return boolean True if hashes match else false. + */ + abstract public function check($password, $hashedPassword); + +} diff --git a/lib/Cake/Controller/Component/Auth/BaseAuthenticate.php b/lib/Cake/Controller/Component/Auth/BaseAuthenticate.php index 9467dfb44..423115fb6 100644 --- a/lib/Cake/Controller/Component/Auth/BaseAuthenticate.php +++ b/lib/Cake/Controller/Component/Auth/BaseAuthenticate.php @@ -33,6 +33,9 @@ abstract class BaseAuthenticate { * i.e. `array('User.is_active' => 1).` * - `recursive` The value of the recursive key passed to find(). Defaults to 0. * - `contain` Extra models to contain and store in session. + * - `passwordHasher` Password hasher class. Can be a string specifying class name + * or an array containing `className` key, any other keys will be passed as + * settings to the class. Defaults to 'Simple'. * * @var array */ @@ -45,6 +48,7 @@ abstract class BaseAuthenticate { 'scope' => array(), 'recursive' => 0, 'contain' => null, + 'passwordHasher' => 'Simple' ); /** @@ -54,6 +58,13 @@ abstract class BaseAuthenticate { */ protected $_Collection; +/** + * Password hasher instance. + * + * @var AbstractPasswordHasher + */ + protected $_passwordHasher; + /** * Constructor * @@ -68,56 +79,96 @@ abstract class BaseAuthenticate { /** * Find a user record using the standard options. * - * The $conditions parameter can be a (string)username or an array containing conditions for Model::find('first'). If - * the password field is not included in the conditions the password will be returned. + * The $username parameter can be a (string)username or an array containing + * conditions for Model::find('first'). If the $password param is not provided + * the password field will be present in returned array. * - * @param Mixed $conditions The username/identifier, or an array of find conditions. - * @param Mixed $password The password, only use if passing as $conditions = 'username'. - * @return Mixed Either false on failure, or an array of user data. + * Input passwords will be hashed even when a user doesn't exist. This + * helps mitigate timing attacks that are attempting to find valid usernames. + * + * @param string|array $username The username/identifier, or an array of find conditions. + * @param string $password The password, only used if $username param is string. + * @return boolean|array Either false on failure, or an array of user data. */ - protected function _findUser($conditions, $password = null) { + protected function _findUser($username, $password = null) { $userModel = $this->settings['userModel']; list(, $model) = pluginSplit($userModel); $fields = $this->settings['fields']; - if (!is_array($conditions)) { - if (!$password) { - return false; - } - $username = $conditions; + if (is_array($username)) { + $conditions = $username; + } else { $conditions = array( - $model . '.' . $fields['username'] => $username, - $model . '.' . $fields['password'] => $this->_password($password), + $model . '.' . $fields['username'] => $username ); } + if (!empty($this->settings['scope'])) { $conditions = array_merge($conditions, $this->settings['scope']); } + $result = ClassRegistry::init($userModel)->find('first', array( 'conditions' => $conditions, 'recursive' => $this->settings['recursive'], 'contain' => $this->settings['contain'], )); - if (empty($result) || empty($result[$model])) { + if (empty($result[$model])) { + $this->passwordHasher()->hash($password); return false; } + $user = $result[$model]; - if ( - isset($conditions[$model . '.' . $fields['password']]) || - isset($conditions[$fields['password']]) - ) { + if ($password) { + if (!$this->passwordHasher()->check($password, $user[$fields['password']])) { + return false; + } unset($user[$fields['password']]); } + unset($result[$model]); return array_merge($user, $result); } +/** + * Return password hasher object + * + * @return AbstractPasswordHasher Password hasher instance + * @throws CakeException If password hasher class not found or + * it does not extend AbstractPasswordHasher + */ + public function passwordHasher() { + if ($this->_passwordHasher) { + return $this->_passwordHasher; + } + + $config = array(); + if (is_string($this->settings['passwordHasher'])) { + $class = $this->settings['passwordHasher']; + } else { + $class = $this->settings['passwordHasher']['className']; + $config = $this->settings['passwordHasher']; + unset($config['className']); + } + list($plugin, $class) = pluginSplit($class, true); + $className = $class . 'PasswordHasher'; + App::uses($className, $plugin . 'Controller/Component/Auth'); + if (!class_exists($className)) { + throw new CakeException(__d('cake_dev', 'Password hasher class "%s" was not found.', $class)); + } + if (!is_subclass_of($className, 'AbstractPasswordHasher')) { + throw new CakeException(__d('cake_dev', 'Password hasher must extend AbstractPasswordHasher class.')); + } + $this->_passwordHasher = new $className($config); + return $this->_passwordHasher; + } + /** * Hash the plain text password so that it matches the hashed/encrypted password * in the datasource. * * @param string $password The plain text password. * @return string The hashed form of the password. + * @deprecated Since 2.4. Use a PasswordHasher class instead. */ protected function _password($password) { return Security::hash($password, null, true); @@ -156,4 +207,15 @@ abstract class BaseAuthenticate { return false; } +/** + * Handle unauthenticated access attempt. + * + * @param CakeRequest $request A request object. + * @param CakeResponse $response A response object. + * @return mixed Either true to indicate the unauthenticated request has been + * dealt with and no more action is required by AuthComponent or void (default). + */ + public function unauthenticated(CakeRequest $request, CakeResponse $response) { + } + } diff --git a/lib/Cake/Controller/Component/Auth/BasicAuthenticate.php b/lib/Cake/Controller/Component/Auth/BasicAuthenticate.php index 18aa2f643..b4d868f34 100644 --- a/lib/Cake/Controller/Component/Auth/BasicAuthenticate.php +++ b/lib/Cake/Controller/Component/Auth/BasicAuthenticate.php @@ -43,31 +43,6 @@ App::uses('BaseAuthenticate', 'Controller/Component/Auth'); */ class BasicAuthenticate extends BaseAuthenticate { -/** - * Settings for this object. - * - * - `fields` The fields to use to identify a user by. - * - `userModel` The model name of the User, defaults to User. - * - `scope` Additional conditions to use when looking up and authenticating users, - * i.e. `array('User.is_active' => 1).` - * - `recursive` The value of the recursive key passed to find(). Defaults to 0. - * - `contain` Extra models to contain and store in session. - * - `realm` The realm authentication is for. Defaults the server name. - * - * @var array - */ - public $settings = array( - 'fields' => array( - 'username' => 'username', - 'password' => 'password' - ), - 'userModel' => 'User', - 'scope' => array(), - 'recursive' => 0, - 'contain' => null, - 'realm' => '', - ); - /** * Constructor, completes configuration for basic authentication. * @@ -82,23 +57,15 @@ class BasicAuthenticate extends BaseAuthenticate { } /** - * Authenticate a user using basic HTTP auth. Will use the configured User model and attempt a - * login using basic HTTP auth. + * Authenticate a user using HTTP auth. Will use the configured User model and attempt a + * login using HTTP auth. * * @param CakeRequest $request The request to authenticate with. * @param CakeResponse $response The response to add headers to. * @return mixed Either false on failure, or an array of user data on success. */ public function authenticate(CakeRequest $request, CakeResponse $response) { - $result = $this->getUser($request); - - if (empty($result)) { - $response->header($this->loginHeaders()); - $response->statusCode(401); - $response->send(); - return false; - } - return $result; + return $this->getUser($request); } /** @@ -117,6 +84,20 @@ class BasicAuthenticate extends BaseAuthenticate { return $this->_findUser($username, $pass); } +/** + * Handles an unauthenticated access attempt by sending appropriate login headers + * + * @param CakeRequest $request A request object. + * @param CakeResponse $response A response object. + * @return void + * @throws UnauthorizedException + */ + public function unauthenticated(CakeRequest $request, CakeResponse $response) { + $Exception = new UnauthorizedException(); + $Exception->responseHeader(array($this->loginHeaders())); + throw $Exception; + } + /** * Generate the login headers * diff --git a/lib/Cake/Controller/Component/Auth/BlowfishAuthenticate.php b/lib/Cake/Controller/Component/Auth/BlowfishAuthenticate.php index 022be62b3..9fabf99af 100644 --- a/lib/Cake/Controller/Component/Auth/BlowfishAuthenticate.php +++ b/lib/Cake/Controller/Component/Auth/BlowfishAuthenticate.php @@ -34,46 +34,22 @@ App::uses('FormAuthenticate', 'Controller/Component/Auth'); * For initial password hashing/creation see Security::hash(). Other than how the password is initially hashed, * BlowfishAuthenticate works exactly the same way as FormAuthenticate. * - * @package Cake.Controller.Component.Auth + * @package Cake.Controller.Component.Auth * @since CakePHP(tm) v 2.3 - * @see AuthComponent::$authenticate + * @see AuthComponent::$authenticate + * @deprecated Since 2.4. Just use FormAuthenticate with 'passwordHasher' setting set to 'Blowfish' */ class BlowfishAuthenticate extends FormAuthenticate { /** - * Authenticates the identity contained in a request. Will use the `settings.userModel`, and `settings.fields` - * to find POST data that is used to find a matching record in the`settings.userModel`. Will return false if - * there is no post data, either username or password is missing, or if the scope conditions have not been met. + * Constructor. Sets default passwordHasher to Blowfish * - * @param CakeRequest $request The request that contains login information. - * @param CakeResponse $response Unused response object. - * @return mixed False on login failure. An array of User data on success. + * @param ComponentCollection $collection The Component collection used on this request. + * @param array $settings Array of settings to use. */ - public function authenticate(CakeRequest $request, CakeResponse $response) { - $userModel = $this->settings['userModel']; - list(, $model) = pluginSplit($userModel); - - $fields = $this->settings['fields']; - if (!$this->_checkFields($request, $model, $fields)) { - return false; - } - $user = $this->_findUser( - array( - $model . '.' . $fields['username'] => $request->data[$model][$fields['username']], - ) - ); - if (!$user) { - return false; - } - $password = Security::hash( - $request->data[$model][$fields['password']], - 'blowfish', - $user[$fields['password']] - ); - if ($password === $user[$fields['password']]) { - unset($user[$fields['password']]); - return $user; - } - return false; + public function __construct(ComponentCollection $collection, $settings) { + $this->settings['passwordHasher'] = 'Blowfish'; + parent::__construct($collection, $settings); } + } diff --git a/lib/Cake/Controller/Component/Auth/BlowfishPasswordHasher.php b/lib/Cake/Controller/Component/Auth/BlowfishPasswordHasher.php new file mode 100644 index 000000000..e5c071088 --- /dev/null +++ b/lib/Cake/Controller/Component/Auth/BlowfishPasswordHasher.php @@ -0,0 +1,48 @@ + '', 'qop' => 'auth', 'nonce' => '', - 'opaque' => '' + 'opaque' => '', + 'passwordHasher' => 'Simple', ); /** @@ -97,9 +98,6 @@ class DigestAuthenticate extends BaseAuthenticate { */ public function __construct(ComponentCollection $collection, $settings) { parent::__construct($collection, $settings); - if (empty($this->settings['realm'])) { - $this->settings['realm'] = env('SERVER_NAME'); - } if (empty($this->settings['nonce'])) { $this->settings['nonce'] = uniqid(''); } @@ -108,26 +106,6 @@ class DigestAuthenticate extends BaseAuthenticate { } } -/** - * Authenticate a user using Digest HTTP auth. Will use the configured User model and attempt a - * login using Digest HTTP auth. - * - * @param CakeRequest $request The request to authenticate with. - * @param CakeResponse $response The response to add headers to. - * @return mixed Either false on failure, or an array of user data on success. - */ - public function authenticate(CakeRequest $request, CakeResponse $response) { - $user = $this->getUser($request); - - if (empty($user)) { - $response->header($this->loginHeaders()); - $response->statusCode(401); - $response->send(); - return false; - } - return $user; - } - /** * Get a user based on information in the request. Used by cookie-less auth for stateless clients. * @@ -139,7 +117,11 @@ class DigestAuthenticate extends BaseAuthenticate { if (empty($digest)) { return false; } - $user = $this->_findUser($digest['username']); + + list(, $model) = pluginSplit($this->settings['userModel']); + $user = $this->_findUser(array( + $model . '.' . $this->settings['fields']['username'] => $digest['username'] + )); if (empty($user)) { return false; } @@ -151,34 +133,6 @@ class DigestAuthenticate extends BaseAuthenticate { return false; } -/** - * Find a user record using the standard options. - * - * @param string $username The username/identifier. - * @param string $password Unused password, digest doesn't require passwords. - * @return Mixed Either false on failure, or an array of user data. - */ - protected function _findUser($username, $password = null) { - $userModel = $this->settings['userModel']; - list(, $model) = pluginSplit($userModel); - $fields = $this->settings['fields']; - - $conditions = array( - $model . '.' . $fields['username'] => $username, - ); - if (!empty($this->settings['scope'])) { - $conditions = array_merge($conditions, $this->settings['scope']); - } - $result = ClassRegistry::init($userModel)->find('first', array( - 'conditions' => $conditions, - 'recursive' => $this->settings['recursive'] - )); - if (empty($result) || empty($result[$model])) { - return false; - } - return $result[$model]; - } - /** * Gets the digest headers from the request/environment. * diff --git a/lib/Cake/Controller/Component/Auth/SimplePasswordHasher.php b/lib/Cake/Controller/Component/Auth/SimplePasswordHasher.php new file mode 100644 index 000000000..4877c0284 --- /dev/null +++ b/lib/Cake/Controller/Component/Auth/SimplePasswordHasher.php @@ -0,0 +1,55 @@ + null); + +/** + * Generates password hash. + * + * @param string $password Plain text password to hash. + * @return string Password hash + */ + public function hash($password) { + return Security::hash($password, $this->_config['hashType'], true); + } + +/** + * Check hash. Generate hash for user provided password and check against existing hash. + * + * @param string $password Plain text password to hash. + * @param string Existing hashed password. + * @return boolean True if hashes match else false. + */ + public function check($password, $hashedPassword) { + return $hashedPassword === $this->hash($password); + } + +} diff --git a/lib/Cake/Controller/Component/AuthComponent.php b/lib/Cake/Controller/Component/AuthComponent.php index 50e264f68..6d8e92b59 100644 --- a/lib/Cake/Controller/Component/AuthComponent.php +++ b/lib/Cake/Controller/Component/AuthComponent.php @@ -157,8 +157,9 @@ class AuthComponent extends Component { ); /** - * The session key name where the record of the current user is stored. If - * unspecified, it will be "Auth.User". + * The session key name where the record of the current user is stored. Default + * key is "Auth.User". If you are using only stateless authenticators set this + * to false to ensure session is not started. * * @var string */ @@ -188,7 +189,7 @@ class AuthComponent extends Component { * Normally, if a user is redirected to the $loginAction page, the location they * were redirected from will be stored in the session so that they can be * redirected back after a successful login. If this session value is not - * set, the user will be redirected to the page specified in $loginRedirect. + * set, redirectUrl() method will return the url specified in $loginRedirect. * * @var mixed * @link http://book.cakephp.org/2.0/en/core-libraries/components/authentication.html#AuthComponent::$loginRedirect @@ -210,7 +211,7 @@ class AuthComponent extends Component { * Error to display when user attempts to access an object or action to which they do not have * access. * - * @var string + * @var string|bool Error message or boolean false to suppress flash message * @link http://book.cakephp.org/2.0/en/core-libraries/components/authentication.html#AuthComponent::$authError */ public $authError = null; @@ -294,43 +295,13 @@ class AuthComponent extends Component { if (!$this->_setDefaults()) { return false; } - $request = $controller->request; - $url = ''; - - if (isset($request->url)) { - $url = $request->url; - } - $url = Router::normalize($url); - $loginAction = Router::normalize($this->loginAction); - - if ($loginAction != $url && in_array($action, array_map('strtolower', $this->allowedActions))) { - return true; - } - if ($loginAction == $url) { - if (empty($request->data)) { - if (!$this->Session->check('Auth.redirect') && env('HTTP_REFERER')) { - $referer = $request->referer(true); - $this->Session->write('Auth.redirect', $referer); - } - } + if ($this->_isAllowed($controller)) { return true; } if (!$this->_getUser()) { - if (!$request->is('ajax')) { - $this->flash($this->authError); - $this->Session->write('Auth.redirect', $request->here(false)); - $controller->redirect($loginAction); - return false; - } - if (!empty($this->ajaxLogin)) { - $controller->viewPath = 'Elements'; - echo $controller->render($this->ajaxLogin, $this->RequestHandler->ajaxLayout); - $this->_stop(); - return false; - } - $controller->redirect(null, 403); + return $this->_unauthenticated($controller); } if (empty($this->authorize) || $this->isAuthorized($this->user())) { @@ -340,12 +311,95 @@ class AuthComponent extends Component { return $this->_unauthorized($controller); } +/** + * Checks whether current action is accessible without authentication. + * + * @param Controller $controller A reference to the instantiating controller object + * @return boolean True if action is accessible without authentication else false + */ + protected function _isAllowed(Controller $controller) { + $action = strtolower($controller->request->params['action']); + if (in_array($action, array_map('strtolower', $this->allowedActions))) { + return true; + } + return false; + } + +/** + * Handles unauthenticated access attempt. First the `unathenticated()` method + * of the last authenticator in the chain will be called. The authenticator can + * handle sending response or redirection as appropriate and return `true` to + * indicate no furthur action is necessary. If authenticator returns null this + * method redirects user to login action. If it's an ajax request and + * $ajaxLogin is specified that element is rendered else a 403 http status code + * is returned. + * + * @param Controller $controller A reference to the controller object. + * @return boolean True if current action is login action else false. + */ + protected function _unauthenticated(Controller $controller) { + if (empty($this->_authenticateObjects)) { + $this->constructAuthenticate(); + } + $auth = $this->_authenticateObjects[count($this->_authenticateObjects) - 1]; + if ($auth->unauthenticated($this->request, $this->response)) { + return false; + } + + if ($this->_isLoginAction($controller)) { + return true; + } + + if (!$controller->request->is('ajax')) { + $this->flash($this->authError); + $this->Session->write('Auth.redirect', $controller->request->here(false)); + $controller->redirect($this->loginAction); + return false; + } + if (!empty($this->ajaxLogin)) { + $controller->viewPath = 'Elements'; + echo $controller->render($this->ajaxLogin, $this->RequestHandler->ajaxLayout); + $this->_stop(); + return false; + } + $controller->redirect(null, 403); + return false; + } + +/** + * Normalizes $loginAction and checks if current request url is same as login + * action. If current url is same as login action, referrer url is saved in session + * which is later accessible using redirectUrl(). + * + * @param Controller $controller A reference to the controller object. + * @return boolean True if current action is login action else false. + */ + protected function _isLoginAction(Controller $controller) { + $url = ''; + if (isset($controller->request->url)) { + $url = $controller->request->url; + } + $url = Router::normalize($url); + $loginAction = Router::normalize($this->loginAction); + + if ($loginAction == $url) { + if (empty($controller->request->data)) { + if (!$this->Session->check('Auth.redirect') && env('HTTP_REFERER')) { + $this->Session->write('Auth.redirect', $controller->referer(null, true)); + } + } + return true; + } + return false; + } + /** * Handle unauthorized access attempt * * @param Controller $controller A reference to the controller object * @return boolean Returns false * @throws ForbiddenException + * @see AuthComponent::$unauthorizedRedirect */ protected function _unauthorized(Controller $controller) { if ($this->unauthorizedRedirect === false) { @@ -369,7 +423,7 @@ class AuthComponent extends Component { /** * Attempts to introspect the correct values for object properties. * - * @return boolean + * @return boolean True */ protected function _setDefaults() { $defaults = array( @@ -377,7 +431,7 @@ class AuthComponent extends Component { 'authError' => __d('cake', 'You are not authorized to access that location.') ); foreach ($defaults as $key => $value) { - if (empty($this->{$key})) { + if (!isset($this->{$key}) || $this->{$key} === true) { $this->{$key} = $value; } } @@ -441,7 +495,7 @@ class AuthComponent extends Component { throw new CakeException(__d('cake_dev', 'Authorization adapter "%s" was not found.', $class)); } if (!method_exists($className, 'authorize')) { - throw new CakeException(__d('cake_dev', 'Authorization objects must implement an authorize method.')); + throw new CakeException(__d('cake_dev', 'Authorization objects must implement an %s method.', 'authorize()')); } $settings = array_merge($global, (array)$settings); $this->_authorizeObjects[] = new $className($this->_Collection, $settings); @@ -593,13 +647,12 @@ class AuthComponent extends Component { * @link http://book.cakephp.org/2.0/en/core-libraries/components/authentication.html#accessing-the-logged-in-user */ public static function user($key = null) { - if (empty(self::$_user) && !CakeSession::check(self::$sessionKey)) { - return null; - } if (!empty(self::$_user)) { $user = self::$_user; - } else { + } elseif (self::$sessionKey && CakeSession::check(self::$sessionKey)) { $user = CakeSession::read(self::$sessionKey); + } else { + return null; } if ($key === null) { return $user; @@ -616,8 +669,10 @@ class AuthComponent extends Component { protected function _getUser() { $user = $this->user(); if ($user) { + $this->Session->delete('Auth.redirect'); return true; } + if (empty($this->_authenticateObjects)) { $this->constructAuthenticate(); } @@ -628,6 +683,7 @@ class AuthComponent extends Component { return true; } } + return false; } @@ -728,7 +784,7 @@ class AuthComponent extends Component { throw new CakeException(__d('cake_dev', 'Authentication adapter "%s" was not found.', $class)); } if (!method_exists($className, 'authenticate')) { - throw new CakeException(__d('cake_dev', 'Authentication objects must implement an authenticate method.')); + throw new CakeException(__d('cake_dev', 'Authentication objects must implement an %s method.', 'authenticate()')); } $settings = array_merge($global, (array)$settings); $this->_authenticateObjects[] = new $className($this->_Collection, $settings); @@ -744,24 +800,12 @@ class AuthComponent extends Component { * * @param string $password Password to hash * @return string Hashed password - * @link http://book.cakephp.org/2.0/en/core-libraries/components/authentication.html#hashing-passwords + * @deprecated Since 2.4. Use Security::hash() directly or a password hasher object. */ public static function password($password) { return Security::hash($password, null, true); } -/** - * Component shutdown. If user is logged in, wipe out redirect. - * - * @param Controller $controller Instantiating controller - * @return void - */ - public function shutdown(Controller $controller) { - if ($this->loggedIn()) { - $this->Session->delete('Auth.redirect'); - } - } - /** * Check whether or not the current user has data in the session, and is considered logged in. * @@ -778,6 +822,9 @@ class AuthComponent extends Component { * @return void */ public function flash($message) { + if ($message === false) { + return; + } $this->Session->setFlash($message, $this->flash['element'], $this->flash['params'], $this->flash['key']); } diff --git a/lib/Cake/Controller/Component/RequestHandlerComponent.php b/lib/Cake/Controller/Component/RequestHandlerComponent.php index 8b6e204bc..fd90a6d9e 100644 --- a/lib/Cake/Controller/Component/RequestHandlerComponent.php +++ b/lib/Cake/Controller/Component/RequestHandlerComponent.php @@ -144,6 +144,9 @@ class RequestHandlerComponent extends Component { * Compares the accepted types and configured extensions. * If there is one common type, that is assigned as the ext/content type * for the response. + * Type with the highest weight will be set. If the highest weight has more + * then one type matching the extensions, the order in which extensions are specified + * determines which type will be set. * * If html is one of the preferred types, no content type will be set, this * is to avoid issues with browsers that prefer html and several other content types. @@ -155,13 +158,19 @@ class RequestHandlerComponent extends Component { if (empty($accept)) { return; } + + $accepts = $this->response->mapType($this->request->parseAccept()); + $preferedTypes = current($accepts); + if (array_intersect($preferedTypes, array('html', 'xhtml'))) { + return null; + } + $extensions = Router::extensions(); - $preferred = array_shift($accept); - $preferredTypes = $this->response->mapType($preferred); - if (!in_array('xhtml', $preferredTypes) && !in_array('html', $preferredTypes)) { - $similarTypes = array_intersect($extensions, $preferredTypes); - if (count($similarTypes) === 1) { - $this->ext = array_shift($similarTypes); + foreach ($accepts as $types) { + $ext = array_intersect($extensions, $types); + if ($ext) { + $this->ext = current($ext); + break; } } } diff --git a/lib/Cake/Controller/ComponentCollection.php b/lib/Cake/Controller/ComponentCollection.php index d1499eeda..8d92ff657 100644 --- a/lib/Cake/Controller/ComponentCollection.php +++ b/lib/Cake/Controller/ComponentCollection.php @@ -94,7 +94,7 @@ class ComponentCollection extends ObjectCollection implements CakeEventListener * @throws MissingComponentException when the component could not be found */ public function load($component, $settings = array()) { - if (is_array($settings) && isset($settings['className'])) { + if (isset($settings['className'])) { $alias = $component; $component = $settings['className']; } diff --git a/lib/Cake/Controller/Controller.php b/lib/Cake/Controller/Controller.php index edf05e425..daf2cf2cc 100644 --- a/lib/Cake/Controller/Controller.php +++ b/lib/Cake/Controller/Controller.php @@ -705,7 +705,7 @@ class Controller extends Object implements CakeEventListener { * * @return array Associative array of the HTTP codes as keys, and the message * strings as values, or null of the given $code does not exist. - * @deprecated Use CakeResponse::httpCodes(); + * @deprecated Since 2.4. Will be removed in 3.0. Use CakeResponse::httpCodes(). */ public function httpCodes($code = null) { return $this->response->httpCodes($code); @@ -820,7 +820,7 @@ class Controller extends Object implements CakeEventListener { * * @param string $status The header message that is being set. * @return void - * @deprecated Use CakeResponse::header() + * @deprecated Will be removed in 3.0. Use CakeResponse::header(). */ public function header($status) { $this->response->header($status); @@ -978,7 +978,7 @@ class Controller extends Object implements CakeEventListener { * * @return void * @link http://book.cakephp.org/2.0/en/controllers.html#Controller::disableCache - * @deprecated Use CakeResponse::disableCache() + * @deprecated Will be removed in 3.0. Use CakeResponse::disableCache(). */ public function disableCache() { $this->response->disableCache(); @@ -995,6 +995,7 @@ class Controller extends Object implements CakeEventListener { * @param string $layout Layout you want to use, defaults to 'flash' * @return void * @link http://book.cakephp.org/2.0/en/controllers.html#Controller::flash + * @deprecated Will be removed in 3.0. Use Session::setFlash(). */ public function flash($message, $url, $pause = 1, $layout = 'flash') { $this->autoRender = false; @@ -1015,7 +1016,7 @@ class Controller extends Object implements CakeEventListener { * @param boolean $exclusive If true, and $op is an array, fields not included in $op will not be * included in the returned conditions * @return array An array of model conditions - * @deprecated Will be removed in 3.0 + * @deprecated Will be removed in 3.0. */ public function postConditions($data = array(), $op = null, $bool = 'AND', $exclusive = false) { if (!is_array($data) || empty($data)) { @@ -1072,7 +1073,7 @@ class Controller extends Object implements CakeEventListener { * @param array $whitelist List of allowed options for paging * @return array Model query results * @link http://book.cakephp.org/2.0/en/controllers.html#Controller::paginate - * @deprecated Use PaginatorComponent instead + * @deprecated Will be removed in 3.0. Use PaginatorComponent instead. */ public function paginate($object = null, $scope = array(), $whitelist = array()) { return $this->Components->load('Paginator', $this->paginate)->paginate($object, $scope, $whitelist); @@ -1146,7 +1147,7 @@ class Controller extends Object implements CakeEventListener { * @param string $method * @return boolean * @see Controller::beforeScaffold() - * @deprecated + * @deprecated Will be removed in 3.0. */ protected function _beforeScaffold($method) { return $this->beforeScaffold($method); @@ -1169,7 +1170,7 @@ class Controller extends Object implements CakeEventListener { * @param string $method * @return boolean * @see Controller::afterScaffoldSave() - * @deprecated + * @deprecated Will be removed in 3.0. */ protected function _afterScaffoldSave($method) { return $this->afterScaffoldSave($method); @@ -1192,7 +1193,7 @@ class Controller extends Object implements CakeEventListener { * @param string $method * @return boolean * @see Controller::afterScaffoldSaveError() - * @deprecated + * @deprecated Will be removed in 3.0. */ protected function _afterScaffoldSaveError($method) { return $this->afterScaffoldSaveError($method); @@ -1217,7 +1218,7 @@ class Controller extends Object implements CakeEventListener { * @param string $method * @return boolean * @see Controller::scaffoldError() - * @deprecated + * @deprecated Will be removed in 3.0. */ protected function _scaffoldError($method) { return $this->scaffoldError($method); diff --git a/lib/Cake/Core/Configure.php b/lib/Cake/Core/Configure.php index 338dfd5cb..666de9429 100644 --- a/lib/Cake/Core/Configure.php +++ b/lib/Cake/Core/Configure.php @@ -67,16 +67,14 @@ class Configure { */ public static function bootstrap($boot = true) { if ($boot) { - self::write('App', array( - 'base' => false, - 'baseUrl' => false, - 'dir' => APP_DIR, - 'webroot' => WEBROOT_DIR, - 'www_root' => WWW_ROOT - )); + self::_appDefaults(); if (!include APP . 'Config' . DS . 'core.php') { - trigger_error(__d('cake_dev', "Can't find application core file. Please create %score.php, and make sure it is readable by PHP.", APP . 'Config' . DS), E_USER_ERROR); + trigger_error(__d('cake_dev', + "Can't find application core file. Please create %s, and make sure it is readable by PHP.", + APP . 'Config' . DS . 'core.php'), + E_USER_ERROR + ); } App::init(); App::$bootstrapping = false; @@ -92,7 +90,11 @@ class Configure { self::_setErrorHandlers($error, $exception); if (!include APP . 'Config' . DS . 'bootstrap.php') { - trigger_error(__d('cake_dev', "Can't find application bootstrap file. Please create %sbootstrap.php, and make sure it is readable by PHP.", APP . 'Config' . DS), E_USER_ERROR); + trigger_error(__d('cake_dev', + "Can't find application bootstrap file. Please create %s, and make sure it is readable by PHP.", + APP . 'Config' . DS . 'bootstrap.php'), + E_USER_ERROR + ); } restore_error_handler(); @@ -109,6 +111,20 @@ class Configure { } } +/** + * Set app's default configs + * @return void + */ + protected static function _appDefaults() { + self::write('App', (array)self::read('App') + array( + 'base' => false, + 'baseUrl' => false, + 'dir' => APP_DIR, + 'webroot' => WEBROOT_DIR, + 'www_root' => WWW_ROOT + )); + } + /** * Used to store a dynamic variable in Configure. * @@ -128,7 +144,8 @@ class Configure { * }}} * * @link http://book.cakephp.org/2.0/en/development/configuration.html#Configure::write - * @param array $config Name of var to write + * @param string|array $config The key to write, can be a dot notation value. + * Alternatively can be an array containing key(s) and value(s). * @param mixed $value Value to set for var * @return boolean True if write was successful */ @@ -323,7 +340,7 @@ class Configure { throw new ConfigureException(__d('cake_dev', 'There is no "%s" adapter.', $config)); } if (!method_exists($reader, 'dump')) { - throw new ConfigureException(__d('cake_dev', 'The "%s" adapter, does not have a dump() method.', $config)); + throw new ConfigureException(__d('cake_dev', 'The "%s" adapter, does not have a %s method.', $config, 'dump()')); } $values = self::$_values; if (!empty($keys) && is_array($keys)) { diff --git a/lib/Cake/Core/Object.php b/lib/Cake/Core/Object.php index 997678def..053c68f7c 100644 --- a/lib/Cake/Core/Object.php +++ b/lib/Cake/Core/Object.php @@ -16,7 +16,9 @@ App::uses('CakeLog', 'Log'); App::uses('Dispatcher', 'Routing'); +App::uses('Router', 'Routing'); App::uses('Set', 'Utility'); +App::uses('CakeLog', 'Log'); /** * Object class provides a few generic methods used in several subclasses. @@ -86,8 +88,8 @@ class Object { $data = isset($extra['data']) ? $extra['data'] : null; unset($extra['data']); - if (is_string($url) && strpos($url, FULL_BASE_URL) === 0) { - $url = Router::normalize(str_replace(FULL_BASE_URL, '', $url)); + if (is_string($url) && strpos($url, Router::fullBaseUrl()) === 0) { + $url = Router::normalize(str_replace(Router::fullBaseUrl(), '', $url)); } if (is_string($url)) { $request = new CakeRequest($url); @@ -148,17 +150,16 @@ class Object { * Convenience method to write a message to CakeLog. See CakeLog::write() * for more information on writing to logs. * - * @param string $msg Log message. - * @param integer|string $type Type of message being written. Either a valid - * LOG_* constant or a string matching the recognized levels. - * @return boolean Success of log write. - * @see CakeLog::write() + * @param string $msg Log message + * @param integer $type Error type constant. Defined in app/Config/core.php. + * @return boolean Success of log write */ - public function log($msg, $type = LOG_ERR) { + public function log($msg, $type = LOG_ERR, $scope = null) { if (!is_string($msg)) { $msg = print_r($msg, true); } - return CakeLog::write($type, $msg); + + return CakeLog::write($type, $msg, $scope); } /** diff --git a/lib/Cake/Error/ErrorHandler.php b/lib/Cake/Error/ErrorHandler.php index 1791c7d4b..b529b0d06 100644 --- a/lib/Cake/Error/ErrorHandler.php +++ b/lib/Cake/Error/ErrorHandler.php @@ -110,9 +110,8 @@ class ErrorHandler { */ public static function handleException(Exception $exception) { $config = Configure::read('Exception'); - if (!empty($config['log'])) { - CakeLog::write(LOG_ERR, self::_getMessage($exception)); - } + self::_log($exception, $config); + $renderer = isset($config['renderer']) ? $config['renderer'] : 'ExceptionRenderer'; if ($renderer !== 'ExceptionRenderer') { list($plugin, $renderer) = pluginSplit($renderer, true); @@ -159,6 +158,28 @@ class ErrorHandler { return $message; } +/** + * Handles exception logging + * + * @param Exception $exception + * @param array $config + * @return boolean + */ + protected static function _log(Exception $exception, $config) { + if (empty($config['log'])) { + return false; + } + + if (!empty($config['skipLog'])) { + foreach ((array)$config['skipLog'] as $class) { + if ($exception instanceof $class) { + return false; + } + } + } + return CakeLog::write(LOG_ERR, self::_getMessage($exception)); + } + /** * Set as the default error handler by CakePHP. Use Configure::write('Error.handler', $callback), to use your own * error handling methods. This function will use Debugger to display errors when debug > 0. And diff --git a/lib/Cake/Error/exceptions.php b/lib/Cake/Error/exceptions.php index ef14bbf03..37b8a7e45 100644 --- a/lib/Cake/Error/exceptions.php +++ b/lib/Cake/Error/exceptions.php @@ -39,7 +39,7 @@ class CakeBaseException extends RuntimeException { * @param string|array $header. An array of header strings or a single header string * - an associative array of "header name" => "header value" * - an array of string headers is also accepted - * @param string $value. The header value. + * @param string $value The header value. * @return array * @see CakeResponse::header() */ @@ -78,7 +78,7 @@ class BadRequestException extends HttpException { * Constructor * * @param string $message If no message is given 'Bad Request' will be the message - * @param int $code Status code, defaults to 400 + * @param integer $code Status code, defaults to 400 */ public function __construct($message = null, $code = 400) { if (empty($message)) { @@ -100,7 +100,7 @@ class UnauthorizedException extends HttpException { * Constructor * * @param string $message If no message is given 'Unauthorized' will be the message - * @param int $code Status code, defaults to 401 + * @param integer $code Status code, defaults to 401 */ public function __construct($message = null, $code = 401) { if (empty($message)) { @@ -122,7 +122,7 @@ class ForbiddenException extends HttpException { * Constructor * * @param string $message If no message is given 'Forbidden' will be the message - * @param int $code Status code, defaults to 403 + * @param integer $code Status code, defaults to 403 */ public function __construct($message = null, $code = 403) { if (empty($message)) { @@ -144,7 +144,7 @@ class NotFoundException extends HttpException { * Constructor * * @param string $message If no message is given 'Not Found' will be the message - * @param int $code Status code, defaults to 404 + * @param integer $code Status code, defaults to 404 */ public function __construct($message = null, $code = 404) { if (empty($message)) { @@ -166,7 +166,7 @@ class MethodNotAllowedException extends HttpException { * Constructor * * @param string $message If no message is given 'Method Not Allowed' will be the message - * @param int $code Status code, defaults to 405 + * @param integer $code Status code, defaults to 405 */ public function __construct($message = null, $code = 405) { if (empty($message)) { @@ -188,7 +188,7 @@ class InternalErrorException extends HttpException { * Constructor * * @param string $message If no message is given 'Internal Server Error' will be the message - * @param int $code Status code, defaults to 500 + * @param integer $code Status code, defaults to 500 */ public function __construct($message = null, $code = 500) { if (empty($message)) { @@ -230,7 +230,7 @@ class CakeException extends CakeBaseException { * * @param string|array $message Either the string of the error message, or an array of attributes * that are made available in the view, and sprintf()'d into CakeException::$_messageTemplate - * @param int $code The code of the error, is also the HTTP status code for the error. + * @param integer $code The code of the error, is also the HTTP status code for the error. */ public function __construct($message, $code = 500) { if (is_array($message)) { diff --git a/lib/Cake/I18n/L10n.php b/lib/Cake/I18n/L10n.php index c98bcedce..aba0afbf7 100644 --- a/lib/Cake/I18n/L10n.php +++ b/lib/Cake/I18n/L10n.php @@ -39,7 +39,7 @@ class L10n { * * @var array */ - public $languagePath = array('eng'); + public $languagePath = array('en_us', 'eng'); /** * ISO 639-3 for current locale @@ -56,9 +56,11 @@ class L10n { public $locale = 'en_us'; /** - * Default ISO 639-3 language. + * Default language. * - * DEFAULT_LANGUAGE is defined in an application this will be set as a fall back + * If config value 'Config.language' is set in an application this will be set + * as a fall back else if DEFAULT_LANGUAGE it defined it will be used. + * Constant DEFAULT_LANGUAGE has been deprecated in 2.4 * * @var string */ @@ -78,13 +80,6 @@ class L10n { */ public $direction = 'ltr'; -/** - * Set to true if a locale is found - * - * @var string - */ - public $found = false; - /** * Maps ISO 639-3 to I10n::_l10nCatalog * The terminological codes (first one per language) should be used if possible. @@ -138,6 +133,8 @@ class L10n { /* Irish */ 'gle' => 'ga', /* Italian */ 'ita' => 'it', /* Japanese */ 'jpn' => 'ja', + /* Kazakh */ 'kaz' => 'kk', + /* Kalaallisut (Greenlandic) */ 'kal' => 'kl', /* Korean */ 'kor' => 'ko', /* Latvian */ 'lav' => 'lv', /* Lithuanian */ 'lit' => 'lt', @@ -155,7 +152,7 @@ class L10n { /* Romanian */ 'ron' => 'ro', /* Romanian - bibliographic */ 'rum' => 'ro', /* Russian */ 'rus' => 'ru', - /* Sami (Lappish) */ 'smi' => 'sz', + /* Sami */ 'sme' => 'se', /* Serbian */ 'srp' => 'sr', /* Slovak */ 'slk' => 'sk', /* Slovak - bibliographic */ 'slo' => 'sk', @@ -219,8 +216,7 @@ class L10n { 'de-de' => array('language' => 'German (Germany)', 'locale' => 'de_de', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), 'de-li' => array('language' => 'German (Liechtenstein)', 'locale' => 'de_li', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), 'de-lu' => array('language' => 'German (Luxembourg)', 'locale' => 'de_lu', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'e' => array('language' => 'Greek', 'locale' => 'gre', 'localeFallback' => 'gre', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'el' => array('language' => 'Greek', 'locale' => 'gre', 'localeFallback' => 'gre', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'el' => array('language' => 'Greek', 'locale' => 'ell', 'localeFallback' => 'ell', 'charset' => 'utf-8', 'direction' => 'ltr'), 'en' => array('language' => 'English', 'locale' => 'eng', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), 'en-au' => array('language' => 'English (Australian)', 'locale' => 'en_au', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), 'en-bz' => array('language' => 'English (Belize)', 'locale' => 'en_bz', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), @@ -254,7 +250,7 @@ class L10n { 'es-ve' => array('language' => 'Spanish (Venezuela)', 'locale' => 'es_ve', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), 'et' => array('language' => 'Estonian', 'locale' => 'est', 'localeFallback' => 'est', 'charset' => 'utf-8', 'direction' => 'ltr'), 'eu' => array('language' => 'Basque', 'locale' => 'eus', 'localeFallback' => 'eus', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'fa' => array('language' => 'Farsi', 'locale' => 'per', 'localeFallback' => 'per', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'fa' => array('language' => 'Farsi', 'locale' => 'fas', 'localeFallback' => 'fas', 'charset' => 'utf-8', 'direction' => 'rtl'), 'fi' => array('language' => 'Finnish', 'locale' => 'fin', 'localeFallback' => 'fin', 'charset' => 'utf-8', 'direction' => 'ltr'), 'fo' => array('language' => 'Faeroese', 'locale' => 'fao', 'localeFallback' => 'fao', 'charset' => 'utf-8', 'direction' => 'ltr'), 'fr' => array('language' => 'French (Standard)', 'locale' => 'fra', 'localeFallback' => 'fra', 'charset' => 'utf-8', 'direction' => 'ltr'), @@ -273,28 +269,27 @@ class L10n { 'hu' => array('language' => 'Hungarian', 'locale' => 'hun', 'localeFallback' => 'hun', 'charset' => 'utf-8', 'direction' => 'ltr'), 'hy' => array('language' => 'Armenian - Armenia', 'locale' => 'hye', 'localeFallback' => 'hye', 'charset' => 'utf-8', 'direction' => 'ltr'), 'id' => array('language' => 'Indonesian', 'locale' => 'ind', 'localeFallback' => 'ind', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'in' => array('language' => 'Indonesian', 'locale' => 'ind', 'localeFallback' => 'ind', 'charset' => 'utf-8', 'direction' => 'ltr'), 'is' => array('language' => 'Icelandic', 'locale' => 'isl', 'localeFallback' => 'isl', 'charset' => 'utf-8', 'direction' => 'ltr'), 'it' => array('language' => 'Italian', 'locale' => 'ita', 'localeFallback' => 'ita', 'charset' => 'utf-8', 'direction' => 'ltr'), 'it-ch' => array('language' => 'Italian (Swiss) ', 'locale' => 'it_ch', 'localeFallback' => 'ita', 'charset' => 'utf-8', 'direction' => 'ltr'), 'ja' => array('language' => 'Japanese', 'locale' => 'jpn', 'localeFallback' => 'jpn', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'kk' => array('language' => 'Kazakh', 'locale' => 'kaz', 'localeFallback' => 'kaz', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'kl' => array('language' => 'Kalaallisut (Greenlandic)', 'locale' => 'kal', 'localeFallback' => 'kal', 'charset' => 'kl', 'direction' => 'ltr'), 'ko' => array('language' => 'Korean', 'locale' => 'kor', 'localeFallback' => 'kor', 'charset' => 'kr', 'direction' => 'ltr'), 'ko-kp' => array('language' => 'Korea (North)', 'locale' => 'ko_kp', 'localeFallback' => 'kor', 'charset' => 'kr', 'direction' => 'ltr'), 'ko-kr' => array('language' => 'Korea (South)', 'locale' => 'ko_kr', 'localeFallback' => 'kor', 'charset' => 'kr', 'direction' => 'ltr'), 'koi8-r' => array('language' => 'Russian', 'locale' => 'koi8_r', 'localeFallback' => 'rus', 'charset' => 'koi8-r', 'direction' => 'ltr'), 'lt' => array('language' => 'Lithuanian', 'locale' => 'lit', 'localeFallback' => 'lit', 'charset' => 'utf-8', 'direction' => 'ltr'), 'lv' => array('language' => 'Latvian', 'locale' => 'lav', 'localeFallback' => 'lav', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'mk' => array('language' => 'FYRO Macedonian', 'locale' => 'mk', 'localeFallback' => 'mkd', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'mk' => array('language' => 'FYRO Macedonian', 'locale' => 'mkd', 'localeFallback' => 'mkd', 'charset' => 'utf-8', 'direction' => 'ltr'), 'mk-mk' => array('language' => 'Macedonian', 'locale' => 'mk_mk', 'localeFallback' => 'mkd', 'charset' => 'utf-8', 'direction' => 'ltr'), 'ms' => array('language' => 'Malaysian', 'locale' => 'msa', 'localeFallback' => 'msa', 'charset' => 'utf-8', 'direction' => 'ltr'), 'mt' => array('language' => 'Maltese', 'locale' => 'mlt', 'localeFallback' => 'mlt', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'n' => array('language' => 'Dutch (Standard)', 'locale' => 'nld', 'localeFallback' => 'nld', 'charset' => 'utf-8', 'direction' => 'ltr'), 'nb' => array('language' => 'Norwegian Bokmal', 'locale' => 'nob', 'localeFallback' => 'nor', 'charset' => 'utf-8', 'direction' => 'ltr'), 'nl' => array('language' => 'Dutch (Standard)', 'locale' => 'nld', 'localeFallback' => 'nld', 'charset' => 'utf-8', 'direction' => 'ltr'), 'nl-be' => array('language' => 'Dutch (Belgium)', 'locale' => 'nl_be', 'localeFallback' => 'nld', 'charset' => 'utf-8', 'direction' => 'ltr'), 'nn' => array('language' => 'Norwegian Nynorsk', 'locale' => 'nno', 'localeFallback' => 'nor', 'charset' => 'utf-8', 'direction' => 'ltr'), 'no' => array('language' => 'Norwegian', 'locale' => 'nor', 'localeFallback' => 'nor', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'p' => array('language' => 'Polish', 'locale' => 'pol', 'localeFallback' => 'pol', 'charset' => 'utf-8', 'direction' => 'ltr'), 'pl' => array('language' => 'Polish', 'locale' => 'pol', 'localeFallback' => 'pol', 'charset' => 'utf-8', 'direction' => 'ltr'), 'pt' => array('language' => 'Portuguese (Portugal)', 'locale' => 'por', 'localeFallback' => 'por', 'charset' => 'utf-8', 'direction' => 'ltr'), 'pt-br' => array('language' => 'Portuguese (Brazil)', 'locale' => 'pt_br', 'localeFallback' => 'por', 'charset' => 'utf-8', 'direction' => 'ltr'), @@ -310,8 +305,7 @@ class L10n { 'sr' => array('language' => 'Serbian', 'locale' => 'srp', 'localeFallback' => 'srp', 'charset' => 'utf-8', 'direction' => 'ltr'), 'sv' => array('language' => 'Swedish', 'locale' => 'swe', 'localeFallback' => 'swe', 'charset' => 'utf-8', 'direction' => 'ltr'), 'sv-fi' => array('language' => 'Swedish (Finland)', 'locale' => 'sv_fi', 'localeFallback' => 'swe', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'sx' => array('language' => 'Sutu', 'locale' => 'sx', 'localeFallback' => 'sx', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'sz' => array('language' => 'Sami (Lappish)', 'locale' => 'smi', 'localeFallback' => 'smi', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'se' => array('language' => 'Sami', 'locale' => 'sme', 'localeFallback' => 'sme', 'charset' => 'utf-8', 'direction' => 'ltr'), 'th' => array('language' => 'Thai', 'locale' => 'tha', 'localeFallback' => 'tha', 'charset' => 'utf-8', 'direction' => 'ltr'), 'tn' => array('language' => 'Tswana', 'locale' => 'tsn', 'localeFallback' => 'tsn', 'charset' => 'utf-8', 'direction' => 'ltr'), 'tr' => array('language' => 'Turkish', 'locale' => 'tur', 'localeFallback' => 'tur', 'charset' => 'utf-8', 'direction' => 'ltr'), @@ -338,6 +332,10 @@ class L10n { if (defined('DEFAULT_LANGUAGE')) { $this->default = DEFAULT_LANGUAGE; } + $default = Configure::read('Config.language'); + if ($default) { + $this->default = $default; + } } /** @@ -361,44 +359,44 @@ class L10n { /** * Sets the class vars to correct values for $language. - * If $language is null it will use the DEFAULT_LANGUAGE if defined + * If $language is null it will use the L10n::$default if defined * - * @param string $language Language (if null will use DEFAULT_LANGUAGE if defined) + * @param string $language Language (if null will use L10n::$default if defined) * @return mixed */ protected function _setLanguage($language = null) { - $langKey = null; - if ($language !== null && isset($this->_l10nMap[$language]) && isset($this->_l10nCatalog[$this->_l10nMap[$language]])) { - $langKey = $this->_l10nMap[$language]; - } elseif ($language !== null && isset($this->_l10nCatalog[$language])) { - $langKey = $language; - } elseif (defined('DEFAULT_LANGUAGE')) { - $langKey = $language = DEFAULT_LANGUAGE; + $catalog = false; + if ($language !== null) { + $catalog = $this->catalog($language); } - if ($langKey !== null && isset($this->_l10nCatalog[$langKey])) { - $this->language = $this->_l10nCatalog[$langKey]['language']; - $this->languagePath = array( - $this->_l10nCatalog[$langKey]['locale'], - $this->_l10nCatalog[$langKey]['localeFallback'] - ); + if (!$catalog && $this->default) { + $language = $this->default; + $catalog = $this->catalog($language); + } + + if ($catalog) { + $this->language = $catalog['language']; + $this->languagePath = array_unique(array( + $catalog['locale'], + $catalog['localeFallback'] + )); $this->lang = $language; - $this->locale = $this->_l10nCatalog[$langKey]['locale']; - $this->charset = $this->_l10nCatalog[$langKey]['charset']; - $this->direction = $this->_l10nCatalog[$langKey]['direction']; - } else { + $this->locale = $catalog['locale']; + $this->charset = $catalog['charset']; + $this->direction = $catalog['direction']; + } elseif ($language) { $this->lang = $language; $this->languagePath = array($language); } - if ($this->default) { - if (isset($this->_l10nMap[$this->default]) && isset($this->_l10nCatalog[$this->_l10nMap[$this->default]])) { - $this->languagePath[] = $this->_l10nCatalog[$this->_l10nMap[$this->default]]['localeFallback']; - } elseif (isset($this->_l10nCatalog[$this->default])) { - $this->languagePath[] = $this->_l10nCatalog[$this->default]['localeFallback']; + if ($this->default && $language !== $this->default) { + $catalog = $this->catalog($this->default); + $fallback = $catalog['localeFallback']; + if (!in_array($fallback, $this->languagePath)) { + $this->languagePath[] = $fallback; } } - $this->found = true; if (Configure::read('Config.language') === null) { Configure::write('Config.language', $this->lang); @@ -420,7 +418,8 @@ class L10n { if (isset($this->_l10nCatalog[$langKey])) { $this->_setLanguage($langKey); return true; - } elseif (strpos($langKey, '-') !== false) { + } + if (strpos($langKey, '-') !== false) { $langKey = substr($langKey, 0, 2); if (isset($this->_l10nCatalog[$langKey])) { $this->_setLanguage($langKey); @@ -447,10 +446,12 @@ class L10n { } } return $result; - } elseif (is_string($mixed)) { + } + if (is_string($mixed)) { if (strlen($mixed) === 2 && in_array($mixed, $this->_l10nMap)) { return array_search($mixed, $this->_l10nMap); - } elseif (isset($this->_l10nMap[$mixed])) { + } + if (isset($this->_l10nMap[$mixed])) { return $this->_l10nMap[$mixed]; } return false; @@ -474,10 +475,12 @@ class L10n { } } return $result; - } elseif (is_string($language)) { + } + if (is_string($language)) { if (isset($this->_l10nCatalog[$language])) { return $this->_l10nCatalog[$language]; - } elseif (isset($this->_l10nMap[$language]) && isset($this->_l10nCatalog[$this->_l10nMap[$language]])) { + } + if (isset($this->_l10nMap[$language]) && isset($this->_l10nCatalog[$this->_l10nMap[$language]])) { return $this->_l10nCatalog[$this->_l10nMap[$language]]; } return false; diff --git a/lib/Cake/Log/CakeLog.php b/lib/Cake/Log/CakeLog.php index 17ad88ca2..f7b932051 100644 --- a/lib/Cake/Log/CakeLog.php +++ b/lib/Cake/Log/CakeLog.php @@ -34,7 +34,7 @@ App::uses('LogEngineCollection', 'Log'); * A sample configuration would look like: * * {{{ - * CakeLog::config('my_log', array('engine' => 'FileLog')); + * CakeLog::config('my_log', array('engine' => 'File')); * }}} * * See the documentation on CakeLog::config() for more detail. @@ -133,7 +133,7 @@ class CakeLog { * * {{{ * CakeLog::config('second_file', array( - * 'engine' => 'FileLog', + * 'engine' => 'File', * 'path' => '/var/logs/my_app/' * )); * }}} @@ -378,7 +378,7 @@ class CakeLog { */ protected static function _autoConfig() { self::$_Collection->load('default', array( - 'engine' => 'FileLog', + 'engine' => 'File', 'path' => LOGS, )); } diff --git a/lib/Cake/Log/Engine/FileLog.php b/lib/Cake/Log/Engine/FileLog.php index 100980c6c..071f12f8d 100644 --- a/lib/Cake/Log/Engine/FileLog.php +++ b/lib/Cake/Log/Engine/FileLog.php @@ -20,6 +20,7 @@ App::uses('BaseLog', 'Log/Engine'); App::uses('Hash', 'Utility'); +App::uses('CakeNumber', 'Utility'); /** * File Storage stream for Logging. Writes logs to different files @@ -29,6 +30,22 @@ App::uses('Hash', 'Utility'); */ class FileLog extends BaseLog { +/** + * Default configuration values + * + * @var array + * @see FileLog::__construct() + */ + protected $_defaults = array( + 'path' => LOGS, + 'file' => null, + 'types' => null, + 'scopes' => array(), + 'rotate' => 10, + 'size' => 10485760, // 10MB + 'mask' => null, + ); + /** * Path to save log files on. * @@ -36,6 +53,20 @@ class FileLog extends BaseLog { */ protected $_path = null; +/** + * Log file name + * + * @var string + */ + protected $_file = null; + +/** + * Max file size, used for log file rotation. + * + * @var integer + */ + protected $_size = null; + /** * Constructs a new File Logger. * @@ -43,25 +74,55 @@ class FileLog extends BaseLog { * * - `types` string or array, levels the engine is interested in * - `scopes` string or array, scopes the engine is interested in - * - `file` log file name - * - `path` the path to save logs on. + * - `file` Log file name + * - `path` The path to save logs on. + * - `size` Used to implement basic log file rotation. If log file size + * reaches specified size the existing file is renamed by appending timestamp + * to filename and new log file is created. Can be integer bytes value or + * human reabable string values like '10MB', '100KB' etc. + * - `rotate` Log files are rotated specified times before being removed. + * If value is 0, old versions are removed rather then rotated. + * - `mask` A mask is applied when log files are created. Left empty no chmod + * is made. * * @param array $options Options for the FileLog, see above. */ public function __construct($config = array()) { + $config = Hash::merge($this->_defaults, $config); parent::__construct($config); - $config = Hash::merge(array( - 'path' => LOGS, - 'file' => null, - 'types' => null, - 'scopes' => array(), - ), $this->_config); - $config = $this->config($config); - $this->_path = $config['path']; - $this->_file = $config['file']; - if (!empty($this->_file) && substr($this->_file, -4) !== '.log') { - $this->_file .= '.log'; + } + +/** + * Sets protected properties based on config provided + * + * @param array $config Engine configuration + * @return array + */ + public function config($config = array()) { + parent::config($config); + + if (!empty($config['path'])) { + $this->_path = $config['path']; } + if (Configure::read('debug') && !is_dir($this->_path)) { + mkdir($this->_path, 0775, true); + } + + if (!empty($config['file'])) { + $this->_file = $config['file']; + if (substr($this->_file, -4) !== '.log') { + $this->_file .= '.log'; + } + } + if (!empty($config['size'])) { + if (is_numeric($config['size'])) { + $this->_size = (int)$config['size']; + } else { + $this->_size = CakeNumber::fromReadableSize($config['size']); + } + } + + return $this->_config; } /** @@ -72,19 +133,85 @@ class FileLog extends BaseLog { * @return boolean success of write. */ public function write($type, $message) { + $output = date('Y-m-d H:i:s') . ' ' . ucfirst($type) . ': ' . $message . "\n"; + $filename = $this->_getFilename($type); + if (!empty($this->_size)) { + $this->_rotateFile($filename); + } + + $pathname = $this->_path . $filename; + if (empty($this->_config['mask'])) { + return file_put_contents($pathname, $output, FILE_APPEND); + } + + $exists = file_exists($pathname); + $result = file_put_contents($pathname, $output, FILE_APPEND); + static $selfError = false; + if (!$selfError && !$exists && !chmod($pathname, (int)$this->_config['mask'])) { + $selfError = true; + trigger_error(__d( + 'cake_dev', 'Could not apply permission mask "%s" on log file "%s"', + array($this->_config['mask'], $pathname)), E_USER_WARNING); + $selfError = false; + } + return $result; + } + +/** + * Get filename + * @param string $type The type of log. + * @return string File name + */ + protected function _getFilename($type) { $debugTypes = array('notice', 'info', 'debug'); if (!empty($this->_file)) { - $filename = $this->_path . $this->_file; - } elseif ($type === 'error' || $type === 'warning') { - $filename = $this->_path . 'error.log'; + $filename = $this->_file; + } elseif ($type == 'error' || $type == 'warning') { + $filename = 'error.log'; } elseif (in_array($type, $debugTypes)) { - $filename = $this->_path . 'debug.log'; + $filename = 'debug.log'; } else { - $filename = $this->_path . $type . '.log'; + $filename = $type . '.log'; } - $output = date('Y-m-d H:i:s') . ' ' . ucfirst($type) . ': ' . $message . "\n"; - return file_put_contents($filename, $output, FILE_APPEND); + + return $filename; + } + +/** + * Rotate log file if size specified in config is reached. + * Also if `rotate` count is reached oldest file is removed. + * + * @param string $filename Log file name + * @return mixed True if rotated successfully or false in case of error. + * Void if file doesn't need to be rotated. + */ + protected function _rotateFile($filename) { + $filepath = $this->_path . $filename; + if (version_compare(PHP_VERSION, '5.3.0') >= 0) { + clearstatcache(true, $filepath); + } else { + clearstatcache(); + } + + if (!file_exists($filepath) || + filesize($filepath) < $this->_size + ) { + return; + } + + if ($this->_config['rotate'] === 0) { + return unlink($filepath); + } + + if ($this->_config['rotate']) { + $files = glob($filepath . '.*'); + if (count($files) === $this->_config['rotate']) { + unlink(array_shift($files)); + } + } + + return rename($filepath, $filepath . '.' . time()); } } diff --git a/lib/Cake/Log/Engine/SyslogLog.php b/lib/Cake/Log/Engine/SyslogLog.php new file mode 100644 index 000000000..cf5701de1 --- /dev/null +++ b/lib/Cake/Log/Engine/SyslogLog.php @@ -0,0 +1,164 @@ + 'Syslog', + * 'types' => array('emergency', 'alert', 'critical', 'error'), + * 'format' => "%s: My-App - %s", + * 'prefix' => 'Web Server 01' + * )); + * }}} + * + * @var array + */ + protected $_defaults = array( + 'format' => '%s: %s', + 'flag' => LOG_ODELAY, + 'prefix' => '', + 'facility' => LOG_USER + ); + +/** + * + * Used to map the string names back to their LOG_* constants + * + * @var array + */ + protected $_priorityMap = array( + 'emergency' => LOG_EMERG, + 'alert' => LOG_ALERT, + 'critical' => LOG_CRIT, + 'error' => LOG_ERR, + 'warning' => LOG_WARNING, + 'notice' => LOG_NOTICE, + 'info' => LOG_INFO, + 'debug' => LOG_DEBUG + ); + +/** + * Whether the logger connection is open or not + * + * @var boolean + */ + protected $_open = false; + +/** + * Make sure the configuration contains the format parameter, by default it uses + * the error number and the type as a prefix to the message + * + * @param array $config + */ + public function __construct($config = array()) { + $config += $this->_defaults; + parent::__construct($config); + } + +/** + * Writes a message to syslog + * + * Map the $type back to a LOG_ constant value, split multi-line messages into multiple + * log messages, pass all messages through the format defined in the configuration + * + * @param string $type The type of log you are making. + * @param string $message The message you want to log. + * @return boolean success of write. + */ + public function write($type, $message) { + if (!$this->_open) { + $config = $this->_config; + $this->_open($config['prefix'], $config['flag'], $config['facility']); + $this->_open = true; + } + + $priority = LOG_DEBUG; + if (isset($this->_priorityMap[$type])) { + $priority = $this->_priorityMap[$type]; + } + + $messages = explode("\n", $message); + foreach ($messages as $message) { + $message = sprintf($this->_config['format'], $type, $message); + $this->_write($priority, $message); + } + + return true; + } + +/** + * Extracts the call to openlog() in order to run unit tests on it. This function + * will initialize the connection to the system logger + * + * @param string $ident the prefix to add to all messages logged + * @param integer $options the options flags to be used for logged messages + * @param integer $facility the stream or facility to log to + * @return void + */ + protected function _open($ident, $options, $facility) { + openlog($ident, $options, $facility); + } + +/** + * Extracts the call to syslog() in order to run unit tests on it. This function + * will perform the actual write in the system logger + * + * @param integer $priority + * @param string $message + * @return bool + */ + protected function _write($priority, $message) { + return syslog($priority, $message); + } + +/** + * Closes the logger connection + * + * @return void + **/ + public function __destruct() { + closelog(); + } + +} diff --git a/lib/Cake/Log/LogEngineCollection.php b/lib/Cake/Log/LogEngineCollection.php index 0704c77fa..5de70d02c 100644 --- a/lib/Cake/Log/LogEngineCollection.php +++ b/lib/Cake/Log/LogEngineCollection.php @@ -42,9 +42,9 @@ class LogEngineCollection extends ObjectCollection { $className = $this->_getLogger($loggerName); $logger = new $className($options); if (!$logger instanceof CakeLogInterface) { - throw new CakeLogException(sprintf( - __d('cake_dev', 'logger class %s does not implement a write method.'), $loggerName - )); + throw new CakeLogException( + __d('cake_dev', 'logger class %s does not implement a %s method.', $loggerName, 'write()') + ); } $this->_loaded[$name] = $logger; if ($enable) { @@ -63,7 +63,9 @@ class LogEngineCollection extends ObjectCollection { */ protected static function _getLogger($loggerName) { list($plugin, $loggerName) = pluginSplit($loggerName, true); - + if (substr($loggerName, -3) !== 'Log') { + $loggerName .= 'Log'; + } App::uses($loggerName, $plugin . 'Log/Engine'); if (!class_exists($loggerName)) { throw new CakeLogException(__d('cake_dev', 'Could not load class %s', $loggerName)); diff --git a/lib/Cake/Model/Behavior/AclBehavior.php b/lib/Cake/Model/Behavior/AclBehavior.php index 0fbc92a4e..f2234989f 100644 --- a/lib/Cake/Model/Behavior/AclBehavior.php +++ b/lib/Cake/Model/Behavior/AclBehavior.php @@ -65,7 +65,7 @@ class AclBehavior extends ModelBehavior { $model->{$type} = ClassRegistry::init($type); } if (!method_exists($model, 'parentNode')) { - trigger_error(__d('cake_dev', 'Callback parentNode() not defined in %s', $model->alias), E_USER_WARNING); + trigger_error(__d('cake_dev', 'Callback %s not defined in %s', 'parentNode()', $model->alias), E_USER_WARNING); } } diff --git a/lib/Cake/Model/Behavior/TreeBehavior.php b/lib/Cake/Model/Behavior/TreeBehavior.php index f57865ef7..fd8d3f724 100644 --- a/lib/Cake/Model/Behavior/TreeBehavior.php +++ b/lib/Cake/Model/Behavior/TreeBehavior.php @@ -628,19 +628,8 @@ class TreeBehavior extends ModelBehavior { $Model->updateAll(array($Model->escapeField($parent) => $missingParentAction), array($Model->escapeField($Model->primaryKey) => array_flip($missingParents))); } } - $count = 1; - foreach ($Model->find('all', array('conditions' => $scope, 'fields' => array($Model->primaryKey), 'order' => $left)) as $array) { - $lft = $count++; - $rght = $count++; - $Model->create(false); - $Model->id = $array[$Model->alias][$Model->primaryKey]; - $Model->save(array($left => $lft, $right => $rght), array('callbacks' => false, 'validate' => false)); - } - foreach ($Model->find('all', array('conditions' => $scope, 'fields' => array($Model->primaryKey, $parent), 'order' => $left)) as $array) { - $Model->create(false); - $Model->id = $array[$Model->alias][$Model->primaryKey]; - $this->_setParent($Model, $array[$Model->alias][$parent]); - } + + $this->_recoverByParentId($Model); } else { $db = ConnectionManager::getDataSource($Model->useDbConfig); foreach ($Model->find('all', array('conditions' => $scope, 'fields' => array($Model->primaryKey, $parent), 'order' => $left)) as $array) { @@ -655,6 +644,77 @@ class TreeBehavior extends ModelBehavior { return true; } +/** + * _recoverByParentId + * + * Recursive helper function used by recover + * + * @param Model $Model + * @param integer $counter + * @param mixed $parentId + * @return integer $counter + */ + protected function _recoverByParentId(Model $Model, $counter = 1, $parentId = null) { + $params = array( + 'conditions' => array( + $this->settings[$Model->alias]['parent'] => $parentId + ), + 'fields' => array($Model->primaryKey), + 'page' => 1, + 'limit' => 100, + 'order' => array($Model->primaryKey) + ); + + $scope = $this->settings[$Model->alias]['scope']; + if ($scope && ($scope !== '1 = 1' && $scope !== true)) { + $conditions[] = $scope; + } + + $children = $Model->find('all', $params); + $hasChildren = (bool)$children; + + if (!is_null($parentId)) { + if ($hasChildren) { + $Model->updateAll( + array($this->settings[$Model->alias]['left'] => $counter), + array($Model->escapeField() => $parentId) + ); + $counter++; + } else { + $Model->updateAll( + array( + $this->settings[$Model->alias]['left'] => $counter, + $this->settings[$Model->alias]['right'] => $counter + 1 + ), + array($Model->escapeField() => $parentId) + ); + $counter += 2; + } + } + + while ($children) { + foreach ($children as $row) { + $counter = $this->_recoverByParentId($Model, $counter, $row[$Model->alias][$Model->primaryKey]); + } + + if (count($children) !== $params['limit']) { + break; + } + $params['page']++; + $children = $Model->find('all', $params); + } + + if (!is_null($parentId) && $hasChildren) { + $Model->updateAll( + array($this->settings[$Model->alias]['right'] => $counter), + array($Model->escapeField() => $parentId) + ); + $counter++; + } + + return $counter; + } + /** * Reorder method. * @@ -729,10 +789,9 @@ class TreeBehavior extends ModelBehavior { if ($node[$right] == $node[$left] + 1) { if ($delete) { return $Model->delete($id); - } else { - $Model->id = $id; - return $Model->saveField($parent, null); } + $Model->id = $id; + return $Model->saveField($parent, null); } elseif ($node[$parent]) { list($parentNode) = array_values($Model->find('first', array( 'conditions' => array($scope, $Model->escapeField() => $node[$parent]), diff --git a/lib/Cake/Model/BehaviorCollection.php b/lib/Cake/Model/BehaviorCollection.php index 963ece11c..4813e7ed0 100644 --- a/lib/Cake/Model/BehaviorCollection.php +++ b/lib/Cake/Model/BehaviorCollection.php @@ -76,7 +76,7 @@ class BehaviorCollection extends ObjectCollection implements CakeEventListener { * @param string $behavior * @param array $config * @return void - * @deprecated Replaced with load() + * @deprecated Will be removed in 3.0. Replaced with load(). */ public function attach($behavior, $config = array()) { return $this->load($behavior, $config); @@ -103,7 +103,7 @@ class BehaviorCollection extends ObjectCollection implements CakeEventListener { * @throws MissingBehaviorException when a behavior could not be found. */ public function load($behavior, $config = array()) { - if (is_array($config) && isset($config['className'])) { + if (isset($config['className'])) { $alias = $behavior; $behavior = $config['className']; } @@ -206,7 +206,7 @@ class BehaviorCollection extends ObjectCollection implements CakeEventListener { * * @param string $name Name of behavior * @return void - * @deprecated Use unload instead. + * @deprecated Will be removed in 3.0. Use unload instead. */ public function detach($name) { return $this->unload($name); @@ -228,7 +228,7 @@ class BehaviorCollection extends ObjectCollection implements CakeEventListener { $method = $this->hasMethod($method, true); if ($strict && empty($method)) { - trigger_error(__d('cake_dev', "BehaviorCollection::dispatchMethod() - Method %s not found in any attached behavior", $method), E_USER_WARNING); + trigger_error(__d('cake_dev', '%s - Method %s not found in any attached behavior', 'BehaviorCollection::dispatchMethod()', $method), E_USER_WARNING); return null; } if (empty($method)) { diff --git a/lib/Cake/Model/CakeSchema.php b/lib/Cake/Model/CakeSchema.php index e16e3bbed..a860108fb 100644 --- a/lib/Cake/Model/CakeSchema.php +++ b/lib/Cake/Model/CakeSchema.php @@ -476,7 +476,7 @@ class CakeSchema extends Object { continue; } if (!array_key_exists($table, $old)) { - $tables[$table]['add'] = $fields; + $tables[$table]['create'] = $fields; } else { $diff = $this->_arrayDiffAssoc($fields, $old[$table]); if (!empty($diff)) { diff --git a/lib/Cake/Model/Datasource/CakeSession.php b/lib/Cake/Model/Datasource/CakeSession.php index c9f7f08b8..04d1dc9f5 100644 --- a/lib/Cake/Model/Datasource/CakeSession.php +++ b/lib/Cake/Model/Datasource/CakeSession.php @@ -132,7 +132,7 @@ class CakeSession { self::$time = time(); $checkAgent = Configure::read('Session.checkAgent'); - if (($checkAgent === true || $checkAgent === null) && env('HTTP_USER_AGENT')) { + if (env('HTTP_USER_AGENT')) { self::$_userAgent = md5(env('HTTP_USER_AGENT') . Configure::read('Security.salt')); } self::_setPath($base); @@ -486,10 +486,7 @@ class CakeSession { if (!empty($sessionConfig['ini']) && is_array($sessionConfig['ini'])) { foreach ($sessionConfig['ini'] as $setting => $value) { if (ini_set($setting, $value) === false) { - throw new CakeSessionException(sprintf( - __d('cake_dev', 'Unable to configure the session, setting %s failed.'), - $setting - )); + throw new CakeSessionException(__d('cake_dev', 'Unable to configure the session, setting %s failed.', $setting)); } } } diff --git a/lib/Cake/Model/Datasource/Database/Mysql.php b/lib/Cake/Model/Datasource/Database/Mysql.php index 839a4d2be..5211f3422 100644 --- a/lib/Cake/Model/Datasource/Database/Mysql.php +++ b/lib/Cake/Model/Datasource/Database/Mysql.php @@ -130,6 +130,15 @@ class Mysql extends DboSource { /** * Connects to the database using options in the given configuration array. * + * MySQL supports a few additional options that other drivers do not: + * + * - `unix_socket` Set to the path of the MySQL sock file. Can be used in place + * of host + port. + * - `ssl_key` SSL key file for connecting via SSL. Must be combined with `ssl_cert`. + * - `ssl_cert` The SSL certificate to use when connecting via SSL. Must be + * combined with `ssl_key`. + * - `ssl_ca` The certificate authority for SSL connections. + * * @return boolean True if the database could be connected, else false * @throws MissingConnectionException */ @@ -146,7 +155,13 @@ class Mysql extends DboSource { if (!empty($config['encoding'])) { $flags[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES ' . $config['encoding']; } - + if (!empty($config['ssl_key']) && !empty($config['ssl_cert'])) { + $flags[PDO::MYSQL_ATTR_SSL_KEY] = $config['ssl_key']; + $flags[PDO::MYSQL_ATTR_SSL_CERT] = $config['ssl_cert']; + } + if (!empty($config['ssl_ca'])) { + $flags[PDO::MYSQL_ATTR_SSL_CA] = $config['ssl_ca']; + } if (empty($config['unix_socket'])) { $dsn = "mysql:host={$config['host']};port={$config['port']};dbname={$config['database']}"; } else { @@ -161,6 +176,11 @@ class Mysql extends DboSource { $flags ); $this->connected = true; + if (!empty($config['settings'])) { + foreach ($config['settings'] as $key => $value) { + $this->_execute("SET $key=$value"); + } + } } catch (PDOException $e) { throw new MissingConnectionException(array( 'class' => get_class($this), diff --git a/lib/Cake/Model/Datasource/Database/Postgres.php b/lib/Cake/Model/Datasource/Database/Postgres.php index 00093b052..faad9c1b8 100644 --- a/lib/Cake/Model/Datasource/Database/Postgres.php +++ b/lib/Cake/Model/Datasource/Database/Postgres.php @@ -131,6 +131,11 @@ class Postgres extends DboSource { if (!empty($config['schema'])) { $this->_execute('SET search_path TO ' . $config['schema']); } + if (!empty($config['settings'])) { + foreach ($config['settings'] as $key => $value) { + $this->_execute("SET $key TO $value"); + } + } } catch (PDOException $e) { throw new MissingConnectionException(array( 'class' => get_class($this), diff --git a/lib/Cake/Model/Datasource/Database/Sqlserver.php b/lib/Cake/Model/Datasource/Database/Sqlserver.php index a1eab7210..788885dbc 100644 --- a/lib/Cake/Model/Datasource/Database/Sqlserver.php +++ b/lib/Cake/Model/Datasource/Database/Sqlserver.php @@ -135,6 +135,11 @@ class Sqlserver extends DboSource { $flags ); $this->connected = true; + if (!empty($config['settings'])) { + foreach ($config['settings'] as $key => $value) { + $this->_execute("SET $key $value"); + } + } } catch (PDOException $e) { throw new MissingConnectionException(array( 'class' => get_class($this), diff --git a/lib/Cake/Model/Datasource/DboSource.php b/lib/Cake/Model/Datasource/DboSource.php index 2994ed808..41482cd39 100644 --- a/lib/Cake/Model/Datasource/DboSource.php +++ b/lib/Cake/Model/Datasource/DboSource.php @@ -898,7 +898,7 @@ class DboSource extends DataSource { if (PHP_SAPI !== 'cli') { $controller = null; $View = new View($controller, false); - $View->set('logs', array($this->configKeyName => $log)); + $View->set('sqlLogs', array($this->configKeyName => $log)); echo $View->element('sql_dump', array('_forced_from_dbo_' => true)); } else { foreach ($log['log'] as $k => $i) { @@ -2489,7 +2489,7 @@ class DboSource extends DataSource { $keys = array_keys($value); if ($keys === array_values($keys)) { $count = count($value); - if ($count === 1 && !preg_match("/\s+NOT$/", $key)) { + if ($count === 1 && !preg_match('/\s+(?:NOT|\!=)$/', $key)) { $data = $this->_quoteFields($key) . ' = ('; if ($quoteValues) { if (is_object($model)) { diff --git a/lib/Cake/Model/Model.php b/lib/Cake/Model/Model.php index 67289c151..a48c730f3 100644 --- a/lib/Cake/Model/Model.php +++ b/lib/Cake/Model/Model.php @@ -1497,6 +1497,17 @@ class Model extends Object implements CakeEventListener { return $this->data; } +/** + * This function is a convenient wrapper class to create(false) and, as the name suggests, clears the id, data, and validation errors. + * + * @return always boolean TRUE upon success + * @see Model::create() + */ + public function clear() { + $this->create(false); + return true; + } + /** * Returns a list of fields from the database, and sets the current model * data (Model::$data) with the record found. @@ -1575,7 +1586,8 @@ class Model extends Object implements CakeEventListener { * @param mixed $value Value of the field * @param boolean|array $validate Either a boolean, or an array. * If a boolean, indicates whether or not to validate before saving. - * If an array, allows control of 'validate' and 'callbacks' options. + * If an array, allows control of 'validate', 'callbacks' and 'counterCache' options. + * See Model::save() for details of each options. * @return boolean See Model::save() * @see Model::save() * @link http://book.cakephp.org/2.0/en/models/saving-your-data.html#model-savefield-string-fieldname-string-fieldvalue-validate-false @@ -1604,13 +1616,17 @@ class Model extends Object implements CakeEventListener { * - fieldList: An array of fields you want to allow for saving. * - callbacks: Set to false to disable callbacks. Using 'before' or 'after' * will enable only those callbacks. + * - `counterCache`: Boolean to control updating of counter caches (if any) * * @param array $fieldList List of fields to allow to be saved * @return mixed On success Model::$data if its not empty or true, false on failure * @link http://book.cakephp.org/2.0/en/models/saving-your-data.html */ public function save($data = null, $validate = true, $fieldList = array()) { - $defaults = array('validate' => true, 'fieldList' => array(), 'callbacks' => true); + $defaults = array( + 'validate' => true, 'fieldList' => array(), + 'callbacks' => true, 'counterCache' => true + ); $_whitelist = $this->whitelist; $fields = array(); @@ -1749,7 +1765,7 @@ class Model extends Object implements CakeEventListener { } } - if ($success && !empty($this->belongsTo)) { + if ($success && $options['counterCache'] && !empty($this->belongsTo)) { $this->updateCounterCache($cache, $created); } } @@ -2030,7 +2046,9 @@ class Model extends Object implements CakeEventListener { * 'AssociatedModel' => array('field', 'otherfield') * ) * }}} - * - `deep`: see saveMany/saveAssociated + * - `deep`: See saveMany/saveAssociated + * - `callbacks`: See Model::save() + * - `counterCache`: See Model::save() * * @param array $data Record data to save. This can be either a numerically-indexed array (for saving multiple * records of the same type), or an array indexed by association name. @@ -2066,6 +2084,8 @@ class Model extends Object implements CakeEventListener { * Should be set to false if database/table does not support transactions. * - `fieldList`: Equivalent to the $fieldList parameter in Model::save() * - `deep`: If set to true, all associated data will be saved as well. + * - `callbacks`: See Model::save() + * - `counterCache`: See Model::save() * * @param array $data Record data to save. This should be a numerically-indexed array * @param array $options Options to use when saving record data, See $options above. @@ -2179,6 +2199,8 @@ class Model extends Object implements CakeEventListener { * ) * }}} * - `deep`: If set to true, not only directly associated data is saved, but deeper nested associated data as well. + * - `callbacks`: See Model::save() + * - `counterCache`: See Model::save() * * @param array $data Record data to save. This should be an array indexed by association name. * @param array $options Options to use when saving record data, See $options above. @@ -2697,6 +2719,34 @@ class Model extends Object implements CakeEventListener { return null; } + return $this->_readDataSource($type, $query); + } + +/** + * Read from the datasource + * + * Model::_readDataSource() is used by all find() calls to read from the data source and can be overloaded to allow + * caching of datasource calls. + * + * {{{ + * protected function _readDataSource($type, $query) { + * $cacheName = md5(json_encode($query)); + * $cache = Cache::read($cacheName, 'cache-config-name'); + * if ($cache !== false) { + * return $cache; + * } + * + * $results = parent::_readDataSource($type, $query); + * Cache::write($cacheName, $results, 'cache-config-name'); + * return $results; + * } + * }}} + * + * @param string $type Type of find operation (all / first / count / neighbors / list / threaded) + * @param array $query Option fields (conditions / fields / joins / limit / offset / order / page / group / callbacks) + * @return array + */ + protected function _readDataSource($type, $query) { $results = $this->getDataSource()->read($this, $query); $this->resetAssociations(); @@ -2706,10 +2756,6 @@ class Model extends Object implements CakeEventListener { $this->findQueryType = null; - if ($type === 'all') { - return $results; - } - if ($this->findMethods[$type] === true) { return $this->{'_find' . ucfirst($type)}('after', $query, $results); } @@ -2732,7 +2778,7 @@ class Model extends Object implements CakeEventListener { (array)$query ); - if ($type !== 'all' && $this->findMethods[$type] === true) { + if ($this->findMethods[$type] === true) { $query = $this->{'_find' . ucfirst($type)}('before', $query); } @@ -2760,6 +2806,23 @@ class Model extends Object implements CakeEventListener { return $query; } +/** + * Handles the before/after filter logic for find('all') operations. Only called by Model::find(). + * + * @param string $state Either "before" or "after" + * @param array $query + * @param array $results + * @return array + * @see Model::find() + */ + protected function _findAll($state, $query, $results = array()) { + if ($state === 'before') { + return $query; + } elseif ($state === 'after') { + return $results; + } + } + /** * Handles the before/after filter logic for find('first') operations. Only called by Model::find(). * diff --git a/lib/Cake/Model/Permission.php b/lib/Cake/Model/Permission.php index 16572eb57..1de294a78 100644 --- a/lib/Cake/Model/Permission.php +++ b/lib/Cake/Model/Permission.php @@ -26,13 +26,6 @@ App::uses('AppModel', 'Model'); */ class Permission extends AppModel { -/** - * Model name - * - * @var string - */ - public $name = 'Permission'; - /** * Explicitly disable in-memory query caching * @@ -81,7 +74,7 @@ class Permission extends AppModel { * @param string $action Action (defaults to *) * @return boolean Success (true if ARO has access to action in ACO, false otherwise) */ - public function check($aro, $aco, $action = "*") { + public function check($aro, $aco, $action = '*') { if (!$aro || !$aco) { return false; } @@ -91,17 +84,29 @@ class Permission extends AppModel { $acoPath = $this->Aco->node($aco); if (!$aroPath || !$acoPath) { - trigger_error(__d('cake_dev', "DbAcl::check() - Failed ARO/ACO node lookup in permissions check. Node references:\nAro: ") . print_r($aro, true) . "\nAco: " . print_r($aco, true), E_USER_WARNING); + trigger_error(__d('cake_dev', + "%s - Failed ARO/ACO node lookup in permissions check. Node references:\nAro: %s\nAco: %s", + 'DbAcl::check()', + print_r($aro, true), + print_r($aco, true)), + E_USER_WARNING + ); return false; } if (!$acoPath) { - trigger_error(__d('cake_dev', "DbAcl::check() - Failed ACO node lookup in permissions check. Node references:\nAro: ") . print_r($aro, true) . "\nAco: " . print_r($aco, true), E_USER_WARNING); + trigger_error(__d('cake_dev', + "%s - Failed ACO node lookup in permissions check. Node references:\nAro: %s\nAco: %s", + 'DbAcl::check()', + print_r($aro, true), + print_r($aco, true)), + E_USER_WARNING + ); return false; } if ($action !== '*' && !in_array('_' . $action, $permKeys)) { - trigger_error(__d('cake_dev', "ACO permissions key %s does not exist in DbAcl::check()", $action), E_USER_NOTICE); + trigger_error(__d('cake_dev', "ACO permissions key %s does not exist in %s", $action, 'DbAcl::check()'), E_USER_NOTICE); return false; } @@ -166,20 +171,20 @@ class Permission extends AppModel { * @return boolean Success * @throws AclException on Invalid permission key. */ - public function allow($aro, $aco, $actions = "*", $value = 1) { + public function allow($aro, $aco, $actions = '*', $value = 1) { $perms = $this->getAclLink($aro, $aco); $permKeys = $this->getAcoKeys($this->schema()); $save = array(); if (!$perms) { - trigger_error(__d('cake_dev', 'DbAcl::allow() - Invalid node'), E_USER_WARNING); + trigger_error(__d('cake_dev', '%s - Invalid node', 'DbAcl::allow()'), E_USER_WARNING); return false; } if (isset($perms[0])) { $save = $perms[0][$this->alias]; } - if ($actions === "*") { + if ($actions === '*') { $save = array_combine($permKeys, array_pad(array(), count($permKeys), $value)); } else { if (!is_array($actions)) { diff --git a/lib/Cake/Network/CakeRequest.php b/lib/Cake/Network/CakeRequest.php index 2dac6cdec..fb7ccc04d 100644 --- a/lib/Cake/Network/CakeRequest.php +++ b/lib/Cake/Network/CakeRequest.php @@ -238,7 +238,7 @@ class CakeRequest implements ArrayAccess { if ($qPosition !== false && strpos($_SERVER['REQUEST_URI'], '://') > $qPosition) { $uri = $_SERVER['REQUEST_URI']; } else { - $uri = substr($_SERVER['REQUEST_URI'], strlen(FULL_BASE_URL)); + $uri = substr($_SERVER['REQUEST_URI'], strlen(Configure::read('App.fullBaseUrl'))); } } elseif (isset($_SERVER['PHP_SELF']) && isset($_SERVER['SCRIPT_NAME'])) { $uri = str_replace($_SERVER['SCRIPT_NAME'], '', $_SERVER['PHP_SELF']); @@ -424,10 +424,7 @@ class CakeRequest implements ArrayAccess { $ref = $forwarded; } - $base = ''; - if (defined('FULL_BASE_URL')) { - $base = FULL_BASE_URL . $this->webroot; - } + $base = Configure::read('App.fullBaseUrl') . $this->webroot; if (!empty($ref) && !empty($base)) { if ($local && strpos($ref, $base) === 0) { $ref = substr($ref, strlen($base)); @@ -485,14 +482,21 @@ class CakeRequest implements ArrayAccess { } /** - * Check whether or not a Request is a certain type. Uses the built in detection rules - * as well as additional rules defined with CakeRequest::addDetector(). Any detector can be called + * Check whether or not a Request is a certain type. + * + * Uses the built in detection rules as well as additional rules + * defined with CakeRequest::addDetector(). Any detector can be called * as `is($type)` or `is$Type()`. * - * @param string $type The type of request you want to check. + * @param string|array $type The type of request you want to check. If an array + * this method will return true if the request matches any type. * @return boolean Whether or not the request is the type you are checking. */ public function is($type) { + if (is_array($type)) { + $result = array_map(array($this, 'is'), $type); + return count(array_filter($result)) > 0; + } $type = strtolower($type); if (!isset($this->_detectors[$type])) { return false; @@ -521,6 +525,22 @@ class CakeRequest implements ArrayAccess { return false; } +/** + * Check that a request matches all the given types. + * + * Allows you to test multiple types and union the results. + * See CakeRequest::is() for how to add additional types and the + * built-in types. + * + * @param array $types The types to check. + * @return boolean Success. + * @see CakeRequest::is() + */ + public function isAll(array $types) { + $result = array_filter(array_map(array($this, 'is'), $types)); + return count($result) === count($types); + } + /** * Add a new detector to the list of detectors that a request can use. * There are several different formats and types of detectors that can be set. @@ -825,6 +845,20 @@ class CakeRequest implements ArrayAccess { return Hash::get($this->data, $name); } +/** + * Safely access the values in $this->params. + * + * @param string $name The name of the parameter to get. + * @return mixed The value of the provided parameter. Will + * return false if the parameter doesn't exist or is falsey. + */ + public function param($name) { + if (!isset($this->params[$name])) { + return false; + } + return $this->params[$name]; + } + /** * Read data from `php://input`. Useful when interacting with XML or JSON * request body content. diff --git a/lib/Cake/Network/CakeResponse.php b/lib/Cake/Network/CakeResponse.php index bf9b5ace3..c63623d17 100644 --- a/lib/Cake/Network/CakeResponse.php +++ b/lib/Cake/Network/CakeResponse.php @@ -416,8 +416,10 @@ class CakeResponse { $this->_setContent(); $this->_setContentLength(); $this->_setContentType(); - foreach ($this->_headers as $header => $value) { - $this->_sendHeader($header, $value); + foreach ($this->_headers as $header => $values) { + foreach ((array)$values as $value) { + $this->_sendHeader($header, $value); + } } if ($this->_file) { $this->_sendFile($this->_file, $this->_fileRange); @@ -556,34 +558,42 @@ class CakeResponse { * @param string|array $header. An array of header strings or a single header string * - an associative array of "header name" => "header value" is also accepted * - an array of string headers is also accepted - * @param string $value. The header value. + * @param string|array $value. The header value(s) * @return array list of headers to be sent */ public function header($header = null, $value = null) { if ($header === null) { return $this->_headers; } - if (is_array($header)) { - foreach ($header as $h => $v) { - if (is_numeric($h)) { - $this->header($v); - continue; - } - $this->_headers[$h] = trim($v); + $headers = is_array($header) ? $header : array($header => $value); + foreach ($headers as $header => $value) { + if (is_numeric($header)) { + list($header, $value) = array($value, null); } - return $this->_headers; + if (is_null($value)) { + list($header, $value) = explode(':', $header, 2); + } + $this->_headers[$header] = is_array($value) ? array_map('trim', $value) : trim($value); } - - if ($value !== null) { - $this->_headers[$header] = $value; - return $this->_headers; - } - - list($header, $value) = explode(':', $header, 2); - $this->_headers[$header] = trim($value); return $this->_headers; } +/** + * Acccessor for the location header. + * + * Get/Set the Location header value. + * @param null|string $url Either null to get the current location, or a string to set one. + * @return string|null When setting the location null will be returned. When reading the location + * a string of the current location header value (if any) will be returned. + */ + public function location($url = null) { + if ($url === null) { + $headers = $this->header(); + return isset($headers['Location']) ? $headers['Location'] : null; + } + $this->header('Location', $url); + } + /** * Buffers the response message to be sent * if $content is null the current buffer is returned @@ -602,7 +612,7 @@ class CakeResponse { * Sets the HTTP status code to be sent * if $code is null the current code is returned * - * @param integer $code + * @param integer $code the HTTP status code * @return integer current status code * @throws CakeException When an unknown status code is reached. */ @@ -619,31 +629,47 @@ class CakeResponse { /** * Queries & sets valid HTTP response codes & messages. * - * @param integer|array $code If $code is an integer, then the corresponding code/message is - * returned if it exists, null if it does not exist. If $code is an array, - * then the 'code' and 'message' keys of each nested array are added to the default - * HTTP codes. Example: + * @param integer|array $code If $code is an integer, then the corresponding code/message is + * returned if it exists, null if it does not exist. If $code is an array, then the + * keys are used as codes and the values as messages to add to the default HTTP + * codes. The codes must be integers greater than 99 and less than 1000. Keep in + * mind that the HTTP specification outlines that status codes begin with a digit + * between 1 and 5, which defines the class of response the client is to expect. + * Example: * * httpCodes(404); // returns array(404 => 'Not Found') * * httpCodes(array( - * 701 => 'Unicorn Moved', - * 800 => 'Unexpected Minotaur' + * 381 => 'Unicorn Moved', + * 555 => 'Unexpected Minotaur' * )); // sets these new values, and returns true * + * httpCodes(array( + * 0 => 'Nothing Here', + * -1 => 'Reverse Infinity', + * 12345 => 'Universal Password', + * 'Hello' => 'World' + * )); // throws an exception due to invalid codes + * + * For more on HTTP status codes see: http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1 + * * @return mixed associative array of the HTTP codes as keys, and the message * strings as values, or null of the given $code does not exist. + * @throws CakeException If an attempt is made to add an invalid status code */ public function httpCodes($code = null) { if (empty($code)) { return $this->_statusCodes; } - if (is_array($code)) { + $codes = array_keys($code); + $min = min($codes); + if (!is_int($min) || $min < 100 || max($codes) > 999) { + throw new CakeException(__d('cake_dev', 'Invalid status code')); + } $this->_statusCodes = $code + $this->_statusCodes; return true; } - if (!isset($this->_statusCodes[$code])) { return null; } @@ -724,9 +750,7 @@ class CakeResponse { } foreach ($this->_mimeTypes as $alias => $types) { - if (is_array($types) && in_array($ctype, $types)) { - return $alias; - } elseif (is_string($types) && $types == $ctype) { + if (in_array($ctype, (array)$types)) { return $alias; } } diff --git a/lib/Cake/Network/CakeSocket.php b/lib/Cake/Network/CakeSocket.php index 3fcebad6e..58ce159d4 100644 --- a/lib/Cake/Network/CakeSocket.php +++ b/lib/Cake/Network/CakeSocket.php @@ -184,7 +184,7 @@ class CakeSocket { * * Instead we need to handle those errors manually. * - * @param int $code + * @param integer $code * @param string $message * @return void */ diff --git a/lib/Cake/Network/Email/CakeEmail.php b/lib/Cake/Network/Email/CakeEmail.php index a40fff59b..73ae71285 100644 --- a/lib/Cake/Network/Email/CakeEmail.php +++ b/lib/Cake/Network/Email/CakeEmail.php @@ -318,6 +318,14 @@ class CakeEmail { 'ISO-2022-JP-MS' => 'ISO-2022-JP' ); +/** + * Regex for email validation + * If null, it will use built in regex + * + * @var string + */ + protected $_emailPattern = null; + /** * Constructor * @@ -521,6 +529,20 @@ class CakeEmail { return $this->headerCharset = $charset; } +/** + * EmailPattern setter/getter + * + * @param string $regex for email address validation + * @return string|CakeEmail + */ + public function emailPattern($regex = null) { + if ($regex === null) { + return $this->_emailPattern; + } + $this->_emailPattern = $regex; + return $this; + } + /** * Set email * @@ -532,7 +554,7 @@ class CakeEmail { */ protected function _setEmail($varName, $email, $name) { if (!is_array($email)) { - if (!Validation::email($email)) { + if (!Validation::email($email, false, $this->_emailPattern)) { throw new SocketException(__d('cake_dev', 'Invalid email: "%s"', $email)); } if ($name === null) { @@ -546,7 +568,7 @@ class CakeEmail { if (is_int($key)) { $key = $value; } - if (!Validation::email($key)) { + if (!Validation::email($key, false, $this->_emailPattern)) { throw new SocketException(__d('cake_dev', 'Invalid email: "%s"', $key)); } $list[$key] = $value; @@ -586,7 +608,7 @@ class CakeEmail { */ protected function _addEmail($varName, $email, $name) { if (!is_array($email)) { - if (!Validation::email($email)) { + if (!Validation::email($email, false, $this->_emailPattern)) { throw new SocketException(__d('cake_dev', 'Invalid email: "%s"', $email)); } if ($name === null) { @@ -600,7 +622,7 @@ class CakeEmail { if (is_int($key)) { $key = $value; } - if (!Validation::email($key)) { + if (!Validation::email($key, false, $this->_emailPattern)) { throw new SocketException(__d('cake_dev', 'Invalid email: "%s"', $key)); } $list[$key] = $value; @@ -884,7 +906,7 @@ class CakeEmail { if (!class_exists($transportClassname)) { throw new SocketException(__d('cake_dev', 'Class "%s" not found.', $transportClassname)); } elseif (!method_exists($transportClassname, 'send')) { - throw new SocketException(__d('cake_dev', 'The "%s" do not have send method.', $transportClassname)); + throw new SocketException(__d('cake_dev', 'The "%s" does not have a %s method.', $transportClassname, 'send()')); } return $this->_transportClass = new $transportClassname(); @@ -953,6 +975,15 @@ class CakeEmail { * 'contentDisposition' => false * )); * }}} + * + * Attach a file from string and specify additional properties: + * + * {{{ + * $email->attachments(array('custom_name.png' => array( + * 'data' => file_get_contents('path/to/file'), + * 'mimetype' => 'image/png' + * )); + * }}} * * The `contentId` key allows you to specify an inline attachment. In your email text, you * can use `` to display the image inline. @@ -974,14 +1005,21 @@ class CakeEmail { $fileInfo = array('file' => $fileInfo); } if (!isset($fileInfo['file'])) { - throw new SocketException(__d('cake_dev', 'File not specified.')); - } - $fileInfo['file'] = realpath($fileInfo['file']); - if ($fileInfo['file'] === false || !file_exists($fileInfo['file'])) { - throw new SocketException(__d('cake_dev', 'File not found: "%s"', $fileInfo['file'])); - } - if (is_int($name)) { - $name = basename($fileInfo['file']); + if (!isset($fileInfo['data'])) { + throw new SocketException(__d('cake_dev', 'No file or data specified.')); + } + if (is_int($name)) { + throw new SocketException(__d('cake_dev', 'No filename specified.')); + } + $fileInfo['data'] = chunk_split(base64_encode($fileInfo['data']), 76, "\r\n"); + } else { + $fileInfo['file'] = realpath($fileInfo['file']); + if ($fileInfo['file'] === false || !file_exists($fileInfo['file'])) { + throw new SocketException(__d('cake_dev', 'File not found: "%s"', $fileInfo['file'])); + } + if (is_int($name)) { + $name = basename($fileInfo['file']); + } } if (!isset($fileInfo['mimetype'])) { $fileInfo['mimetype'] = 'application/octet-stream'; @@ -1074,11 +1112,21 @@ class CakeEmail { $contents = $this->transportClass()->send($this); if (!empty($this->_config['log'])) { - $level = LOG_DEBUG; + $config = array( + 'level' => LOG_DEBUG, + 'scope' => 'email' + ); if ($this->_config['log'] !== true) { - $level = $this->_config['log']; + if (!is_array($this->_config['log'])) { + $this->_config['log'] = array('level' => $this->_config['log']); + } + $config = array_merge($config, $this->_config['log']); } - CakeLog::write($level, PHP_EOL . $contents['headers'] . PHP_EOL . $contents['message']); + CakeLog::write( + $config['level'], + PHP_EOL . $contents['headers'] . PHP_EOL . $contents['message'], + $config['scope'] + ); } return $contents; } @@ -1149,7 +1197,7 @@ class CakeEmail { $simpleMethods = array( 'from', 'sender', 'to', 'replyTo', 'readReceipt', 'returnPath', 'cc', 'bcc', 'messageId', 'domain', 'subject', 'viewRender', 'viewVars', 'attachments', - 'transport', 'emailFormat', 'theme', 'helpers' + 'transport', 'emailFormat', 'theme', 'helpers', 'emailPattern' ); foreach ($simpleMethods as $method) { if (isset($config[$method])) { @@ -1206,6 +1254,7 @@ class CakeEmail { $this->headerCharset = null; $this->_attachments = array(); $this->_config = array(); + $this->_emailPattern = null; return $this; } @@ -1376,7 +1425,7 @@ class CakeEmail { if (!empty($fileInfo['contentId'])) { continue; } - $data = $this->_readFile($fileInfo['file']); + $data = isset($fileInfo['data']) ? $fileInfo['data'] : $this->_readFile($fileInfo['file']); $msg[] = '--' . $boundary; $msg[] = 'Content-Type: ' . $fileInfo['mimetype']; @@ -1421,7 +1470,7 @@ class CakeEmail { if (empty($fileInfo['contentId'])) { continue; } - $data = $this->_readFile($fileInfo['file']); + $data = isset($fileInfo['data']) ? $fileInfo['data'] : $this->_readFile($fileInfo['file']); $msg[] = '--' . $boundary; $msg[] = 'Content-Type: ' . $fileInfo['mimetype']; diff --git a/lib/Cake/Network/Http/HttpSocket.php b/lib/Cake/Network/Http/HttpSocket.php index d61204958..07552a57f 100644 --- a/lib/Cake/Network/Http/HttpSocket.php +++ b/lib/Cake/Network/Http/HttpSocket.php @@ -96,6 +96,7 @@ class HttpSocket extends CakeSocket { 'port' => 80, 'timeout' => 30, 'ssl_verify_peer' => true, + 'ssl_allow_self_signed' => false, 'ssl_verify_depth' => 5, 'ssl_verify_host' => true, 'request' => array( @@ -495,6 +496,19 @@ class HttpSocket extends CakeSocket { return $this->request($request); } +/** + * Issues a PATCH request to the specified URI, query, and request. + * + * @param string|array $uri URI to request, See HttpSocket::_parseUri() + * @param array $data Array of PATCH data keys and values. + * @param array $request An indexed array with indexes such as 'method' or uri + * @return mixed Result of request + */ + public function patch($uri = null, $data = array(), $request = array()) { + $request = Hash::merge(array('method' => 'PATCH', 'uri' => $uri, 'body' => $data), $request); + return $this->request($request); + } + /** * Issues a DELETE request to the specified URI, query, and request. * @@ -590,7 +604,7 @@ class HttpSocket extends CakeSocket { throw new SocketException(__d('cake_dev', 'Unknown authentication method.')); } if (!method_exists($authClass, 'authentication')) { - throw new SocketException(sprintf(__d('cake_dev', 'The %s do not support authentication.'), $authClass)); + throw new SocketException(__d('cake_dev', 'The %s does not support authentication.', $authClass)); } call_user_func_array("$authClass::authentication", array($this, &$this->_auth[$method])); } @@ -619,7 +633,7 @@ class HttpSocket extends CakeSocket { throw new SocketException(__d('cake_dev', 'Unknown authentication method for proxy.')); } if (!method_exists($authClass, 'proxyAuthentication')) { - throw new SocketException(sprintf(__d('cake_dev', 'The %s do not support proxy authentication.'), $authClass)); + throw new SocketException(__d('cake_dev', 'The %s does not support proxy authentication.', $authClass)); } call_user_func_array("$authClass::proxyAuthentication", array($this, &$this->_proxy)); } diff --git a/lib/Cake/Routing/Router.php b/lib/Cake/Routing/Router.php index 420e64c01..6d37d3d20 100644 --- a/lib/Cake/Routing/Router.php +++ b/lib/Cake/Routing/Router.php @@ -56,6 +56,14 @@ class Router { */ public static $initialized = false; +/** + * Contains the base string that will be applied to all generated URLs + * For example `https://example.com` + * + * @var string + */ + protected static $_fullBaseUrl; + /** * List of action prefixes used in connected routes. * Includes admin prefix @@ -551,7 +559,8 @@ class Router { $url = '/' . $url; } if (strpos($url, '?') !== false) { - $url = substr($url, 0, strpos($url, '?')); + list($url, $queryParameters) = explode('?', $url, 2); + parse_str($queryParameters, $queryParameters); } extract(self::_parseExtension($url)); @@ -572,6 +581,10 @@ class Router { if (!empty($ext) && !isset($out['ext'])) { $out['ext'] = $ext; } + + if (!empty($queryParameters) && !isset($out['?'])) { + $out['?'] = $queryParameters; + } return $out; } @@ -759,7 +772,7 @@ class Router { * cake relative URLs are required when using requestAction. * - `?` - Takes an array of query string parameters * - `#` - Allows you to set URL hash fragments. - * - `full_base` - If true the `FULL_BASE_URL` constant will be prepended to generated URLs. + * - `full_base` - If true the `Router::fullBaseUrl()` value will be prepended to generated URLs. * * @param string|array $url Cake-relative URL, like "/products/edit/92" or "/presidents/elect/4" * or an array specifying any of the following: 'controller', 'action', @@ -796,8 +809,8 @@ class Router { if (empty($url)) { $output = isset($path['here']) ? $path['here'] : '/'; - if ($full && defined('FULL_BASE_URL')) { - $output = FULL_BASE_URL . $output; + if ($full) { + $output = self::fullBaseUrl() . $output; } return $output; } elseif (is_array($url)) { @@ -860,7 +873,7 @@ class Router { $output = self::_handleNoRoute($url); } } else { - if (preg_match('/:\/\/|^(javascript|mailto|tel|sms):|^\#/i', $url)) { + if (preg_match('/^([a-z][a-z0-9.+\-]+:|:?\/\/|[#?])/i', $url)) { return $url; } if (substr($url, 0, 1) === '/') { @@ -878,12 +891,12 @@ class Router { $output .= Inflector::underscore($params['controller']) . '/' . $url; } } - $protocol = preg_match('#^[a-z][a-z0-9+-.]*\://#i', $output); + $protocol = preg_match('#^[a-z][a-z0-9+\-.]*\://#i', $output); if ($protocol === 0) { $output = str_replace('//', '/', $base . '/' . $output); - if ($full && defined('FULL_BASE_URL')) { - $output = FULL_BASE_URL . $output; + if ($full) { + $output = self::fullBaseUrl() . $output; } if (!empty($extension)) { $output = rtrim($output, '/'); @@ -892,6 +905,32 @@ class Router { return $output . $extension . self::queryString($q, array(), $escape) . $frag; } +/** + * Sets the full base url that will be used as a prefix for generating + * fully qualified URLs for this application. If not parameters are passed, + * the currently configured value is returned. + * + * ## Note: + * + * If you change the configuration value ``App.fullBaseUrl`` during runtime + * and expect the router to produce links using the new setting, you are + * required to call this method passing such value again. + * + * @param string $base the prefix for URLs generated containing the domain. + * For example: ``http://example.com`` + * @return string + */ + public static function fullBaseUrl($base = null) { + if ($base !== null) { + self::$_fullBaseUrl = $base; + Configure::write('App.fullBaseUrl', $base); + } + if (empty(self::$_fullBaseUrl)) { + self::$_fullBaseUrl = Configure::read('App.fullBaseUrl'); + } + return self::$_fullBaseUrl; + } + /** * A special fallback method that handles URL arrays that cannot match * any defined routes. diff --git a/lib/Cake/Test/Case/BasicsTest.php b/lib/Cake/Test/Case/BasicsTest.php index 914e096a9..b6e752b01 100644 --- a/lib/Cake/Test/Case/BasicsTest.php +++ b/lib/Cake/Test/Case/BasicsTest.php @@ -703,8 +703,9 @@ class BasicsTest extends CakeTestCase { ########## DEBUG ########## 'this-is-a-test' ########################### + EXPECTED; - $expected = sprintf($expectedText, str_replace(CAKE_CORE_INCLUDE_PATH, '', __FILE__), __LINE__ - 8); + $expected = sprintf($expectedText, str_replace(CAKE_CORE_INCLUDE_PATH, '', __FILE__), __LINE__ - 9); $this->assertEquals($expected, $result); @@ -766,9 +767,10 @@ EXPECTED; ########## DEBUG ########## '
this-is-a-test
' ########################### + EXPECTED; if (php_sapi_name() === 'cli') { - $expected = sprintf($expectedText, str_replace(CAKE_CORE_INCLUDE_PATH, '', __FILE__), __LINE__ - 17); + $expected = sprintf($expectedText, str_replace(CAKE_CORE_INCLUDE_PATH, '', __FILE__), __LINE__ - 18); } else { $expected = sprintf($expectedHtml, str_replace(CAKE_CORE_INCLUDE_PATH, '', __FILE__), __LINE__ - 19); } @@ -790,9 +792,10 @@ EXPECTED; ########## DEBUG ########## '
this-is-a-test
' ########################### + EXPECTED; if (php_sapi_name() === 'cli') { - $expected = sprintf($expectedText, str_replace(CAKE_CORE_INCLUDE_PATH, '', __FILE__), __LINE__ - 17); + $expected = sprintf($expectedText, str_replace(CAKE_CORE_INCLUDE_PATH, '', __FILE__), __LINE__ - 18); } else { $expected = sprintf($expectedHtml, str_replace(CAKE_CORE_INCLUDE_PATH, '', __FILE__), __LINE__ - 19); } @@ -806,8 +809,9 @@ EXPECTED; ########## DEBUG ########## '
this-is-a-test
' ########################### + EXPECTED; - $expected = sprintf($expected, str_replace(CAKE_CORE_INCLUDE_PATH, '', __FILE__), __LINE__ - 8); + $expected = sprintf($expected, str_replace(CAKE_CORE_INCLUDE_PATH, '', __FILE__), __LINE__ - 9); $this->assertEquals($expected, $result); ob_start(); @@ -818,8 +822,9 @@ EXPECTED; ########## DEBUG ########## '
this-is-a-test
' ########################### + EXPECTED; - $expected = sprintf($expected, str_replace(CAKE_CORE_INCLUDE_PATH, '', __FILE__), __LINE__ - 8); + $expected = sprintf($expected, str_replace(CAKE_CORE_INCLUDE_PATH, '', __FILE__), __LINE__ - 9); $this->assertEquals($expected, $result); ob_start(); @@ -830,8 +835,9 @@ EXPECTED; ########## DEBUG ########## '
this-is-a-test
' ########################### + EXPECTED; - $expected = sprintf($expected, str_replace(CAKE_CORE_INCLUDE_PATH, '', __FILE__), __LINE__ - 8); + $expected = sprintf($expected, str_replace(CAKE_CORE_INCLUDE_PATH, '', __FILE__), __LINE__ - 9); $this->assertEquals($expected, $result); ob_start(); @@ -842,8 +848,9 @@ EXPECTED; ########## DEBUG ########## false ########################### + EXPECTED; - $expected = sprintf($expected, str_replace(CAKE_CORE_INCLUDE_PATH, '', __FILE__), __LINE__ - 8); + $expected = sprintf($expected, str_replace(CAKE_CORE_INCLUDE_PATH, '', __FILE__), __LINE__ - 9); $this->assertEquals($expected, $result); } @@ -853,6 +860,7 @@ EXPECTED; * @return void */ public function testPr() { + $this->skipIf(php_sapi_name() == 'cli', 'Skipping web test in cli mode'); ob_start(); pr('this is a test'); $result = ob_get_clean(); @@ -866,6 +874,26 @@ EXPECTED; $this->assertEquals($expected, $result); } +/** + * test pr() + * + * @return void + */ + public function testPrCli() { + $this->skipIf(php_sapi_name() != 'cli', 'Skipping cli test in web mode'); + ob_start(); + pr('this is a test'); + $result = ob_get_clean(); + $expected = "\nthis is a test\n"; + $this->assertEquals($expected, $result); + + ob_start(); + pr(array('this' => 'is', 'a' => 'test')); + $result = ob_get_clean(); + $expected = "\nArray\n(\n [this] => is\n [a] => test\n)\n\n"; + $this->assertEquals($expected, $result); + } + /** * test stripslashes_deep() * diff --git a/lib/Cake/Test/Case/Cache/CacheTest.php b/lib/Cake/Test/Case/Cache/CacheTest.php index c67f80d40..13116c2f7 100644 --- a/lib/Cake/Test/Case/Cache/CacheTest.php +++ b/lib/Cake/Test/Case/Cache/CacheTest.php @@ -48,6 +48,9 @@ class CacheTest extends CakeTestCase { */ public function tearDown() { parent::tearDown(); + Cache::drop('latest'); + Cache::drop('page'); + Cache::drop('archive'); Configure::write('Cache.disable', $this->_cacheDisable); Cache::config('default', $this->_defaultCacheConfig['settings']); } @@ -129,6 +132,10 @@ class CacheTest extends CakeTestCase { * @return void */ public function testInvalidConfig() { + // In debug mode it would auto create the folder. + $debug = Configure::read('debug'); + Configure::write('debug', 0); + Cache::config('invalid', array( 'engine' => 'File', 'duration' => '+1 year', @@ -138,6 +145,8 @@ class CacheTest extends CakeTestCase { 'random' => 'wii' )); Cache::read('Test', 'invalid'); + + Configure::write('debug', $debug); } /** @@ -237,6 +246,67 @@ class CacheTest extends CakeTestCase { Cache::config('sessions', $_cacheConfigSessions['settings']); } +/** + * testGroupConfigs method + */ + public function testGroupConfigs() { + Cache::config('latest', array( + 'duration' => 300, + 'engine' => 'File', + 'groups' => array( + 'posts', 'comments', + ), + )); + + $expected = array( + 'posts' => array('latest'), + 'comments' => array('latest'), + ); + $result = Cache::groupConfigs(); + $this->assertEquals($expected, $result); + + $result = Cache::groupConfigs('posts'); + $this->assertEquals(array('posts' => array('latest')), $result); + + Cache::config('page', array( + 'duration' => 86400, + 'engine' => 'File', + 'groups' => array( + 'posts', 'archive' + ), + )); + + $result = Cache::groupConfigs(); + $expected = array( + 'posts' => array('latest', 'page'), + 'comments' => array('latest'), + 'archive' => array('page'), + ); + $this->assertEquals($expected, $result); + + $result = Cache::groupConfigs('archive'); + $this->assertEquals(array('archive' => array('page')), $result); + + Cache::config('archive', array( + 'duration' => 86400 * 30, + 'engine' => 'File', + 'groups' => array( + 'posts', 'archive', 'comments', + ), + )); + + $result = Cache::groupConfigs('archive'); + $this->assertEquals(array('archive' => array('archive', 'page')), $result); + } + +/** + * testGroupConfigsThrowsException method + * @expectedException CacheException + */ + public function testGroupConfigsThrowsException() { + Cache::groupConfigs('bogus'); + } + /** * test that configured returns an array of the currently configured cache * settings diff --git a/lib/Cake/Test/Case/Cache/Engine/FileEngineTest.php b/lib/Cake/Test/Case/Cache/Engine/FileEngineTest.php index 4c3c4b569..982729cfb 100644 --- a/lib/Cake/Test/Case/Cache/Engine/FileEngineTest.php +++ b/lib/Cake/Test/Case/Cache/Engine/FileEngineTest.php @@ -381,20 +381,19 @@ class FileEngineTest extends CakeTestCase { } /** - * check that FileEngine generates an error when a configured Path does not exist. + * check that FileEngine does not generate an error when a configured Path does not exist in debug mode. * - * @expectedException PHPUnit_Framework_Error_Warning * @return void */ - public function testErrorWhenPathDoesNotExist() { - $this->skipIf(is_dir(TMP . 'tests' . DS . 'file_failure'), 'Cannot run test directory exists.'); + public function testPathDoesNotExist() { + $this->skipIf(is_dir(TMP . 'tests' . DS . 'autocreate'), 'Cannot run if test directory exists.'); - Cache::config('failure', array( + Cache::config('autocreate', array( 'engine' => 'File', - 'path' => TMP . 'tests' . DS . 'file_failure' + 'path' => TMP . 'tests' . DS . 'autocreate' )); - Cache::drop('failure'); + Cache::drop('autocreate'); } /** diff --git a/lib/Cake/Test/Case/Console/Command/SchemaShellTest.php b/lib/Cake/Test/Case/Console/Command/SchemaShellTest.php index fd3b7059a..a78c0fbdd 100644 --- a/lib/Cake/Test/Case/Console/Command/SchemaShellTest.php +++ b/lib/Cake/Test/Case/Console/Command/SchemaShellTest.php @@ -72,6 +72,14 @@ class SchemaShellTestSchema extends CakeSchema { 'updated' => array('type' => 'datetime', 'null' => true, 'default' => null), 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => true)), ); + + public $newone = array( + 'id' => array('type' => 'integer', 'null' => false, 'default' => 0, 'key' => 'primary'), + 'testit' => array('type' => 'string', 'null' => false, 'default' => 'Title'), + 'created' => array('type' => 'datetime', 'null' => true, 'default' => null), + 'updated' => array('type' => 'datetime', 'null' => true, 'default' => null), + 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => true)), + ); } /** @@ -86,8 +94,9 @@ class SchemaShellTest extends CakeTestCase { * * @var array */ - public $fixtures = array('core.article', 'core.user', 'core.post', 'core.auth_user', 'core.author', - 'core.comment', 'core.test_plugin_comment' + public $fixtures = array( + 'core.article', 'core.user', 'core.post', 'core.auth_user', 'core.author', + 'core.comment', 'core.test_plugin_comment', 'core.aco', 'core.aro', 'core.aros_aco', ); /** @@ -390,6 +399,33 @@ class SchemaShellTest extends CakeTestCase { CakePlugin::unload(); } +/** + * test generate with excluded tables + * + * @return void + */ + public function testGenerateExclude() { + Configure::write('Acl.database', 'test'); + $this->db->cacheSources = false; + $this->Shell->params = array( + 'connection' => 'test', + 'force' => false, + 'models' => 'Aro, Aco, Permission', + 'overwrite' => true, + 'exclude' => 'acos, aros', + ); + $this->Shell->startup(); + $this->Shell->Schema->path = TMP . 'tests' . DS; + + $this->Shell->generate(); + $this->file = new File(TMP . 'tests' . DS . 'schema.php'); + $contents = $this->file->read(); + + $this->assertNotContains('public $acos = array(', $contents); + $this->assertNotContains('public $aros = array(', $contents); + $this->assertContains('public $aros_acos = array(', $contents); + } + /** * Test schema run create with no table args. * @@ -422,15 +458,15 @@ class SchemaShellTest extends CakeTestCase { public function testCreateWithTableArgs() { $db = ConnectionManager::getDataSource('test'); $sources = $db->listSources(); - if (in_array('acos', $sources)) { - $this->markTestSkipped('acos table already exists, cannot try to create it again.'); + if (in_array('i18n', $sources)) { + $this->markTestSkipped('i18n table already exists, cannot try to create it again.'); } $this->Shell->params = array( 'connection' => 'test', - 'name' => 'DbAcl', + 'name' => 'I18n', 'path' => APP . 'Config' . DS . 'Schema' ); - $this->Shell->args = array('DbAcl', 'acos'); + $this->Shell->args = array('I18n', 'i18n'); $this->Shell->startup(); $this->Shell->expects($this->any())->method('in')->will($this->returnValue('y')); $this->Shell->create(); @@ -438,12 +474,10 @@ class SchemaShellTest extends CakeTestCase { $db = ConnectionManager::getDataSource('test'); $db->cacheSources = false; $sources = $db->listSources(); - $this->assertTrue(in_array($db->config['prefix'] . 'acos', $sources), 'acos should be present.'); - $this->assertFalse(in_array($db->config['prefix'] . 'aros', $sources), 'aros should not be found.'); - $this->assertFalse(in_array('aros_acos', $sources), 'aros_acos should not be found.'); + $this->assertTrue(in_array($db->config['prefix'] . 'i18n', $sources), 'i18n should be present.'); - $schema = new DbAclSchema(); - $db->execute($db->dropSchema($schema, 'acos')); + $schema = new I18nSchema(); + $db->execute($db->dropSchema($schema, 'i18n')); } /** @@ -474,6 +508,34 @@ class SchemaShellTest extends CakeTestCase { $this->Shell->update(); } +/** + * test run update with a table arg. and checks that a CREATE statement is issued + * table creation + * @return void + */ + public function testUpdateWithTableCreate() { + $this->Shell = $this->getMock( + 'SchemaShell', + array('in', 'out', 'hr', 'createFile', 'error', 'err', '_stop', '_run'), + array(&$this->Dispatcher) + ); + + $this->Shell->params = array( + 'connection' => 'test', + 'force' => true + ); + $this->Shell->args = array('SchemaShellTest', 'newone'); + $this->Shell->startup(); + $this->Shell->expects($this->any()) + ->method('in') + ->will($this->returnValue('y')); + $r = $this->Shell->expects($this->once()) + ->method('_run') + ->with($this->arrayHasKey('newone'), 'update', $this->isInstanceOf('CakeSchema')); + + $this->Shell->update(); + } + /** * test that the plugin param creates the correct path in the schema object. * diff --git a/lib/Cake/Test/Case/Console/Command/Task/FixtureTaskTest.php b/lib/Cake/Test/Case/Console/Command/Task/FixtureTaskTest.php index a0297636a..b9b6203af 100644 --- a/lib/Cake/Test/Case/Console/Command/Task/FixtureTaskTest.php +++ b/lib/Cake/Test/Case/Console/Command/Task/FixtureTaskTest.php @@ -123,6 +123,48 @@ class FixtureTaskTest extends CakeTestCase { $this->assertEquals($expected, $result); } +/** + * test importOptions with overwriting command line options. + * + * @return void + */ + public function testImportOptionsWithCommandLineOptions() { + $this->Task->params = array('schema' => true, 'records' => true); + + $result = $this->Task->importOptions('Article'); + $expected = array('schema' => 'Article', 'records' => true); + $this->assertEquals($expected, $result); + } + +/** + * test importOptions with schema. + * + * @return void + */ + public function testImportOptionsWithSchema() { + $this->Task->params = array('schema' => true); + $this->Task->expects($this->at(0))->method('in')->will($this->returnValue('n')); + $this->Task->expects($this->at(1))->method('in')->will($this->returnValue('n')); + + $result = $this->Task->importOptions('Article'); + $expected = array('schema' => 'Article'); + $this->assertEquals($expected, $result); + } + +/** + * test importOptions with records. + * + * @return void + */ + public function testImportOptionsWithRecords() { + $this->Task->params = array('records' => true); + $this->Task->expects($this->at(0))->method('in')->will($this->returnValue('n')); + + $result = $this->Task->importOptions('Article'); + $expected = array('records' => true); + $this->assertEquals($expected, $result); + } + /** * test importOptions choosing from Table. * @@ -272,6 +314,32 @@ class FixtureTaskTest extends CakeTestCase { $this->Task->all(); } +/** + * test using all() with -schema + * + * @return void + */ + public function testAllWithSchemaImport() { + $this->Task->connection = 'test'; + $this->Task->path = '/my/path/'; + $this->Task->args = array('all'); + $this->Task->params = array('schema' => true); + + $this->Task->Model->expects($this->any())->method('listAll') + ->will($this->returnValue(array('Articles', 'comments'))); + + $filename = '/my/path/ArticleFixture.php'; + $this->Task->expects($this->at(0))->method('createFile') + ->with($filename, $this->stringContains('public $import = array(\'model\' => \'Article\'')); + + $filename = '/my/path/CommentFixture.php'; + $this->Task->expects($this->at(1))->method('createFile') + ->with($filename, $this->stringContains('public $import = array(\'model\' => \'Comment\'')); + $this->Task->expects($this->exactly(2))->method('createFile'); + + $this->Task->all(); + } + /** * test interactive mode of execute * diff --git a/lib/Cake/Test/Case/Console/Command/Task/ModelTaskTest.php b/lib/Cake/Test/Case/Console/Command/Task/ModelTaskTest.php index 02b96c37a..533afac36 100644 --- a/lib/Cake/Test/Case/Console/Command/Task/ModelTaskTest.php +++ b/lib/Cake/Test/Case/Console/Command/Task/ModelTaskTest.php @@ -42,7 +42,7 @@ class ModelTaskTest extends CakeTestCase { */ public $fixtures = array( 'core.bake_article', 'core.bake_comment', 'core.bake_articles_bake_tag', - 'core.bake_tag', 'core.category_thread' + 'core.bake_tag', 'core.category_thread', 'core.number_tree' ); /** @@ -623,6 +623,20 @@ class ModelTaskTest extends CakeTestCase { $this->assertEquals($expected, $result); } +/** + * test non interactive doActsAs + * + * @return void + */ + public function testDoActsAs() { + $this->Task->connection = 'test'; + $this->Task->interactive = false; + $model = new Model(array('ds' => 'test', 'name' => 'NumberTree')); + $result = $this->Task->doActsAs($model); + + $this->assertEquals(array('Tree'), $result); + } + /** * Ensure that the fixture object is correctly called. * @@ -836,6 +850,27 @@ STRINGEND; $this->assertEquals(count(ClassRegistry::mapKeys()), 0); } +/** + * test bake() for models with behaviors + * + * @return void + */ + public function testBakeWithBehaviors() { + $result = $this->Task->bake('NumberTree', array('actsAs' => array('Tree', 'PluginName.Sluggable'))); + $expected = <<assertTextContains($expected, $result); + } + /** * test that execute passes runs bake depending with named model. * @@ -987,7 +1022,7 @@ STRINGEND; $out = $this->getMock('ConsoleOutput', array(), array(), '', false); $in = $this->getMock('ConsoleInput', array(), array(), '', false); $this->Task = $this->getMock('ModelTask', - array('in', 'err', '_stop', '_checkUnitTest', 'getAllTables', '_getModelObject', 'doAssociations', 'doValidation', 'createFile'), + array('in', 'err', '_stop', '_checkUnitTest', 'getAllTables', '_getModelObject', 'doAssociations', 'doValidation', 'doActsAs', 'createFile'), array($out, $out, $in) ); $this->_setupOtherMocks(); @@ -1001,6 +1036,7 @@ STRINGEND; $this->Task->expects($this->once())->method('_getModelObject')->will($this->returnValue($object)); $this->Task->expects($this->once())->method('doAssociations')->will($this->returnValue(array())); $this->Task->expects($this->once())->method('doValidation')->will($this->returnValue(array())); + $this->Task->expects($this->once())->method('doActsAs')->will($this->returnValue(array())); $filename = '/my/path/BakeOdd.php'; $this->Task->expects($this->once())->method('createFile') @@ -1042,7 +1078,7 @@ STRINGEND; $out = $this->getMock('ConsoleOutput', array(), array(), '', false); $in = $this->getMock('ConsoleInput', array(), array(), '', false); $this->Task = $this->getMock('ModelTask', - array('in', 'err', '_stop', '_checkUnitTest', 'getAllTables', '_getModelObject', 'doAssociations', 'doValidation', 'createFile'), + array('in', 'err', '_stop', '_checkUnitTest', 'getAllTables', '_getModelObject', 'doAssociations', 'doValidation', 'doActsAs', 'createFile'), array($out, $out, $in) ); $this->_setupOtherMocks(); @@ -1056,6 +1092,7 @@ STRINGEND; $this->Task->expects($this->once())->method('_getModelObject')->will($this->returnValue($object)); $this->Task->expects($this->once())->method('doAssociations')->will($this->returnValue(array())); $this->Task->expects($this->once())->method('doValidation')->will($this->returnValue(array())); + $this->Task->expects($this->once())->method('doActsAs')->will($this->returnValue(array())); $filename = '/my/path/BakeOdd.php'; $this->Task->expects($this->once())->method('createFile') diff --git a/lib/Cake/Test/Case/Console/ShellTest.php b/lib/Cake/Test/Case/Console/ShellTest.php index c40c04d0a..10e15be6a 100644 --- a/lib/Cake/Test/Case/Console/ShellTest.php +++ b/lib/Cake/Test/Case/Console/ShellTest.php @@ -212,6 +212,32 @@ class ShellTest extends CakeTestCase { App::build(); } +/** + * testLoadModel method + * + * @return void + */ + public function testLoadModel() { + App::build(array( + 'Plugin' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS), + 'Model' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Model' . DS) + ), App::RESET); + + $Shell = new TestMergeShell(); + $this->assertEquals('Comment', $Shell->Comment->alias); + $this->assertInstanceOf('Comment', $Shell->Comment); + $this->assertEquals('Comment', $Shell->modelClass); + + CakePlugin::load('TestPlugin'); + $this->Shell->loadModel('TestPlugin.TestPluginPost'); + $this->assertTrue(isset($this->Shell->TestPluginPost)); + $this->assertInstanceOf('TestPluginPost', $this->Shell->TestPluginPost); + $this->assertEquals('TestPluginPost', $this->Shell->modelClass); + CakePlugin::unload('TestPlugin'); + + App::build(); + } + /** * testIn method * @@ -829,7 +855,7 @@ TEXT; array('types' => 'error'), )); TestCakeLog::config('console', array( - 'engine' => 'ConsoleLog', + 'engine' => 'Console', 'stream' => 'php://stderr', )); TestCakeLog::replace('console', $mock); diff --git a/lib/Cake/Test/Case/Console/TaskCollectionTest.php b/lib/Cake/Test/Case/Console/TaskCollectionTest.php index fd335ebd6..ba1553611 100644 --- a/lib/Cake/Test/Case/Console/TaskCollectionTest.php +++ b/lib/Cake/Test/Case/Console/TaskCollectionTest.php @@ -20,6 +20,11 @@ App::uses('TaskCollection', 'Console'); App::uses('Shell', 'Console'); +/** + * Extended Task + */ +class DbConfigAliasedTask extends Shell { +} /** * Class TaskCollectionTest @@ -127,4 +132,25 @@ class TaskCollectionTest extends CakeTestCase { $this->assertEquals(array('Extract'), $result, 'loaded tasks is wrong'); } +/** + * Tests loading as an alias + * + * @return void + */ + public function testLoadWithAlias() { + $result = $this->Tasks->load('DbConfig', array('className' => 'DbConfigAliased')); + $this->assertInstanceOf('DbConfigAliasedTask', $result); + $this->assertInstanceOf('DbConfigAliasedTask', $this->Tasks->DbConfig); + + $result = $this->Tasks->loaded(); + $this->assertEquals(array('DbConfig'), $result, 'loaded() results are wrong.'); + + $result = $this->Tasks->load('SomeTask', array('className' => 'TestPlugin.OtherTask')); + $this->assertInstanceOf('OtherTaskTask', $result); + $this->assertInstanceOf('OtherTaskTask', $this->Tasks->SomeTask); + + $result = $this->Tasks->loaded(); + $this->assertEquals(array('DbConfig', 'SomeTask'), $result, 'loaded() results are wrong.'); + } + } diff --git a/lib/Cake/Test/Case/Controller/Component/Auth/BasicAuthenticateTest.php b/lib/Cake/Test/Case/Controller/Component/Auth/BasicAuthenticateTest.php index 6da7eab55..ec4b78ad6 100644 --- a/lib/Cake/Test/Case/Controller/Component/Auth/BasicAuthenticateTest.php +++ b/lib/Cake/Test/Case/Controller/Component/Auth/BasicAuthenticateTest.php @@ -84,11 +84,10 @@ class BasicAuthenticateTest extends CakeTestCase { public function testAuthenticateNoData() { $request = new CakeRequest('posts/index', false); - $this->response->expects($this->once()) - ->method('header') - ->with('WWW-Authenticate: Basic realm="localhost"'); + $this->response->expects($this->never()) + ->method('header'); - $this->assertFalse($this->auth->authenticate($request, $this->response)); + $this->assertFalse($this->auth->getUser($request)); } /** @@ -100,10 +99,6 @@ class BasicAuthenticateTest extends CakeTestCase { $request = new CakeRequest('posts/index', false); $_SERVER['PHP_AUTH_PW'] = 'foobar'; - $this->response->expects($this->once()) - ->method('header') - ->with('WWW-Authenticate: Basic realm="localhost"'); - $this->assertFalse($this->auth->authenticate($request, $this->response)); } @@ -117,10 +112,6 @@ class BasicAuthenticateTest extends CakeTestCase { $_SERVER['PHP_AUTH_USER'] = 'mariano'; $_SERVER['PHP_AUTH_PW'] = null; - $this->response->expects($this->once()) - ->method('header') - ->with('WWW-Authenticate: Basic realm="localhost"'); - $this->assertFalse($this->auth->authenticate($request, $this->response)); } @@ -136,6 +127,8 @@ class BasicAuthenticateTest extends CakeTestCase { $_SERVER['PHP_AUTH_USER'] = '> 1'; $_SERVER['PHP_AUTH_PW'] = "' OR 1 = 1"; + $this->assertFalse($this->auth->getUser($request)); + $this->assertFalse($this->auth->authenticate($request, $this->response)); } @@ -148,15 +141,15 @@ class BasicAuthenticateTest extends CakeTestCase { $request = new CakeRequest('posts/index', false); $request->addParams(array('pass' => array(), 'named' => array())); - $this->response->expects($this->at(0)) - ->method('header') - ->with('WWW-Authenticate: Basic realm="localhost"'); + try { + $this->auth->unauthenticated($request, $this->response); + } catch (UnauthorizedException $e) { + } - $this->response->expects($this->at(1)) - ->method('send'); + $this->assertNotEmpty($e); - $result = $this->auth->authenticate($request, $this->response); - $this->assertFalse($result); + $expected = array('WWW-Authenticate: Basic realm="localhost"'); + $this->assertEquals($expected, $e->responseHeader()); } /** @@ -184,6 +177,8 @@ class BasicAuthenticateTest extends CakeTestCase { /** * test scope failure. * + * @expectedException UnauthorizedException + * @expectedExceptionCode 401 * @return void */ public function testAuthenticateFailReChallenge() { @@ -194,18 +189,40 @@ class BasicAuthenticateTest extends CakeTestCase { $_SERVER['PHP_AUTH_USER'] = 'mariano'; $_SERVER['PHP_AUTH_PW'] = 'password'; - $this->response->expects($this->at(0)) - ->method('header') - ->with('WWW-Authenticate: Basic realm="localhost"'); + $this->auth->unauthenticated($request, $this->response); + } - $this->response->expects($this->at(1)) - ->method('statusCode') - ->with(401); +/** + * testAuthenticateWithBlowfish + * + * @return void + */ + public function testAuthenticateWithBlowfish() { + $hash = Security::hash('password', 'blowfish'); + $this->skipIf(strpos($hash, '$2a$') === false, 'Skipping blowfish tests as hashing is not working'); - $this->response->expects($this->at(2)) - ->method('send'); + $request = new CakeRequest('posts/index', false); + $request->addParams(array('pass' => array(), 'named' => array())); - $this->assertFalse($this->auth->authenticate($request, $this->response)); + $_SERVER['PHP_AUTH_USER'] = 'mariano'; + $_SERVER['PHP_AUTH_PW'] = 'password'; + + $User = ClassRegistry::init('User'); + $User->updateAll( + array('password' => $User->getDataSource()->value($hash)), + array('User.user' => 'mariano') + ); + + $this->auth->settings['passwordHasher'] = 'Blowfish'; + + $result = $this->auth->authenticate($request, $this->response); + $expected = array( + 'id' => 1, + 'user' => 'mariano', + 'created' => '2007-03-17 01:16:23', + 'updated' => '2007-03-17 01:18:31' + ); + $this->assertEquals($expected, $result); } } diff --git a/lib/Cake/Test/Case/Controller/Component/Auth/CrudAuthorizeTest.php b/lib/Cake/Test/Case/Controller/Component/Auth/CrudAuthorizeTest.php index 4dd4a5bf9..53d92cbf8 100644 --- a/lib/Cake/Test/Case/Controller/Component/Auth/CrudAuthorizeTest.php +++ b/lib/Cake/Test/Case/Controller/Component/Auth/CrudAuthorizeTest.php @@ -39,6 +39,7 @@ class CrudAuthorizeTest extends CakeTestCase { public function setUp() { parent::setUp(); Configure::write('Routing.prefixes', array()); + Router::reload(); $this->Acl = $this->getMock('AclComponent', array(), array(), '', false); $this->Components = $this->getMock('ComponentCollection'); diff --git a/lib/Cake/Test/Case/Controller/Component/Auth/DigestAuthenticateTest.php b/lib/Cake/Test/Case/Controller/Component/Auth/DigestAuthenticateTest.php index 62fdcefb8..cafc5688b 100644 --- a/lib/Cake/Test/Case/Controller/Component/Auth/DigestAuthenticateTest.php +++ b/lib/Cake/Test/Case/Controller/Component/Auth/DigestAuthenticateTest.php @@ -99,16 +99,17 @@ class DigestAuthenticateTest extends CakeTestCase { public function testAuthenticateNoData() { $request = new CakeRequest('posts/index', false); - $this->response->expects($this->once()) - ->method('header') - ->with('WWW-Authenticate: Digest realm="localhost",qop="auth",nonce="123",opaque="123abc"'); + $this->response->expects($this->never()) + ->method('header'); - $this->assertFalse($this->auth->authenticate($request, $this->response)); + $this->assertFalse($this->auth->getUser($request, $this->response)); } /** * test the authenticate method * + * @expectedException UnauthorizedException + * @expectedExceptionCode 401 * @return void */ public function testAuthenticateWrongUsername() { @@ -127,18 +128,7 @@ response="6629fae49393a05397450978507c4ef1", opaque="123abc" DIGEST; - $this->response->expects($this->at(0)) - ->method('header') - ->with('WWW-Authenticate: Digest realm="localhost",qop="auth",nonce="123",opaque="123abc"'); - - $this->response->expects($this->at(1)) - ->method('statusCode') - ->with(401); - - $this->response->expects($this->at(2)) - ->method('send'); - - $this->assertFalse($this->auth->authenticate($request, $this->response)); + $this->auth->unauthenticated($request, $this->response); } /** @@ -150,19 +140,15 @@ DIGEST; $request = new CakeRequest('posts/index', false); $request->addParams(array('pass' => array(), 'named' => array())); - $this->response->expects($this->at(0)) - ->method('header') - ->with('WWW-Authenticate: Digest realm="localhost",qop="auth",nonce="123",opaque="123abc"'); + try { + $this->auth->unauthenticated($request, $this->response); + } catch (UnauthorizedException $e) { + } - $this->response->expects($this->at(1)) - ->method('statusCode') - ->with(401); + $this->assertNotEmpty($e); - $this->response->expects($this->at(2)) - ->method('send'); - - $result = $this->auth->authenticate($request, $this->response); - $this->assertFalse($result); + $expected = array('WWW-Authenticate: Digest realm="localhost",qop="auth",nonce="123",opaque="123abc"'); + $this->assertEquals($expected, $e->responseHeader()); } /** @@ -199,6 +185,8 @@ DIGEST; /** * test scope failure. * + * @expectedException UnauthorizedException + * @expectedExceptionCode 401 * @return void */ public function testAuthenticateFailReChallenge() { @@ -218,18 +206,7 @@ response="6629fae49393a05397450978507c4ef1", opaque="123abc" DIGEST; - $this->response->expects($this->at(0)) - ->method('header') - ->with('WWW-Authenticate: Digest realm="localhost",qop="auth",nonce="123",opaque="123abc"'); - - $this->response->expects($this->at(1)) - ->method('statusCode') - ->with(401); - - $this->response->expects($this->at(2)) - ->method('send'); - - $this->assertFalse($this->auth->authenticate($request, $this->response)); + $this->auth->unauthenticated($request, $this->response); } /** diff --git a/lib/Cake/Test/Case/Controller/Component/Auth/FormAuthenticateTest.php b/lib/Cake/Test/Case/Controller/Component/Auth/FormAuthenticateTest.php index 59335510a..f26eb02de 100644 --- a/lib/Cake/Test/Case/Controller/Component/Auth/FormAuthenticateTest.php +++ b/lib/Cake/Test/Case/Controller/Component/Auth/FormAuthenticateTest.php @@ -233,4 +233,52 @@ class FormAuthenticateTest extends CakeTestCase { CakePlugin::unload(); } +/** + * test password hasher settings + * + * @return void + */ + public function testPasswordHasherSettings() { + $this->auth->settings['passwordHasher'] = array( + 'className' => 'Simple', + 'hashType' => 'md5' + ); + + $passwordHasher = $this->auth->passwordHasher(); + $result = $passwordHasher->config(); + $this->assertEquals('md5', $result['hashType']); + + $hash = Security::hash('mypass', 'md5', true); + $User = ClassRegistry::init('User'); + $User->updateAll( + array('password' => $User->getDataSource()->value($hash)), + array('User.user' => 'mariano') + ); + + $request = new CakeRequest('posts/index', false); + $request->data = array('User' => array( + 'user' => 'mariano', + 'password' => 'mypass' + )); + + $result = $this->auth->authenticate($request, $this->response); + $expected = array( + 'id' => 1, + 'user' => 'mariano', + 'created' => '2007-03-17 01:16:23', + 'updated' => '2007-03-17 01:18:31' + ); + $this->assertEquals($expected, $result); + + $this->auth = new FormAuthenticate($this->Collection, array( + 'fields' => array('username' => 'user', 'password' => 'password'), + 'userModel' => 'User' + )); + $this->auth->settings['passwordHasher'] = array( + 'className' => 'Simple', + 'hashType' => 'sha1' + ); + $this->assertFalse($this->auth->authenticate($request, $this->response)); + } + } diff --git a/lib/Cake/Test/Case/Controller/Component/AuthComponentTest.php b/lib/Cake/Test/Case/Controller/Component/AuthComponentTest.php index 73e70704a..73366e017 100644 --- a/lib/Cake/Test/Case/Controller/Component/AuthComponentTest.php +++ b/lib/Cake/Test/Case/Controller/Component/AuthComponentTest.php @@ -294,6 +294,7 @@ class AuthComponentTest extends CakeTestCase { $this->Auth = new TestAuthComponent($collection); $this->Auth->request = $request; $this->Auth->response = $this->getMock('CakeResponse'); + AuthComponent::$sessionKey = 'Auth.User'; $this->Controller->Components->init($this->Controller); @@ -392,6 +393,26 @@ class AuthComponentTest extends CakeTestCase { $this->assertEquals($user, $this->Auth->user()); } +/** + * testRedirectVarClearing method + * + * @return void + */ + public function testRedirectVarClearing() { + $this->Controller->request['controller'] = 'auth_test'; + $this->Controller->request['action'] = 'admin_add'; + $this->Controller->here = '/auth_test/admin_add'; + $this->assertNull($this->Auth->Session->read('Auth.redirect')); + + $this->Auth->authenticate = array('Form'); + $this->Auth->startup($this->Controller); + $this->assertEquals('/auth_test/admin_add', $this->Auth->Session->read('Auth.redirect')); + + $this->Auth->Session->write('Auth.User', array('username' => 'admad')); + $this->Auth->startup($this->Controller); + $this->assertNull($this->Auth->Session->read('Auth.redirect')); + } + /** * testAuthorizeFalse method * @@ -794,6 +815,34 @@ class AuthComponentTest extends CakeTestCase { $expected = Router::normalize('posts/index/29?print=true&refer=menu'); $this->assertEquals($expected, $this->Auth->Session->read('Auth.redirect')); + // Different base urls. + $appConfig = Configure::read('App'); + + $_GET = array(); + + Configure::write('App', array( + 'dir' => APP_DIR, + 'webroot' => WEBROOT_DIR, + 'base' => false, + 'baseUrl' => '/cake/index.php' + )); + + $this->Auth->Session->delete('Auth'); + + $url = '/posts/add'; + $this->Auth->request = $this->Controller->request = new CakeRequest($url); + $this->Auth->request->addParams(Router::parse($url)); + $this->Auth->request->url = Router::normalize($url); + + $this->Auth->initialize($this->Controller); + $this->Auth->loginAction = array('controller' => 'users', 'action' => 'login'); + $this->Auth->startup($this->Controller); + $expected = Router::normalize('/posts/add'); + $this->assertEquals($expected, $this->Auth->Session->read('Auth.redirect')); + + $this->Auth->Session->delete('Auth'); + Configure::write('App', $appConfig); + $_GET = $_back; // External Authed Action @@ -881,6 +930,11 @@ class AuthComponentTest extends CakeTestCase { array('on', 'redirect'), array($CakeRequest, $CakeResponse) ); + $this->Auth->Session = $this->getMock( + 'SessionComponent', + array('setFlash'), + array($Controller->Components) + ); $expected = array( 'controller' => 'no_can_do', 'action' => 'jack' @@ -888,6 +942,47 @@ class AuthComponentTest extends CakeTestCase { $Controller->expects($this->once()) ->method('redirect') ->with($this->equalTo($expected)); + $this->Auth->Session->expects($this->once()) + ->method('setFlash'); + $this->Auth->startup($Controller); + } + +/** + * testRedirectToUnauthorizedRedirectSuppressedAuthError + * + * @return void + */ + public function testRedirectToUnauthorizedRedirectSuppressedAuthError() { + $url = '/party/on'; + $this->Auth->request = $CakeRequest = new CakeRequest($url); + $this->Auth->request->addParams(Router::parse($url)); + $this->Auth->authorize = array('Controller'); + $this->Auth->login(array('username' => 'admad', 'password' => 'cake')); + $this->Auth->unauthorizedRedirect = array( + 'controller' => 'no_can_do', 'action' => 'jack' + ); + $this->Auth->authError = false; + + $CakeResponse = new CakeResponse(); + $Controller = $this->getMock( + 'Controller', + array('on', 'redirect'), + array($CakeRequest, $CakeResponse) + ); + $this->Auth->Session = $this->getMock( + 'SessionComponent', + array('setFlash'), + array($Controller->Components) + ); + + $expected = array( + 'controller' => 'no_can_do', 'action' => 'jack' + ); + $Controller->expects($this->once()) + ->method('redirect') + ->with($this->equalTo($expected)); + $this->Auth->Session->expects($this->never()) + ->method('setFlash'); $this->Auth->startup($Controller); } @@ -1059,20 +1154,6 @@ class AuthComponentTest extends CakeTestCase { $this->assertEquals('mariano', $result); } -/** - * Tests that shutdown destroys the redirect session var - * - * @return void - */ - public function testShutDown() { - $this->Auth->Session->write('Auth.User', 'not empty'); - $this->Auth->Session->write('Auth.redirect', 'foo'); - $this->Controller->Auth->loggedIn(true); - - $this->Controller->Auth->shutdown($this->Controller); - $this->assertNull($this->Auth->Session->read('Auth.redirect')); - } - /** * test $settings in Controller::$components * @@ -1351,4 +1432,70 @@ class AuthComponentTest extends CakeTestCase { $result = $this->Auth->user('is_admin'); $this->assertFalse($result); } + +/** + * testStatelessAuthNoRedirect method + * + * @expectedException UnauthorizedException + * @expectedExceptionCode 401 + * @return void + */ + public function testStatelessAuthNoRedirect() { + if (CakeSession::id()) { + session_destroy(); + CakeSession::$id = null; + } + $_SESSION = null; + + AuthComponent::$sessionKey = false; + $this->Auth->authenticate = array('Basic'); + $this->Controller->request['action'] = 'admin_add'; + + $this->Auth->startup($this->Controller); + } + +/** + * testStatelessAuthNoSessionStart method + * + * @return void + */ + public function testStatelessAuthNoSessionStart() { + if (CakeSession::id()) { + session_destroy(); + CakeSession::$id = null; + } + $_SESSION = null; + + $_SERVER['PHP_AUTH_USER'] = 'mariano'; + $_SERVER['PHP_AUTH_PW'] = 'cake'; + + AuthComponent::$sessionKey = false; + $this->Auth->authenticate = array( + 'Basic' => array('userModel' => 'AuthUser') + ); + $this->Controller->request['action'] = 'admin_add'; + + $result = $this->Auth->startup($this->Controller); + $this->assertTrue($result); + + $this->assertNull(CakeSession::id()); + } + +/** + * testStatelessAuthRedirect method + * + * @return void + */ + public function testStatelessFollowedByStatefulAuth() { + $this->Auth->authenticate = array('Basic', 'Form'); + $this->Controller->request['action'] = 'admin_add'; + + $this->Auth->response->expects($this->never())->method('statusCode'); + $this->Auth->response->expects($this->never())->method('send'); + + $result = $this->Auth->startup($this->Controller); + $this->assertFalse($result); + + $this->assertEquals('/users/login', $this->Controller->testUrl); + } } diff --git a/lib/Cake/Test/Case/Controller/Component/RequestHandlerComponentTest.php b/lib/Cake/Test/Case/Controller/Component/RequestHandlerComponentTest.php index 10144ef31..63ced76f3 100644 --- a/lib/Cake/Test/Case/Controller/Component/RequestHandlerComponentTest.php +++ b/lib/Cake/Test/Case/Controller/Component/RequestHandlerComponentTest.php @@ -229,7 +229,10 @@ class RequestHandlerComponentTest extends CakeTestCase { } /** - * Test that ext is not set with multiple accepted content types. + * Test that ext is set to the first listed extension with multiple accepted + * content types. + * Having multiple types accepted with same weight, means the client lets the + * server choose the returned content type. * * @return void */ @@ -239,7 +242,27 @@ class RequestHandlerComponentTest extends CakeTestCase { Router::parseExtensions('xml', 'json'); $this->RequestHandler->initialize($this->Controller); + $this->assertEquals('xml', $this->RequestHandler->ext); + + $this->RequestHandler->ext = null; + Router::setExtensions(array('json', 'xml'), false); + + $this->RequestHandler->initialize($this->Controller); + $this->assertEquals('json', $this->RequestHandler->ext); + } + +/** + * Test that ext is set to type with highest weight + * + * @return void + */ + public function testInitializeContentTypeWithMultipleAcceptedTypes() { + $_SERVER['HTTP_ACCEPT'] = 'text/csv;q=1.0, application/json;q=0.8, application/xml;q=0.7'; $this->assertNull($this->RequestHandler->ext); + Router::parseExtensions('xml', 'json'); + + $this->RequestHandler->initialize($this->Controller); + $this->assertEquals('json', $this->RequestHandler->ext); } /** diff --git a/lib/Cake/Test/Case/Controller/ControllerMergeVarsTest.php b/lib/Cake/Test/Case/Controller/ControllerMergeVarsTest.php index 7085d52b6..c41165a12 100644 --- a/lib/Cake/Test/Case/Controller/ControllerMergeVarsTest.php +++ b/lib/Cake/Test/Case/Controller/ControllerMergeVarsTest.php @@ -239,6 +239,8 @@ class ControllerMergeVarsTest extends CakeTestCase { /** * Ensure that $modelClass is correct even when Controller::$uses * has been iterated, eg: by a Component, or event handlers. + * + * @return void */ public function testMergeVarsModelClass() { $Controller = new MergeVariablescontroller(); diff --git a/lib/Cake/Test/Case/Controller/PagesControllerTest.php b/lib/Cake/Test/Case/Controller/PagesControllerTest.php index 76e5c5c59..89445bd0e 100644 --- a/lib/Cake/Test/Case/Controller/PagesControllerTest.php +++ b/lib/Cake/Test/Case/Controller/PagesControllerTest.php @@ -51,4 +51,30 @@ class PagesControllerTest extends CakeTestCase { $this->assertEquals('TestTheme', $Pages->viewVars['page']); $this->assertEquals('Posts', $Pages->viewVars['subpage']); } + +/** + * Test that missing view renders 404 page in production + * + * @expectedException NotFoundException + * @expectedExceptionCode 404 + * @return void + */ + public function testMissingView() { + Configure::write('debug', 0); + $Pages = new PagesController(new CakeRequest(null, false), new CakeResponse()); + $Pages->display('non_existing_page'); + } + +/** + * Test that missing view in debug mode renders missing_view error page + * + * @expectedException MissingViewException + * @expectedExceptionCode 500 + * @return void + */ + public function testMissingViewInDebug() { + Configure::write('debug', 1); + $Pages = new PagesController(new CakeRequest(null, false), new CakeResponse()); + $Pages->display('non_existing_page'); + } } diff --git a/lib/Cake/Test/Case/Core/ConfigureTest.php b/lib/Cake/Test/Case/Core/ConfigureTest.php index 65e7717ca..407f18643 100644 --- a/lib/Cake/Test/Case/Core/ConfigureTest.php +++ b/lib/Cake/Test/Case/Core/ConfigureTest.php @@ -69,6 +69,23 @@ class ConfigureTest extends CakeTestCase { Configure::drop('test'); } +/** + * Test to ensure bootrapping doesn't overwrite prior configs set under 'App' key + * @return void + */ + public function testBootstrap() { + $expected = array( + 'foo' => 'bar' + ); + Configure::write('App', $expected); + + Configure::bootstrap(true); + $result = Configure::read('App'); + + $this->assertEquals($expected['foo'], $result['foo']); + $this->assertFalse($result['base']); + } + /** * testRead method * diff --git a/lib/Cake/Test/Case/Core/ObjectTest.php b/lib/Cake/Test/Case/Core/ObjectTest.php index b782d14fb..10b0cc7d7 100644 --- a/lib/Cake/Test/Case/Core/ObjectTest.php +++ b/lib/Cake/Test/Case/Core/ObjectTest.php @@ -455,7 +455,9 @@ class ObjectTest extends CakeTestCase { $expected = 'This is a test'; $this->assertEquals($expected, $result); - $result = $this->object->requestAction(FULL_BASE_URL . '/request_action/test_request_action'); + $result = $this->object->requestAction( + Configure::read('App.fullBaseUrl') . '/request_action/test_request_action' + ); $expected = 'This is a test'; $this->assertEquals($expected, $result); diff --git a/lib/Cake/Test/Case/Error/ErrorHandlerTest.php b/lib/Cake/Test/Case/Error/ErrorHandlerTest.php index 88708f573..5d009bdaf 100644 --- a/lib/Cake/Test/Case/Error/ErrorHandlerTest.php +++ b/lib/Cake/Test/Case/Error/ErrorHandlerTest.php @@ -195,8 +195,6 @@ class ErrorHandlerTest extends CakeTestCase { * @return void */ public function testHandleException() { - $this->skipIf(file_exists(APP . 'app_error.php'), 'App error exists cannot run.'); - $error = new NotFoundException('Kaboom!'); ob_start(); ErrorHandler::handleException($error); @@ -210,8 +208,6 @@ class ErrorHandlerTest extends CakeTestCase { * @return void */ public function testHandleExceptionLog() { - $this->skipIf(file_exists(APP . 'app_error.php'), 'App error exists cannot run.'); - if (file_exists(LOGS . 'error.log')) { unlink(LOGS . 'error.log'); } @@ -224,8 +220,37 @@ class ErrorHandlerTest extends CakeTestCase { $this->assertRegExp('/Kaboom!/', $result, 'message missing.'); $log = file(LOGS . 'error.log'); - $this->assertRegExp('/\[NotFoundException\] Kaboom!/', $log[0], 'message missing.'); - $this->assertRegExp('/\#0.*ErrorHandlerTest->testHandleExceptionLog/', $log[2], 'Stack trace missing.'); + $this->assertContains('[NotFoundException] Kaboom!', $log[0], 'message missing.'); + $this->assertContains('ErrorHandlerTest->testHandleExceptionLog', $log[2], 'Stack trace missing.'); + } + +/** + * test handleException generating log. + * + * @return void + */ + public function testHandleExceptionLogSkipping() { + if (file_exists(LOGS . 'error.log')) { + unlink(LOGS . 'error.log'); + } + Configure::write('Exception.log', true); + Configure::write('Exception.skipLog', array('NotFoundException')); + $notFound = new NotFoundException('Kaboom!'); + $forbidden = new ForbiddenException('Fooled you!'); + + ob_start(); + ErrorHandler::handleException($notFound); + $result = ob_get_clean(); + $this->assertRegExp('/Kaboom!/', $result, 'message missing.'); + + ob_start(); + ErrorHandler::handleException($forbidden); + $result = ob_get_clean(); + $this->assertRegExp('/Fooled you!/', $result, 'message missing.'); + + $log = file(LOGS . 'error.log'); + $this->assertNotContains('[NotFoundException] Kaboom!', $log[0], 'message should not be logged.'); + $this->assertContains('[ForbiddenException] Fooled you!', $log[0], 'message missing.'); } /** @@ -257,8 +282,6 @@ class ErrorHandlerTest extends CakeTestCase { * @return void */ public function testHandleFatalErrorPage() { - $this->skipIf(file_exists(APP . 'app_error.php'), 'App error exists cannot run.'); - $line = __LINE__; ob_start(); @@ -286,8 +309,6 @@ class ErrorHandlerTest extends CakeTestCase { * @return void */ public function testHandleFatalErrorLog() { - $this->skipIf(file_exists(APP . 'app_error.php'), 'App error exists cannot run.'); - if (file_exists(LOGS . 'error.log')) { unlink(LOGS . 'error.log'); } diff --git a/lib/Cake/Test/Case/I18n/I18nTest.php b/lib/Cake/Test/Case/I18n/I18nTest.php index 4c5eba26e..aae136cf5 100644 --- a/lib/Cake/Test/Case/I18n/I18nTest.php +++ b/lib/Cake/Test/Case/I18n/I18nTest.php @@ -108,10 +108,10 @@ class I18nTest extends CakeTestCase { * @return void */ public function testDefaultStrings() { - $singular = $this->__singular(); + $singular = $this->_singular(); $this->assertEquals('Plural Rule 1', $singular); - $plurals = $this->__plural(); + $plurals = $this->_plural(); $this->assertTrue(in_array('0 = 0 or > 1', $plurals)); $this->assertTrue(in_array('1 = 1', $plurals)); $this->assertTrue(in_array('2 = 0 or > 1', $plurals)); @@ -139,10 +139,10 @@ class I18nTest extends CakeTestCase { $this->assertTrue(in_array('24 = 0 or > 1', $plurals)); $this->assertTrue(in_array('25 = 0 or > 1', $plurals)); - $coreSingular = $this->__singularFromCore(); + $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 1 (from core)', $coreSingular); - $corePlurals = $this->__pluralFromCore(); + $corePlurals = $this->_pluralFromCore(); $this->assertTrue(in_array('0 = 0 or > 1 (from core)', $corePlurals)); $this->assertTrue(in_array('1 = 1 (from core)', $corePlurals)); $this->assertTrue(in_array('2 = 0 or > 1 (from core)', $corePlurals)); @@ -197,10 +197,10 @@ class I18nTest extends CakeTestCase { * @return void */ public function assertRulesZero() { - $singular = $this->__singular(); + $singular = $this->_singular(); $this->assertEquals('Plural Rule 0 (translated)', $singular); - $plurals = $this->__plural(); + $plurals = $this->_plural(); $this->assertTrue(in_array('0 ends with any # (translated)', $plurals)); $this->assertTrue(in_array('1 ends with any # (translated)', $plurals)); $this->assertTrue(in_array('2 ends with any # (translated)', $plurals)); @@ -228,10 +228,10 @@ class I18nTest extends CakeTestCase { $this->assertTrue(in_array('24 ends with any # (translated)', $plurals)); $this->assertTrue(in_array('25 ends with any # (translated)', $plurals)); - $coreSingular = $this->__singularFromCore(); + $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 0 (from core translated)', $coreSingular); - $corePlurals = $this->__pluralFromCore(); + $corePlurals = $this->_pluralFromCore(); $this->assertTrue(in_array('0 ends with any # (from core translated)', $corePlurals)); $this->assertTrue(in_array('1 ends with any # (from core translated)', $corePlurals)); $this->assertTrue(in_array('2 ends with any # (from core translated)', $corePlurals)); @@ -286,10 +286,10 @@ class I18nTest extends CakeTestCase { * @return void */ public function assertRulesOne() { - $singular = $this->__singular(); + $singular = $this->_singular(); $this->assertEquals('Plural Rule 1 (translated)', $singular); - $plurals = $this->__plural(); + $plurals = $this->_plural(); $this->assertTrue(in_array('0 = 0 or > 1 (translated)', $plurals)); $this->assertTrue(in_array('1 = 1 (translated)', $plurals)); $this->assertTrue(in_array('2 = 0 or > 1 (translated)', $plurals)); @@ -317,10 +317,10 @@ class I18nTest extends CakeTestCase { $this->assertTrue(in_array('24 = 0 or > 1 (translated)', $plurals)); $this->assertTrue(in_array('25 = 0 or > 1 (translated)', $plurals)); - $coreSingular = $this->__singularFromCore(); + $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 1 (from core translated)', $coreSingular); - $corePlurals = $this->__pluralFromCore(); + $corePlurals = $this->_pluralFromCore(); $this->assertTrue(in_array('0 = 0 or > 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('1 = 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('2 = 0 or > 1 (from core translated)', $corePlurals)); @@ -375,10 +375,10 @@ class I18nTest extends CakeTestCase { * @return void */ public function assertRulesTwo() { - $singular = $this->__singular(); + $singular = $this->_singular(); $this->assertEquals('Plural Rule 2 (translated)', $singular); - $plurals = $this->__plural(); + $plurals = $this->_plural(); $this->assertTrue(in_array('0 = 0 or 1 (translated)', $plurals)); $this->assertTrue(in_array('1 = 0 or 1 (translated)', $plurals)); $this->assertTrue(in_array('2 > 1 (translated)', $plurals)); @@ -406,10 +406,10 @@ class I18nTest extends CakeTestCase { $this->assertTrue(in_array('24 > 1 (translated)', $plurals)); $this->assertTrue(in_array('25 > 1 (translated)', $plurals)); - $coreSingular = $this->__singularFromCore(); + $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 2 (from core translated)', $coreSingular); - $corePlurals = $this->__pluralFromCore(); + $corePlurals = $this->_pluralFromCore(); $this->assertTrue(in_array('0 = 0 or 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('1 = 0 or 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('2 > 1 (from core translated)', $corePlurals)); @@ -464,10 +464,10 @@ class I18nTest extends CakeTestCase { * @return void */ public function assertRulesThree() { - $singular = $this->__singular(); + $singular = $this->_singular(); $this->assertEquals('Plural Rule 3 (translated)', $singular); - $plurals = $this->__plural(); + $plurals = $this->_plural(); $this->assertTrue(in_array('0 = 0 (translated)', $plurals)); $this->assertTrue(in_array('1 ends 1 but not 11 (translated)', $plurals)); $this->assertTrue(in_array('2 everything else (translated)', $plurals)); @@ -495,10 +495,10 @@ class I18nTest extends CakeTestCase { $this->assertTrue(in_array('24 everything else (translated)', $plurals)); $this->assertTrue(in_array('25 everything else (translated)', $plurals)); - $coreSingular = $this->__singularFromCore(); + $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 3 (from core translated)', $coreSingular); - $corePlurals = $this->__pluralFromCore(); + $corePlurals = $this->_pluralFromCore(); $this->assertTrue(in_array('0 = 0 (from core translated)', $corePlurals)); $this->assertTrue(in_array('1 ends 1 but not 11 (from core translated)', $corePlurals)); $this->assertTrue(in_array('2 everything else (from core translated)', $corePlurals)); @@ -553,10 +553,10 @@ class I18nTest extends CakeTestCase { * @return void */ public function assertRulesFour() { - $singular = $this->__singular(); + $singular = $this->_singular(); $this->assertEquals('Plural Rule 4 (translated)', $singular); - $plurals = $this->__plural(); + $plurals = $this->_plural(); $this->assertTrue(in_array('0 everything else (translated)', $plurals)); $this->assertTrue(in_array('1 = 1 (translated)', $plurals)); $this->assertTrue(in_array('2 = 2 (translated)', $plurals)); @@ -584,10 +584,10 @@ class I18nTest extends CakeTestCase { $this->assertTrue(in_array('24 everything else (translated)', $plurals)); $this->assertTrue(in_array('25 everything else (translated)', $plurals)); - $coreSingular = $this->__singularFromCore(); + $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 4 (from core translated)', $coreSingular); - $corePlurals = $this->__pluralFromCore(); + $corePlurals = $this->_pluralFromCore(); $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('1 = 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('2 = 2 (from core translated)', $corePlurals)); @@ -642,10 +642,10 @@ class I18nTest extends CakeTestCase { * @return void */ public function assertRulesFive() { - $singular = $this->__singular(); + $singular = $this->_singular(); $this->assertEquals('Plural Rule 5 (translated)', $singular); - $plurals = $this->__plural(); + $plurals = $this->_plural(); $this->assertTrue(in_array('0 = 0 or ends in 01-19 (translated)', $plurals)); $this->assertTrue(in_array('0 = 0 or ends in 01-19 (translated)', $plurals)); $this->assertTrue(in_array('1 = 1 (translated)', $plurals)); @@ -674,10 +674,10 @@ class I18nTest extends CakeTestCase { $this->assertTrue(in_array('24 everything else (translated)', $plurals)); $this->assertTrue(in_array('25 everything else (translated)', $plurals)); - $coreSingular = $this->__singularFromCore(); + $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 5 (from core translated)', $coreSingular); - $corePlurals = $this->__pluralFromCore(); + $corePlurals = $this->_pluralFromCore(); $this->assertTrue(in_array('0 = 0 or ends in 01-19 (from core translated)', $corePlurals)); $this->assertTrue(in_array('0 = 0 or ends in 01-19 (from core translated)', $corePlurals)); $this->assertTrue(in_array('1 = 1 (from core translated)', $corePlurals)); @@ -733,10 +733,10 @@ class I18nTest extends CakeTestCase { * @return void */ public function assertRulesSix() { - $singular = $this->__singular(); + $singular = $this->_singular(); $this->assertEquals('Plural Rule 6 (translated)', $singular); - $plurals = $this->__plural(); + $plurals = $this->_plural(); $this->assertTrue(in_array('0 ends in 0 or ends in 10-20 (translated)', $plurals)); $this->assertTrue(in_array('1 ends in 1, not 11 (translated)', $plurals)); $this->assertTrue(in_array('2 everything else (translated)', $plurals)); @@ -764,10 +764,10 @@ class I18nTest extends CakeTestCase { $this->assertTrue(in_array('24 everything else (translated)', $plurals)); $this->assertTrue(in_array('25 everything else (translated)', $plurals)); - $coreSingular = $this->__singularFromCore(); + $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 6 (from core translated)', $coreSingular); - $corePlurals = $this->__pluralFromCore(); + $corePlurals = $this->_pluralFromCore(); $this->assertTrue(in_array('0 ends in 0 or ends in 10-20 (from core translated)', $corePlurals)); $this->assertTrue(in_array('1 ends in 1, not 11 (from core translated)', $corePlurals)); $this->assertTrue(in_array('2 everything else (from core translated)', $corePlurals)); @@ -822,10 +822,10 @@ class I18nTest extends CakeTestCase { * @return void */ public function assertRulesSeven() { - $singular = $this->__singular(); + $singular = $this->_singular(); $this->assertEquals('Plural Rule 7 (translated)', $singular); - $plurals = $this->__plural(); + $plurals = $this->_plural(); $this->assertTrue(in_array('0 everything else (translated)', $plurals)); $this->assertTrue(in_array('1 ends in 1, not 11 (translated)', $plurals)); $this->assertTrue(in_array('2 ends in 2-4, not 12-14 (translated)', $plurals)); @@ -853,10 +853,10 @@ class I18nTest extends CakeTestCase { $this->assertTrue(in_array('24 ends in 2-4, not 12-14 (translated)', $plurals)); $this->assertTrue(in_array('25 everything else (translated)', $plurals)); - $coreSingular = $this->__singularFromCore(); + $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 7 (from core translated)', $coreSingular); - $corePlurals = $this->__pluralFromCore(); + $corePlurals = $this->_pluralFromCore(); $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('1 ends in 1, not 11 (from core translated)', $corePlurals)); $this->assertTrue(in_array('2 ends in 2-4, not 12-14 (from core translated)', $corePlurals)); @@ -911,10 +911,10 @@ class I18nTest extends CakeTestCase { * @return void */ public function assertRulesEight() { - $singular = $this->__singular(); + $singular = $this->_singular(); $this->assertEquals('Plural Rule 8 (translated)', $singular); - $plurals = $this->__plural(); + $plurals = $this->_plural(); $this->assertTrue(in_array('0 everything else (translated)', $plurals)); $this->assertTrue(in_array('1 is 1 (translated)', $plurals)); $this->assertTrue(in_array('2 is 2-4 (translated)', $plurals)); @@ -942,10 +942,10 @@ class I18nTest extends CakeTestCase { $this->assertTrue(in_array('24 everything else (translated)', $plurals)); $this->assertTrue(in_array('25 everything else (translated)', $plurals)); - $coreSingular = $this->__singularFromCore(); + $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 8 (from core translated)', $coreSingular); - $corePlurals = $this->__pluralFromCore(); + $corePlurals = $this->_pluralFromCore(); $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('1 is 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('2 is 2-4 (from core translated)', $corePlurals)); @@ -1000,10 +1000,10 @@ class I18nTest extends CakeTestCase { * @return void */ public function assertRulesNine() { - $singular = $this->__singular(); + $singular = $this->_singular(); $this->assertEquals('Plural Rule 9 (translated)', $singular); - $plurals = $this->__plural(); + $plurals = $this->_plural(); $this->assertTrue(in_array('0 everything else (translated)', $plurals)); $this->assertTrue(in_array('0 everything else (translated)', $plurals)); $this->assertTrue(in_array('1 is 1 (translated)', $plurals)); @@ -1032,10 +1032,10 @@ class I18nTest extends CakeTestCase { $this->assertTrue(in_array('24 ends in 2-4, not 12-14 (translated)', $plurals)); $this->assertTrue(in_array('25 everything else (translated)', $plurals)); - $coreSingular = $this->__singularFromCore(); + $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 9 (from core translated)', $coreSingular); - $corePlurals = $this->__pluralFromCore(); + $corePlurals = $this->_pluralFromCore(); $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); @@ -1092,10 +1092,10 @@ class I18nTest extends CakeTestCase { * @return void */ public function assertRulesTen() { - $singular = $this->__singular(); + $singular = $this->_singular(); $this->assertEquals('Plural Rule 10 (translated)', $singular); - $plurals = $this->__plural(); + $plurals = $this->_plural(); $this->assertTrue(in_array('0 everything else (translated)', $plurals)); $this->assertTrue(in_array('0 everything else (translated)', $plurals)); $this->assertTrue(in_array('1 ends in 1 (translated)', $plurals)); @@ -1124,10 +1124,10 @@ class I18nTest extends CakeTestCase { $this->assertTrue(in_array('24 everything else (translated)', $plurals)); $this->assertTrue(in_array('25 everything else (translated)', $plurals)); - $coreSingular = $this->__singularFromCore(); + $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 10 (from core translated)', $coreSingular); - $corePlurals = $this->__pluralFromCore(); + $corePlurals = $this->_pluralFromCore(); $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('1 ends in 1 (from core translated)', $corePlurals)); @@ -1183,10 +1183,10 @@ class I18nTest extends CakeTestCase { * @return void */ public function assertRulesEleven() { - $singular = $this->__singular(); + $singular = $this->_singular(); $this->assertEquals('Plural Rule 11 (translated)', $singular); - $plurals = $this->__plural(); + $plurals = $this->_plural(); $this->assertTrue(in_array('0 everything else (translated)', $plurals)); $this->assertTrue(in_array('1 is 1 (translated)', $plurals)); $this->assertTrue(in_array('2 is 2 (translated)', $plurals)); @@ -1214,10 +1214,10 @@ class I18nTest extends CakeTestCase { $this->assertTrue(in_array('24 everything else (translated)', $plurals)); $this->assertTrue(in_array('25 everything else (translated)', $plurals)); - $coreSingular = $this->__singularFromCore(); + $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 11 (from core translated)', $coreSingular); - $corePlurals = $this->__pluralFromCore(); + $corePlurals = $this->_pluralFromCore(); $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('1 is 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('2 is 2 (from core translated)', $corePlurals)); @@ -1272,10 +1272,10 @@ class I18nTest extends CakeTestCase { * @return void */ public function assertRulesTwelve() { - $singular = $this->__singular(); + $singular = $this->_singular(); $this->assertEquals('Plural Rule 12 (translated)', $singular); - $plurals = $this->__plural(); + $plurals = $this->_plural(); $this->assertTrue(in_array('0 is 0 or 3-10 (translated)', $plurals)); $this->assertTrue(in_array('1 is 1 (translated)', $plurals)); $this->assertTrue(in_array('2 is 2 (translated)', $plurals)); @@ -1303,10 +1303,10 @@ class I18nTest extends CakeTestCase { $this->assertTrue(in_array('24 everything else (translated)', $plurals)); $this->assertTrue(in_array('25 everything else (translated)', $plurals)); - $coreSingular = $this->__singularFromCore(); + $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 12 (from core translated)', $coreSingular); - $corePlurals = $this->__pluralFromCore(); + $corePlurals = $this->_pluralFromCore(); $this->assertTrue(in_array('0 is 0 or 3-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('1 is 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('2 is 2 (from core translated)', $corePlurals)); @@ -1361,10 +1361,10 @@ class I18nTest extends CakeTestCase { * @return void */ public function assertRulesThirteen() { - $singular = $this->__singular(); + $singular = $this->_singular(); $this->assertEquals('Plural Rule 13 (translated)', $singular); - $plurals = $this->__plural(); + $plurals = $this->_plural(); $this->assertTrue(in_array('0 is 0 or ends in 01-10 (translated)', $plurals)); $this->assertTrue(in_array('1 is 1 (translated)', $plurals)); $this->assertTrue(in_array('2 is 0 or ends in 01-10 (translated)', $plurals)); @@ -1392,10 +1392,10 @@ class I18nTest extends CakeTestCase { $this->assertTrue(in_array('24 everything else (translated)', $plurals)); $this->assertTrue(in_array('25 everything else (translated)', $plurals)); - $coreSingular = $this->__singularFromCore(); + $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 13 (from core translated)', $coreSingular); - $corePlurals = $this->__pluralFromCore(); + $corePlurals = $this->_pluralFromCore(); $this->assertTrue(in_array('0 is 0 or ends in 01-10 (from core translated)', $corePlurals)); $this->assertTrue(in_array('1 is 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('2 is 0 or ends in 01-10 (from core translated)', $corePlurals)); @@ -1450,10 +1450,10 @@ class I18nTest extends CakeTestCase { * @return void */ public function assertRulesFourteen() { - $singular = $this->__singular(); + $singular = $this->_singular(); $this->assertEquals('Plural Rule 14 (translated)', $singular); - $plurals = $this->__plural(); + $plurals = $this->_plural(); $this->assertTrue(in_array('0 everything else (translated)', $plurals)); $this->assertTrue(in_array('1 ends in 1 (translated)', $plurals)); $this->assertTrue(in_array('2 ends in 2 (translated)', $plurals)); @@ -1481,10 +1481,10 @@ class I18nTest extends CakeTestCase { $this->assertTrue(in_array('24 everything else (translated)', $plurals)); $this->assertTrue(in_array('25 everything else (translated)', $plurals)); - $coreSingular = $this->__singularFromCore(); + $coreSingular = $this->_singularFromCore(); $this->assertEquals('Plural Rule 14 (from core translated)', $coreSingular); - $corePlurals = $this->__pluralFromCore(); + $corePlurals = $this->_pluralFromCore(); $this->assertTrue(in_array('0 everything else (from core translated)', $corePlurals)); $this->assertTrue(in_array('1 ends in 1 (from core translated)', $corePlurals)); $this->assertTrue(in_array('2 ends in 2 (from core translated)', $corePlurals)); @@ -1520,10 +1520,10 @@ class I18nTest extends CakeTestCase { */ public function testSetLanguageWithSession() { $_SESSION['Config']['language'] = 'po'; - $singular = $this->__singular(); + $singular = $this->_singular(); $this->assertEquals('Po (translated)', $singular); - $plurals = $this->__plural(); + $plurals = $this->_plural(); $this->assertTrue(in_array('0 everything else (po translated)', $plurals)); $this->assertTrue(in_array('1 is 1 (po translated)', $plurals)); $this->assertTrue(in_array('2 is 2-4 (po translated)', $plurals)); @@ -1560,13 +1560,13 @@ class I18nTest extends CakeTestCase { */ public function testNoCoreTranslation() { Configure::write('Config.language', 'po'); - $singular = $this->__singular(); + $singular = $this->_singular(); $this->assertEquals('Po (translated)', $singular); - $coreSingular = $this->__singularFromCore(); + $coreSingular = $this->_singularFromCore(); $this->assertNotEquals('Po (from core translated)', $coreSingular); - $corePlurals = $this->__pluralFromCore(); + $corePlurals = $this->_pluralFromCore(); $this->assertFalse(in_array('0 everything else (from core translated)', $corePlurals)); $this->assertFalse(in_array('1 is 1 (from core translated)', $corePlurals)); $this->assertFalse(in_array('2 is 2-4 (from core translated)', $corePlurals)); @@ -1606,10 +1606,10 @@ class I18nTest extends CakeTestCase { )); Configure::write('Config.language', 'po'); - $singular = $this->__domainSingular(); + $singular = $this->_domainSingular(); $this->assertEquals('Plural Rule 1 (from plugin)', $singular); - $plurals = $this->__domainPlural(); + $plurals = $this->_domainPlural(); $this->assertTrue(in_array('0 = 0 or > 1 (from plugin)', $plurals)); $this->assertTrue(in_array('1 = 1 (from plugin)', $plurals)); $this->assertTrue(in_array('2 = 0 or > 1 (from plugin)', $plurals)); @@ -1761,7 +1761,7 @@ class I18nTest extends CakeTestCase { */ public function testCategory() { Configure::write('Config.language', 'po'); - $category = $this->__category(); + $category = $this->_category(); $this->assertEquals('Monetary Po (translated)', $category); } @@ -1773,10 +1773,10 @@ class I18nTest extends CakeTestCase { public function testPluginCategory() { Configure::write('Config.language', 'po'); - $singular = $this->__domainCategorySingular(); + $singular = $this->_domainCategorySingular(); $this->assertEquals('Monetary Plural Rule 1 (from plugin)', $singular); - $plurals = $this->__domainCategoryPlural(); + $plurals = $this->_domainCategoryPlural(); $this->assertTrue(in_array('Monetary 0 = 0 or > 1 (from plugin)', $plurals)); $this->assertTrue(in_array('Monetary 1 = 1 (from plugin)', $plurals)); } @@ -1788,10 +1788,10 @@ class I18nTest extends CakeTestCase { */ public function testCategoryThenSingular() { Configure::write('Config.language', 'po'); - $category = $this->__category(); + $category = $this->_category(); $this->assertEquals('Monetary Po (translated)', $category); - $singular = $this->__singular(); + $singular = $this->_singular(); $this->assertEquals('Po (translated)', $singular); } @@ -1869,7 +1869,7 @@ class I18nTest extends CakeTestCase { * * @return void */ - private function __domainCategorySingular($domain = 'test_plugin', $category = 3) { + protected function _domainCategorySingular($domain = 'test_plugin', $category = 3) { $singular = __dc($domain, 'Plural Rule 1', $category); return $singular; } @@ -1879,7 +1879,7 @@ class I18nTest extends CakeTestCase { * * @return void */ - private function __domainCategoryPlural($domain = 'test_plugin', $category = 3) { + protected function _domainCategoryPlural($domain = 'test_plugin', $category = 3) { $plurals = array(); for ($number = 0; $number <= 25; $number++) { $plurals[] = sprintf(__dcn($domain, '%d = 1', '%d = 0 or > 1', (float)$number, $category), (float)$number); @@ -1892,7 +1892,7 @@ class I18nTest extends CakeTestCase { * * @return void */ - private function __domainSingular($domain = 'test_plugin') { + protected function _domainSingular($domain = 'test_plugin') { $singular = __d($domain, 'Plural Rule 1'); return $singular; } @@ -1902,7 +1902,7 @@ class I18nTest extends CakeTestCase { * * @return void */ - private function __domainPlural($domain = 'test_plugin') { + protected function _domainPlural($domain = 'test_plugin') { $plurals = array(); for ($number = 0; $number <= 25; $number++) { $plurals[] = sprintf(__dn($domain, '%d = 1', '%d = 0 or > 1', (float)$number), (float)$number); @@ -1915,7 +1915,7 @@ class I18nTest extends CakeTestCase { * * @return void */ - private function __category($category = 3) { + protected function _category($category = 3) { $singular = __c('Plural Rule 1', $category); return $singular; } @@ -1925,7 +1925,7 @@ class I18nTest extends CakeTestCase { * * @return void */ - private function __singular() { + protected function _singular() { $singular = __('Plural Rule 1'); return $singular; } @@ -1935,7 +1935,7 @@ class I18nTest extends CakeTestCase { * * @return void */ - private function __plural() { + protected function _plural() { $plurals = array(); for ($number = 0; $number <= 25; $number++) { $plurals[] = sprintf(__n('%d = 1', '%d = 0 or > 1', (float)$number), (float)$number); @@ -1948,7 +1948,7 @@ class I18nTest extends CakeTestCase { * * @return void */ - private function __singularFromCore() { + protected function _singularFromCore() { $singular = __('Plural Rule 1 (from core)'); return $singular; } @@ -1958,7 +1958,7 @@ class I18nTest extends CakeTestCase { * * @return void */ - private function __pluralFromCore() { + protected function _pluralFromCore() { $plurals = array(); for ($number = 0; $number <= 25; $number++) { $plurals[] = sprintf(__n('%d = 1 (from core)', '%d = 0 or > 1 (from core)', (float)$number), (float)$number); diff --git a/lib/Cake/Test/Case/I18n/L10nTest.php b/lib/Cake/Test/Case/I18n/L10nTest.php index a7740099d..0a6468146 100644 --- a/lib/Cake/Test/Case/I18n/L10nTest.php +++ b/lib/Cake/Test/Case/I18n/L10nTest.php @@ -27,6 +27,16 @@ App::uses('L10n', 'I18n'); */ class L10nTest extends CakeTestCase { +/** + * setUp method + * + * @return void + */ + public function setUp() { + parent::setUp(); + Configure::delete('Config.language'); + } + /** * testGet method * @@ -40,14 +50,14 @@ class L10nTest extends CakeTestCase { $this->assertEquals('en', $lang); $this->assertEquals('English', $localize->language); - $this->assertEquals(array('eng', 'eng'), $localize->languagePath); + $this->assertEquals(array('eng'), $localize->languagePath); $this->assertEquals('eng', $localize->locale); // Map Entry $localize->get('eng'); $this->assertEquals('English', $localize->language); - $this->assertEquals(array('eng', 'eng'), $localize->languagePath); + $this->assertEquals(array('eng'), $localize->languagePath); $this->assertEquals('eng', $localize->locale); // Catalog Entry @@ -58,8 +68,7 @@ class L10nTest extends CakeTestCase { $this->assertEquals('en_ca', $localize->locale); // Default Entry - define('DEFAULT_LANGUAGE', 'en-us'); - + $localize->default = 'en-us'; $lang = $localize->get('use_default'); $this->assertEquals('en-us', $lang); @@ -70,14 +79,6 @@ class L10nTest extends CakeTestCase { $localize->get('es'); $localize->get(''); $this->assertEquals('en-us', $localize->lang); - - // Using $this->default - $localize = new L10n(); - - $localize->get('use_default'); - $this->assertEquals('English (United States)', $localize->language); - $this->assertEquals(array('en_us', 'eng', 'eng'), $localize->languagePath); - $this->assertEquals('en_us', $localize->locale); } /** @@ -94,7 +95,7 @@ class L10nTest extends CakeTestCase { $this->assertEquals('en-ca', $lang); $this->assertEquals('English (Canadian)', $localize->language); - $this->assertEquals(array('en_ca', 'eng', 'eng'), $localize->languagePath); + $this->assertEquals(array('en_ca', 'eng'), $localize->languagePath); $this->assertEquals('en_ca', $localize->locale); $_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'es_mx'; @@ -102,19 +103,52 @@ class L10nTest extends CakeTestCase { $this->assertEquals('es-mx', $lang); $this->assertEquals('Spanish (Mexican)', $localize->language); - $this->assertEquals(array('es_mx', 'spa', 'eng'), $localize->languagePath); + $this->assertEquals(array('es_mx', 'spa'), $localize->languagePath); $this->assertEquals('es_mx', $localize->locale); + $localize = new L10n(); + $localize->default = 'en-us'; + $lang = $localize->get(); + $this->assertEquals(array('es_mx', 'spa', 'eng'), $localize->languagePath); + $_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'en_xy,en_ca'; $localize->get(); $this->assertEquals('English', $localize->language); - $this->assertEquals(array('eng', 'eng', 'eng'), $localize->languagePath); + $this->assertEquals(array('eng'), $localize->languagePath); $this->assertEquals('eng', $localize->locale); $_SERVER = $serverBackup; } +/** + * testGet method with deprecated constant DEFAULT_LANGUAGE + * + * @return void + */ + public function testGetWithDeprecatedConstant() { + $this->skipIf(defined('DEFAULT_LANGUAGE'), 'Cannot re-define already defined constant.'); + + define('DEFAULT_LANGUAGE', 'en-us'); + $localize = new L10n(); + + $lang = $localize->get('use_default'); + + $this->assertEquals('en-us', $lang); + $this->assertEquals('English (United States)', $localize->language); + $this->assertEquals(array('en_us', 'eng'), $localize->languagePath); + $this->assertEquals('en_us', $localize->locale); + + $localize = new L10n(); + + $lang = $localize->get(); + + $this->assertEquals('en-us', $lang); + $this->assertEquals('English (United States)', $localize->language); + $this->assertEquals(array('en_us', 'eng'), $localize->languagePath); + $this->assertEquals('en_us', $localize->locale); + } + /** * testMap method * @@ -299,6 +333,10 @@ class L10nTest extends CakeTestCase { $expected = array('jpn' => 'ja', 'ja' => 'jpn'); $this->assertEquals($expected, $result); + $result = $localize->map(array('kaz', 'kk')); + $expected = array('kaz' => 'kk', 'kk' => 'kaz'); + $this->assertEquals($expected, $result); + $result = $localize->map(array('kor', 'ko')); $expected = array('kor' => 'ko', 'ko' => 'kor'); $this->assertEquals($expected, $result); @@ -367,8 +405,8 @@ class L10nTest extends CakeTestCase { $expected = array('rus' => 'ru', 'ru' => 'rus'); $this->assertEquals($expected, $result); - $result = $localize->map(array('smi', 'sz')); - $expected = array('smi' => 'sz', 'sz' => 'smi'); + $result = $localize->map(array('sme', 'se')); + $expected = array('sme' => 'se', 'se' => 'sme'); $this->assertEquals($expected, $result); $result = $localize->map(array('srp', 'sr')); @@ -532,10 +570,9 @@ class L10nTest extends CakeTestCase { ); $this->assertEquals($expected, $result); - $result = $localize->catalog(array('e', 'el')); + $result = $localize->catalog(array('el')); $expected = array( - 'e' => array('language' => 'Greek', 'locale' => 'gre', 'localeFallback' => 'gre', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'el' => array('language' => 'Greek', 'locale' => 'gre', 'localeFallback' => 'gre', 'charset' => 'utf-8', 'direction' => 'ltr') + 'el' => array('language' => 'Greek', 'locale' => 'ell', 'localeFallback' => 'ell', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); @@ -595,7 +632,7 @@ class L10nTest extends CakeTestCase { $result = $localize->catalog(array('fa')); $expected = array( - 'fa' => array('language' => 'Farsi', 'locale' => 'per', 'localeFallback' => 'per', 'charset' => 'utf-8', 'direction' => 'rtl') + 'fa' => array('language' => 'Farsi', 'locale' => 'fas', 'localeFallback' => 'fas', 'charset' => 'utf-8', 'direction' => 'rtl') ); $this->assertEquals($expected, $result); @@ -671,10 +708,9 @@ class L10nTest extends CakeTestCase { ); $this->assertEquals($expected, $result); - $result = $localize->catalog(array('id', 'in')); + $result = $localize->catalog(array('id')); $expected = array( 'id' => array('language' => 'Indonesian', 'locale' => 'ind', 'localeFallback' => 'ind', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'in' => array('language' => 'Indonesian', 'locale' => 'ind', 'localeFallback' => 'ind', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); @@ -697,6 +733,12 @@ class L10nTest extends CakeTestCase { ); $this->assertEquals($expected, $result); + $result = $localize->catalog(array('kk')); + $expected = array( + 'kk' => array('language' => 'Kazakh', 'locale' => 'kaz', 'localeFallback' => 'kaz', 'charset' => 'utf-8', 'direction' => 'ltr') + ); + $this->assertEquals($expected, $result); + $result = $localize->catalog(array('ko', 'ko-kp', 'ko-kr')); $expected = array( 'ko' => array('language' => 'Korean', 'locale' => 'kor', 'localeFallback' => 'kor', 'charset' => 'kr', 'direction' => 'ltr'), @@ -727,7 +769,7 @@ class L10nTest extends CakeTestCase { $result = $localize->catalog(array('mk', 'mk-mk')); $expected = array( - 'mk' => array('language' => 'FYRO Macedonian', 'locale' => 'mk', 'localeFallback' => 'mkd', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'mk' => array('language' => 'FYRO Macedonian', 'locale' => 'mkd', 'localeFallback' => 'mkd', 'charset' => 'utf-8', 'direction' => 'ltr'), 'mk-mk' => array('language' => 'Macedonian', 'locale' => 'mk_mk', 'localeFallback' => 'mkd', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); @@ -744,9 +786,8 @@ class L10nTest extends CakeTestCase { ); $this->assertEquals($expected, $result); - $result = $localize->catalog(array('n', 'nl', 'nl-be')); + $result = $localize->catalog(array('nl', 'nl-be')); $expected = array( - 'n' => array('language' => 'Dutch (Standard)', 'locale' => 'nld', 'localeFallback' => 'nld', 'charset' => 'utf-8', 'direction' => 'ltr'), 'nl' => array('language' => 'Dutch (Standard)', 'locale' => 'nld', 'localeFallback' => 'nld', 'charset' => 'utf-8', 'direction' => 'ltr'), 'nl-be' => array('language' => 'Dutch (Belgium)', 'locale' => 'nl_be', 'localeFallback' => 'nld', 'charset' => 'utf-8', 'direction' => 'ltr') ); @@ -777,9 +818,8 @@ class L10nTest extends CakeTestCase { ); $this->assertEquals($expected, $result); - $result = $localize->catalog(array('p', 'pl')); + $result = $localize->catalog(array('pl')); $expected = array( - 'p' => array('language' => 'Polish', 'locale' => 'pol', 'localeFallback' => 'pol', 'charset' => 'utf-8', 'direction' => 'ltr'), 'pl' => array('language' => 'Polish', 'locale' => 'pol', 'localeFallback' => 'pol', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); @@ -841,15 +881,9 @@ class L10nTest extends CakeTestCase { ); $this->assertEquals($expected, $result); - $result = $localize->catalog(array('sx')); + $result = $localize->catalog(array('se')); $expected = array( - 'sx' => array('language' => 'Sutu', 'locale' => 'sx', 'localeFallback' => 'sx', 'charset' => 'utf-8', 'direction' => 'ltr') - ); - $this->assertEquals($expected, $result); - - $result = $localize->catalog(array('sz')); - $expected = array( - 'sz' => array('language' => 'Sami (Lappish)', 'locale' => 'smi', 'localeFallback' => 'smi', 'charset' => 'utf-8', 'direction' => 'ltr') + 'se' => array('language' => 'Sami', 'locale' => 'sme', 'localeFallback' => 'sme', 'charset' => 'utf-8', 'direction' => 'ltr') ); $this->assertEquals($expected, $result); @@ -936,11 +970,10 @@ class L10nTest extends CakeTestCase { ); $this->assertEquals($expected, $result); - $result = $localize->catalog(array('en-nz', 'es-do', 'sz', 'ar-lb', 'zh-hk', 'pt-br')); + $result = $localize->catalog(array('en-nz', 'es-do', 'ar-lb', 'zh-hk', 'pt-br')); $expected = array( 'en-nz' => array('language' => 'English (New Zealand)', 'locale' => 'en_nz', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), 'es-do' => array('language' => 'Spanish (Dominican Republic)', 'locale' => 'es_do', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'sz' => array('language' => 'Sami (Lappish)', 'locale' => 'smi', 'localeFallback' => 'smi', 'charset' => 'utf-8', 'direction' => 'ltr'), 'ar-lb' => array('language' => 'Arabic (Lebanon)', 'locale' => 'ar_lb', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), 'zh-hk' => array('language' => 'Chinese (Hong Kong)', 'locale' => 'zh_hk', 'localeFallback' => 'zho', 'charset' => 'utf-8', 'direction' => 'ltr'), 'pt-br' => array('language' => 'Portuguese (Brazil)', 'locale' => 'pt_br', 'localeFallback' => 'por', 'charset' => 'utf-8', 'direction' => 'ltr') diff --git a/lib/Cake/Test/Case/Log/CakeLogTest.php b/lib/Cake/Test/Case/Log/CakeLogTest.php index f2d1d94b7..20a5c9132 100644 --- a/lib/Cake/Test/Case/Log/CakeLogTest.php +++ b/lib/Cake/Test/Case/Log/CakeLogTest.php @@ -87,6 +87,18 @@ class CakeLogTest extends CakeTestCase { * @return void */ public function testValidKeyName() { + CakeLog::config('valid', array('engine' => 'File')); + $stream = CakeLog::stream('valid'); + $this->assertInstanceOf('FileLog', $stream); + CakeLog::drop('valid'); + } + +/** + * test config() with valid key name including the deprecated Log suffix + * + * @return void + */ + public function testValidKeyNameLogSuffix() { CakeLog::config('valid', array('engine' => 'FileLog')); $stream = CakeLog::stream('valid'); $this->assertInstanceOf('FileLog', $stream); @@ -100,7 +112,7 @@ class CakeLogTest extends CakeTestCase { * @return void */ public function testInvalidKeyName() { - CakeLog::config('1nv', array('engine' => 'FileLog')); + CakeLog::config('1nv', array('engine' => 'File')); } /** @@ -144,7 +156,7 @@ class CakeLogTest extends CakeTestCase { */ public function testConfig() { CakeLog::config('file', array( - 'engine' => 'FileLog', + 'engine' => 'File', 'path' => LOGS )); $result = CakeLog::configured(); @@ -168,7 +180,7 @@ class CakeLogTest extends CakeTestCase { */ public function testDrop() { CakeLog::config('file', array( - 'engine' => 'FileLog', + 'engine' => 'File', 'path' => LOGS )); $result = CakeLog::configured(); @@ -214,12 +226,12 @@ class CakeLogTest extends CakeTestCase { unlink(LOGS . 'eggs.log'); } CakeLog::config('spam', array( - 'engine' => 'FileLog', + 'engine' => 'File', 'types' => 'debug', 'file' => 'spam', )); CakeLog::config('eggs', array( - 'engine' => 'FileLog', + 'engine' => 'File', 'types' => array('eggs', 'debug', 'error', 'warning'), 'file' => 'eggs', )); @@ -253,7 +265,7 @@ class CakeLogTest extends CakeTestCase { */ public function testStreamEnable() { CakeLog::config('spam', array( - 'engine' => 'FileLog', + 'engine' => 'File', 'file' => 'spam', )); $this->assertTrue(CakeLog::enabled('spam')); @@ -268,7 +280,7 @@ class CakeLogTest extends CakeTestCase { */ public function testStreamDisable() { CakeLog::config('spam', array( - 'engine' => 'FileLog', + 'engine' => 'File', 'file' => 'spam', )); $this->assertTrue(CakeLog::enabled('spam')); @@ -298,12 +310,12 @@ class CakeLogTest extends CakeTestCase { protected function _resetLogConfig() { CakeLog::config('debug', array( - 'engine' => 'FileLog', + 'engine' => 'File', 'types' => array('notice', 'info', 'debug'), 'file' => 'debug', )); CakeLog::config('error', array( - 'engine' => 'FileLog', + 'engine' => 'File', 'types' => array('warning', 'error', 'critical', 'alert', 'emergency'), 'file' => 'error', )); @@ -339,7 +351,7 @@ class CakeLogTest extends CakeTestCase { $this->_resetLogConfig(); CakeLog::config('shops', array( - 'engine' => 'FileLog', + 'engine' => 'File', 'types' => array('info', 'notice', 'warning'), 'scopes' => array('transactions', 'orders'), 'file' => 'shops', @@ -393,13 +405,13 @@ class CakeLogTest extends CakeTestCase { $this->_deleteLogs(); CakeLog::config('shops', array( - 'engine' => 'FileLog', + 'engine' => 'File', 'types' => array('info', 'notice', 'warning'), 'scopes' => array('transactions', 'orders'), 'file' => 'shops.log', )); CakeLog::config('eggs', array( - 'engine' => 'FileLog', + 'engine' => 'File', 'types' => array('info', 'notice', 'warning'), 'scopes' => array('eggs'), 'file' => 'eggs.log', @@ -426,7 +438,7 @@ class CakeLogTest extends CakeTestCase { $this->_deleteLogs(); CakeLog::config('string-scope', array( - 'engine' => 'FileLog', + 'engine' => 'File', 'types' => array('info', 'notice', 'warning'), 'scopes' => 'string-scope', 'file' => 'string-scope.log' @@ -437,7 +449,7 @@ class CakeLogTest extends CakeTestCase { CakeLog::drop('string-scope'); CakeLog::config('shops', array( - 'engine' => 'FileLog', + 'engine' => 'File', 'types' => array('info', 'notice', 'warning'), 'scopes' => array('transactions', 'orders'), 'file' => 'shops.log', @@ -526,7 +538,7 @@ class CakeLogTest extends CakeTestCase { $this->_resetLogConfig(); CakeLog::config('shops', array( - 'engine' => 'FileLog', + 'engine' => 'File', 'types' => array('info', 'debug', 'notice', 'warning'), 'scopes' => array('transactions', 'orders'), 'file' => 'shops', @@ -563,12 +575,12 @@ class CakeLogTest extends CakeTestCase { $this->_deleteLogs(); CakeLog::config('debug', array( - 'engine' => 'FileLog', + 'engine' => 'File', 'types' => array('notice', 'info', 'debug'), 'file' => 'debug', )); CakeLog::config('error', array( - 'engine' => 'FileLog', + 'engine' => 'File', 'types' => array('emergency', 'alert', 'critical', 'error', 'warning'), 'file' => 'error', )); @@ -677,12 +689,12 @@ class CakeLogTest extends CakeTestCase { $this->assertContains('Error: ' . $testMessage, $contents); CakeLog::config('spam', array( - 'engine' => 'FileLog', + 'engine' => 'File', 'file' => 'spam.log', 'types' => 'spam', )); CakeLog::config('eggs', array( - 'engine' => 'FileLog', + 'engine' => 'File', 'file' => 'eggs.log', 'types' => array('spam', 'eggs'), )); diff --git a/lib/Cake/Test/Case/Log/Engine/ConsoleLogTest.php b/lib/Cake/Test/Case/Log/Engine/ConsoleLogTest.php index 4583b18f7..7cfb3d0ed 100644 --- a/lib/Cake/Test/Case/Log/Engine/ConsoleLogTest.php +++ b/lib/Cake/Test/Case/Log/Engine/ConsoleLogTest.php @@ -52,12 +52,12 @@ class ConsoleLogTest extends CakeTestCase { public function setUp() { parent::setUp(); CakeLog::config('debug', array( - 'engine' => 'FileLog', + 'engine' => 'File', 'types' => array('notice', 'info', 'debug'), 'file' => 'debug', )); CakeLog::config('error', array( - 'engine' => 'FileLog', + 'engine' => 'File', 'types' => array('error', 'warning'), 'file' => 'error', )); @@ -78,7 +78,7 @@ class ConsoleLogTest extends CakeTestCase { */ public function testConsoleOutputWrites() { TestCakeLog::config('test_console_log', array( - 'engine' => 'TestConsoleLog', + 'engine' => 'TestConsole', )); $mock = $this->getMock('TestConsoleLog', array('write'), array( @@ -97,7 +97,7 @@ class ConsoleLogTest extends CakeTestCase { */ public function testCombinedLogWriting() { TestCakeLog::config('test_console_log', array( - 'engine' => 'TestConsoleLog', + 'engine' => 'TestConsole', )); $mock = $this->getMock('TestConsoleLog', array('write'), array( array('types' => 'error'), @@ -133,7 +133,7 @@ class ConsoleLogTest extends CakeTestCase { */ public function testDefaultOutputAs() { TestCakeLog::config('test_console_log', array( - 'engine' => 'TestConsoleLog', + 'engine' => 'TestConsole', )); if (DS === '\\' && !(bool)env('ANSICON')) { $expected = ConsoleOutput::PLAIN; diff --git a/lib/Cake/Test/Case/Log/Engine/FileLogTest.php b/lib/Cake/Test/Case/Log/Engine/FileLogTest.php index 94fa475bd..825adb9f0 100644 --- a/lib/Cake/Test/Case/Log/Engine/FileLogTest.php +++ b/lib/Cake/Test/Case/Log/Engine/FileLogTest.php @@ -33,36 +33,26 @@ class FileLogTest extends CakeTestCase { * @return void */ public function testLogFileWriting() { - if (file_exists(LOGS . 'error.log')) { - unlink(LOGS . 'error.log'); - } + $this->_deleteLogs(LOGS); + $log = new FileLog(); $log->write('warning', 'Test warning'); $this->assertTrue(file_exists(LOGS . 'error.log')); $result = file_get_contents(LOGS . 'error.log'); $this->assertRegExp('/^2[0-9]{3}-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+ Warning: Test warning/', $result); - unlink(LOGS . 'error.log'); - if (file_exists(LOGS . 'debug.log')) { - unlink(LOGS . 'debug.log'); - } $log->write('debug', 'Test warning'); $this->assertTrue(file_exists(LOGS . 'debug.log')); $result = file_get_contents(LOGS . 'debug.log'); $this->assertRegExp('/^2[0-9]{3}-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+ Debug: Test warning/', $result); - unlink(LOGS . 'debug.log'); - if (file_exists(LOGS . 'random.log')) { - unlink(LOGS . 'random.log'); - } $log->write('random', 'Test warning'); $this->assertTrue(file_exists(LOGS . 'random.log')); $result = file_get_contents(LOGS . 'random.log'); $this->assertRegExp('/^2[0-9]{3}-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+ Random: Test warning/', $result); - unlink(LOGS . 'random.log'); } /** @@ -72,13 +62,132 @@ class FileLogTest extends CakeTestCase { */ public function testPathSetting() { $path = TMP . 'tests' . DS; - if (file_exists(LOGS . 'error.log')) { - unlink(LOGS . 'error.log'); - } + $this->_deleteLogs($path); $log = new FileLog(compact('path')); $log->write('warning', 'Test warning'); $this->assertTrue(file_exists($path . 'error.log')); + } + +/** + * test log rotation + * + * @return void + */ + public function testRotation() { + $path = TMP . 'tests' . DS; + $this->_deleteLogs($path); + + file_put_contents($path . 'error.log', "this text is under 35 bytes\n"); + $log = new FileLog(array( + 'path' => $path, + 'size' => 35, + 'rotate' => 2 + )); + $log->write('warning', 'Test warning one'); + $this->assertTrue(file_exists($path . 'error.log')); + + $result = file_get_contents($path . 'error.log'); + $this->assertRegExp('/Warning: Test warning one/', $result); + $this->assertEquals(0, count(glob($path . 'error.log.*'))); + + clearstatcache(); + $log->write('warning', 'Test warning second'); + + $files = glob($path . 'error.log.*'); + $this->assertEquals(1, count($files)); + + $result = file_get_contents($files[0]); + $this->assertRegExp('/this text is under 35 bytes/', $result); + $this->assertRegExp('/Warning: Test warning one/', $result); + + sleep(1); + clearstatcache(); + $log->write('warning', 'Test warning third'); + + $result = file_get_contents($path . 'error.log'); + $this->assertRegExp('/Warning: Test warning third/', $result); + + $files = glob($path . 'error.log.*'); + $this->assertEquals(2, count($files)); + + $result = file_get_contents($files[0]); + $this->assertRegExp('/this text is under 35 bytes/', $result); + + $result = file_get_contents($files[1]); + $this->assertRegExp('/Warning: Test warning second/', $result); + + sleep(1); + clearstatcache(); + $log->write('warning', 'Test warning fourth'); + + // rotate count reached so file count should not increase + $files = glob($path . 'error.log.*'); + $this->assertEquals(2, count($files)); + + $result = file_get_contents($path . 'error.log'); + $this->assertRegExp('/Warning: Test warning fourth/', $result); + + $result = file_get_contents(array_pop($files)); + $this->assertRegExp('/Warning: Test warning third/', $result); + + $result = file_get_contents(array_pop($files)); + $this->assertRegExp('/Warning: Test warning second/', $result); + + file_put_contents($path . 'debug.log', "this text is just greater than 35 bytes\n"); + $log = new FileLog(array( + 'path' => $path, + 'size' => 35, + 'rotate' => 0 + )); + $log->write('debug', 'Test debug'); + $this->assertTrue(file_exists($path . 'debug.log')); + + $result = file_get_contents($path . 'debug.log'); + $this->assertRegExp('/^2[0-9]{3}-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+ Debug: Test debug/', $result); + $this->assertFalse(strstr($result, 'greater than 5 bytes')); + $this->assertEquals(0, count(glob($path . 'debug.log.*'))); + } + + public function testMaskSetting() { + if (DS === '\\') { + $this->markTestSkipped('File permission testing does not work on Windows.'); + } + + $path = TMP . 'tests' . DS; + $this->_deleteLogs($path); + + $log = new FileLog(array('path' => $path, 'mask' => 0666)); + $log->write('warning', 'Test warning one'); + $result = substr(sprintf('%o', fileperms($path . 'error.log')), -4); + $expected = '0666'; + $this->assertEquals($expected, $result); + unlink($path . 'error.log'); + + $log = new FileLog(array('path' => $path, 'mask' => 0644)); + $log->write('warning', 'Test warning two'); + $result = substr(sprintf('%o', fileperms($path . 'error.log')), -4); + $expected = '0644'; + $this->assertEquals($expected, $result); + unlink($path . 'error.log'); + + $log = new FileLog(array('path' => $path, 'mask' => 0640)); + $log->write('warning', 'Test warning three'); + $result = substr(sprintf('%o', fileperms($path . 'error.log')), -4); + $expected = '0640'; + $this->assertEquals($expected, $result); unlink($path . 'error.log'); } + +/** + * helper function to clears all log files in specified directory + * + * @return void + */ + protected function _deleteLogs($dir) { + $files = array_merge(glob($dir . '*.log'), glob($dir . '*.log.*')); + foreach ($files as $file) { + unlink($file); + } + } } diff --git a/lib/Cake/Test/Case/Log/Engine/SyslogLogTest.php b/lib/Cake/Test/Case/Log/Engine/SyslogLogTest.php new file mode 100644 index 000000000..ca6c0055e --- /dev/null +++ b/lib/Cake/Test/Case/Log/Engine/SyslogLogTest.php @@ -0,0 +1,94 @@ + + * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The MIT License + * For full copyright and license information, please see the LICENSE.txt + * Redistributions of files must retain the above copyright notice + * + * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests + * @package Cake.Test.Case.Log.Engine + * @since CakePHP(tm) v 2.4 + * @license MIT License (http://www.opensource.org/licenses/mit-license.php) + */ +App::uses('SyslogLog', 'Log/Engine'); + +/** + * SyslogLogTest class + * + * @package Cake.Test.Case.Log.Engine + */ +class SyslogLogTest extends CakeTestCase { + +/** + * Tests that the connection to the logger is open with the right arguments + * + * @return void + */ + public function testOpenLog() { + $log = $this->getMock('SyslogLog', array('_open', '_write')); + $log->expects($this->once())->method('_open')->with('', LOG_ODELAY, LOG_USER); + $log->write('debug', 'message'); + + $log = $this->getMock('SyslogLog', array('_open', '_write')); + $log->config(array( + 'prefix' => 'thing', + 'flag' => LOG_NDELAY, + 'facility' => LOG_MAIL, + 'format' => '%s: %s' + )); + $log->expects($this->once())->method('_open') + ->with('thing', LOG_NDELAY, LOG_MAIL); + $log->write('debug', 'message'); + } + +/** + * Tests that single lines are written to syslog + * + * @dataProvider typesProvider + * @return void + */ + public function testWriteOneLine($type, $expected) { + $log = $this->getMock('SyslogLog', array('_open', '_write')); + $log->expects($this->once())->method('_write')->with($expected, $type . ': Foo'); + $log->write($type, 'Foo'); + } + +/** + * Tests that multiple lines are split and logged separately + * + * @return void + */ + public function testWriteMultiLine() { + $log = $this->getMock('SyslogLog', array('_open', '_write')); + $log->expects($this->at(1))->method('_write')->with(LOG_DEBUG, 'debug: Foo'); + $log->expects($this->at(2))->method('_write')->with(LOG_DEBUG, 'debug: Bar'); + $log->expects($this->exactly(2))->method('_write'); + $log->write('debug', "Foo\nBar"); + } + +/** + * Data provider for the write function test + * + * @return array + */ + public function typesProvider() { + return array( + array('emergency', LOG_EMERG), + array('alert', LOG_ALERT), + array('critical', LOG_CRIT), + array('error', LOG_ERR), + array('warning', LOG_WARNING), + array('notice', LOG_NOTICE), + array('info', LOG_INFO), + array('debug', LOG_DEBUG) + ); + } + +} + diff --git a/lib/Cake/Test/Case/Log/LogEngineCollectionTest.php b/lib/Cake/Test/Case/Log/LogEngineCollectionTest.php new file mode 100644 index 000000000..92133d10b --- /dev/null +++ b/lib/Cake/Test/Case/Log/LogEngineCollectionTest.php @@ -0,0 +1,90 @@ + + * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The MIT License + * For full copyright and license information, please see the LICENSE.txt + * Redistributions of files must retain the above copyright notice + * + * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests + * @package Cake.Test.Case.Log + * @since CakePHP(tm) v 2.4 + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +App::uses('LogEngineCollection', 'Log'); +App::uses('FileLog', 'Log/Engine'); + +/** + * LoggerEngineLog class + */ +class LoggerEngineLog extends FileLog { +} + +/** + * LogEngineCollectionTest class + * + * @package Cake.Test.Case.Log + */ +class LogEngineCollectionTest extends CakeTestCase { + + public $Collection; + +/** + * Start test callback + * + * @return void + */ + public function setUp() { + parent::setUp(); + + $this->Collection = new LogEngineCollection(); + } + +/** + * test load + * + * @return void + */ + public function testLoad() { + $result = $this->Collection->load('key', array('engine' => 'File')); + $this->assertInstanceOf('CakeLogInterface', $result); + } + +/** + * test load with deprecated Log suffix + * + * @return void + */ + public function testLoadWithSuffix() { + $result = $this->Collection->load('key', array('engine' => 'FileLog')); + $this->assertInstanceOf('CakeLogInterface', $result); + } + +/** + * test that engines starting with Log also work properly + * + * @return void + */ + public function testLoadWithSuffixAtBeginning() { + $result = $this->Collection->load('key', array('engine' => 'LoggerEngine')); + $this->assertInstanceOf('CakeLogInterface', $result); + } + +/** + * test load with invalid Log + * + * @return void + * @expectedException CakeLogException + */ + public function testLoadInvalid() { + $result = $this->Collection->load('key', array('engine' => 'ImaginaryFile')); + $this->assertInstanceOf('CakeLogInterface', $result); + } + +} diff --git a/lib/Cake/Test/Case/Model/CakeSchemaTest.php b/lib/Cake/Test/Case/Model/CakeSchemaTest.php index c1ec94fed..07afe7fee 100644 --- a/lib/Cake/Test/Case/Model/CakeSchemaTest.php +++ b/lib/Cake/Test/Case/Model/CakeSchemaTest.php @@ -790,13 +790,13 @@ class CakeSchemaTest extends CakeTestCase { $compare = $New->compare($this->Schema, $tables); $expected = array( 'ratings' => array( - 'add' => array( + 'create' => array( 'id' => array('type' => 'integer', 'null' => false, 'default' => null, 'key' => 'primary'), - 'foreign_key' => array('type' => 'integer', 'null' => false, 'default' => null, 'after' => 'id'), - 'model' => array('type' => 'varchar', 'null' => false, 'default' => null, 'after' => 'foreign_key'), - 'value' => array('type' => 'float', 'null' => false, 'length' => '5,2', 'default' => null, 'after' => 'model'), - 'created' => array('type' => 'datetime', 'null' => false, 'default' => null, 'after' => 'value'), - 'modified' => array('type' => 'datetime', 'null' => false, 'default' => null, 'after' => 'created'), + 'foreign_key' => array('type' => 'integer', 'null' => false, 'default' => null), + 'model' => array('type' => 'varchar', 'null' => false, 'default' => null), + 'value' => array('type' => 'float', 'null' => false, 'length' => '5,2', 'default' => null), + 'created' => array('type' => 'datetime', 'null' => false, 'default' => null), + 'modified' => array('type' => 'datetime', 'null' => false, 'default' => null), 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1)), 'tableParameters' => array('charset' => 'latin1', 'collate' => 'latin1_swedish_ci', 'engine' => 'MyISAM') ) diff --git a/lib/Cake/Test/Case/Model/Datasource/Database/PostgresTest.php b/lib/Cake/Test/Case/Model/Datasource/Database/PostgresTest.php index 70336ba0a..48cc3bc37 100644 --- a/lib/Cake/Test/Case/Model/Datasource/Database/PostgresTest.php +++ b/lib/Cake/Test/Case/Model/Datasource/Database/PostgresTest.php @@ -992,6 +992,22 @@ class PostgresTest extends CakeTestCase { $this->assertTrue($new['currval'] > $original['nextval'], 'Sequence did not update'); } + public function testSettings() { + Configure::write('Cache.disable', true); + $this->Dbo = ConnectionManager::getDataSource('test'); + $this->skipIf(!($this->Dbo instanceof Postgres)); + + $config2 = $this->Dbo->config; + $config2['settings']['datestyle'] = 'sql, dmy'; + ConnectionManager::create('test2', $config2); + $dbo2 = new Postgres($config2, true); + $expected = array(array('r' => date('d/m/Y'))); + $r = $dbo2->fetchRow('SELECT now()::date AS "r"'); + $this->assertEquals($expected, $r); + $dbo2->execute('SET DATESTYLE TO ISO'); + $dbo2->disconnect(); + } + /** * Test the limit function. * diff --git a/lib/Cake/Test/Case/Model/ModelReadTest.php b/lib/Cake/Test/Case/Model/ModelReadTest.php index 807fde84a..365da93ac 100644 --- a/lib/Cake/Test/Case/Model/ModelReadTest.php +++ b/lib/Cake/Test/Case/Model/ModelReadTest.php @@ -7953,6 +7953,27 @@ class ModelReadTest extends BaseModelTest { $this->assertTrue(is_array($result) && !empty($result)); } +/** + * test to assert that != in key together with a single element array will work + * + * @return void + */ + public function testNotEqualsInArrayWithOneValue() { + $this->loadFixtures('Article'); + $Article = new Article(); + $Article->recursive = -1; + + $result = $Article->find( + 'all', + array( + 'conditions' => array( + 'Article.id !=' => array(1) + ) + ) + ); + $this->assertTrue(is_array($result) && !empty($result)); + } + /** * test custom find method * diff --git a/lib/Cake/Test/Case/Model/ModelWriteTest.php b/lib/Cake/Test/Case/Model/ModelWriteTest.php index 505900e7f..6ec1de8ac 100644 --- a/lib/Cake/Test/Case/Model/ModelWriteTest.php +++ b/lib/Cake/Test/Case/Model/ModelWriteTest.php @@ -566,6 +566,28 @@ class ModelWriteTest extends BaseModelTest { $this->assertEquals(1, $result[1]['User']['posts_published']); } +/** + * Tests that counter caches are unchanged when using 'counterCache' => false + * + * @return void + */ + public function testCounterCacheSkip() { + $this->loadFixtures('CounterCacheUser', 'CounterCachePost'); + $User = new CounterCacheUser(); + $Post = new CounterCachePost(); + + $data = $Post->find('first', array( + 'conditions' => array('id' => 1), + 'recursive' => -1 + )); + $data[$Post->alias]['user_id'] = 301; + $Post->save($data, array('counterCache' => false)); + + $users = $User->find('all', array('order' => 'User.id')); + $this->assertEquals(2, $users[0]['User']['post_count']); + $this->assertEquals(1, $users[1]['User']['post_count']); + } + /** * test that beforeValidate returning false can abort saves. * @@ -7128,4 +7150,19 @@ class ModelWriteTest extends BaseModelTest { 'conditions' => array('Item.id' => 1))); $this->assertEquals(true, $result['Item']['published']); } + +/** + * Test the clear() method. + * + * @return void + */ + public function testClear() { + $this->loadFixtures('Bid'); + $model = ClassRegistry::init('Bid'); + $model->set(array('name' => 'Testing', 'message_id' => 3)); + $this->assertTrue(isset($model->data['Bid']['name'])); + $this->assertTrue($model->clear()); + $this->assertFalse(isset($model->data['Bid']['name'])); + $this->assertFalse(isset($model->data['Bid']['message_id'])); + } } diff --git a/lib/Cake/Test/Case/Network/CakeRequestTest.php b/lib/Cake/Test/Case/Network/CakeRequestTest.php index b9ff23bee..06e4f8151 100644 --- a/lib/Cake/Test/Case/Network/CakeRequestTest.php +++ b/lib/Cake/Test/Case/Network/CakeRequestTest.php @@ -157,7 +157,7 @@ class CakeRequestTest extends CakeTestCase { $request = new CakeRequest(); $this->assertEquals('some/path', $request->url); - $_SERVER['REQUEST_URI'] = FULL_BASE_URL . '/other/path?url=http://cakephp.org'; + $_SERVER['REQUEST_URI'] = Configure::read('App.fullBaseUrl') . '/other/path?url=http://cakephp.org'; $request = new CakeRequest(); $this->assertEquals('other/path', $request->url); } @@ -689,19 +689,19 @@ class CakeRequestTest extends CakeTestCase { $result = $request->referer(); $this->assertSame($result, '/'); - $_SERVER['HTTP_REFERER'] = FULL_BASE_URL . '/some/path'; + $_SERVER['HTTP_REFERER'] = Configure::read('App.fullBaseUrl') . '/some/path'; $result = $request->referer(true); $this->assertSame($result, '/some/path'); - $_SERVER['HTTP_REFERER'] = FULL_BASE_URL . '/some/path'; + $_SERVER['HTTP_REFERER'] = Configure::read('App.fullBaseUrl') . '/some/path'; $result = $request->referer(false); - $this->assertSame($result, FULL_BASE_URL . '/some/path'); + $this->assertSame($result, Configure::read('App.fullBaseUrl') . '/some/path'); - $_SERVER['HTTP_REFERER'] = FULL_BASE_URL . '/some/path'; + $_SERVER['HTTP_REFERER'] = Configure::read('App.fullBaseUrl') . '/some/path'; $result = $request->referer(true); $this->assertSame($result, '/some/path'); - $_SERVER['HTTP_REFERER'] = FULL_BASE_URL . '/recipes/add'; + $_SERVER['HTTP_REFERER'] = Configure::read('App.fullBaseUrl') . '/recipes/add'; $result = $request->referer(true); $this->assertSame($result, '/recipes/add'); @@ -757,6 +757,40 @@ class CakeRequestTest extends CakeTestCase { $this->assertFalse($request->is('delete')); } +/** + * Test is() with multiple types. + * + * @return void + */ + public function testIsMultiple() { + $request = new CakeRequest('some/path'); + + $_SERVER['REQUEST_METHOD'] = 'GET'; + $this->assertTrue($request->is(array('get', 'post'))); + + $_SERVER['REQUEST_METHOD'] = 'POST'; + $this->assertTrue($request->is(array('get', 'post'))); + + $_SERVER['REQUEST_METHOD'] = 'PUT'; + $this->assertFalse($request->is(array('get', 'post'))); + } + +/** + * Test isAll() + * + * @return void + */ + public function testIsAll() { + $request = new CakeRequest('some/path'); + + $_SERVER['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'; + $_SERVER['REQUEST_METHOD'] = 'GET'; + + $this->assertTrue($request->isAll(array('ajax', 'get'))); + $this->assertFalse($request->isAll(array('post', 'get'))); + $this->assertFalse($request->isAll(array('ajax', 'post'))); + } + /** * Test the method() method. * @@ -1777,7 +1811,7 @@ class CakeRequestTest extends CakeTestCase { 'SERVER_NAME' => 'localhost', 'DOCUMENT_ROOT' => '/Library/WebServer/Documents', 'SCRIPT_FILENAME' => '/Library/WebServer/Documents/site/index.php', - 'REQUEST_URI' => FULL_BASE_URL . '/site/posts/index', + 'REQUEST_URI' => '/site/posts/index', 'SCRIPT_NAME' => '/site/app/webroot/index.php', 'PHP_SELF' => '/site/app/webroot/index.php', ), @@ -1880,6 +1914,26 @@ class CakeRequestTest extends CakeTestCase { $this->assertNull($result); } +/** + * Test using param() + * + * @return void + */ + public function testReadingParams() { + $request = new CakeRequest(); + $request->addParams(array( + 'controller' => 'posts', + 'admin' => true, + 'truthy' => 1, + 'zero' => '0', + )); + $this->assertFalse($request->param('not_set')); + $this->assertTrue($request->param('admin')); + $this->assertEquals(1, $request->param('truthy')); + $this->assertEquals('posts', $request->param('controller')); + $this->assertEquals('0', $request->param('zero')); + } + /** * Test the data() method reading * diff --git a/lib/Cake/Test/Case/Network/CakeResponseTest.php b/lib/Cake/Test/Case/Network/CakeResponseTest.php index f92750390..94d240047 100644 --- a/lib/Cake/Test/Case/Network/CakeResponseTest.php +++ b/lib/Cake/Test/Case/Network/CakeResponseTest.php @@ -137,41 +137,45 @@ class CakeResponseTest extends CakeTestCase { public function testHeader() { $response = new CakeResponse(); $headers = array(); - $this->assertEquals($response->header(), $headers); + $this->assertEquals($headers, $response->header()); $response->header('Location', 'http://example.com'); $headers += array('Location' => 'http://example.com'); - $this->assertEquals($response->header(), $headers); + $this->assertEquals($headers, $response->header()); //Headers with the same name are overwritten $response->header('Location', 'http://example2.com'); $headers = array('Location' => 'http://example2.com'); - $this->assertEquals($response->header(), $headers); + $this->assertEquals($headers, $response->header()); $response->header(array('WWW-Authenticate' => 'Negotiate')); $headers += array('WWW-Authenticate' => 'Negotiate'); - $this->assertEquals($response->header(), $headers); + $this->assertEquals($headers, $response->header()); $response->header(array('WWW-Authenticate' => 'Not-Negotiate')); $headers['WWW-Authenticate'] = 'Not-Negotiate'; - $this->assertEquals($response->header(), $headers); + $this->assertEquals($headers, $response->header()); $response->header(array('Age' => 12, 'Allow' => 'GET, HEAD')); $headers += array('Age' => 12, 'Allow' => 'GET, HEAD'); - $this->assertEquals($response->header(), $headers); + $this->assertEquals($headers, $response->header()); // String headers are allowed $response->header('Content-Language: da'); $headers += array('Content-Language' => 'da'); - $this->assertEquals($response->header(), $headers); + $this->assertEquals($headers, $response->header()); $response->header('Content-Language: da'); $headers += array('Content-Language' => 'da'); - $this->assertEquals($response->header(), $headers); + $this->assertEquals($headers, $response->header()); $response->header(array('Content-Encoding: gzip', 'Vary: *', 'Pragma' => 'no-cache')); $headers += array('Content-Encoding' => 'gzip', 'Vary' => '*', 'Pragma' => 'no-cache'); - $this->assertEquals($response->header(), $headers); + $this->assertEquals($headers, $response->header()); + + $response->header('Access-Control-Allow-Origin', array('domain1', 'domain2')); + $headers += array('Access-Control-Allow-Origin' => array('domain1', 'domain2')); + $this->assertEquals($headers, $response->header()); } /** @@ -182,7 +186,8 @@ class CakeResponseTest extends CakeTestCase { $response = $this->getMock('CakeResponse', array('_sendHeader', '_sendContent', '_setCookies')); $response->header(array( 'Content-Language' => 'es', - 'WWW-Authenticate' => 'Negotiate' + 'WWW-Authenticate' => 'Negotiate', + 'Access-Control-Allow-Origin' => array('domain1', 'domain2'), )); $response->body('the response body'); $response->expects($this->once())->method('_sendContent')->with('the response body'); @@ -194,8 +199,12 @@ class CakeResponseTest extends CakeTestCase { $response->expects($this->at(3)) ->method('_sendHeader')->with('WWW-Authenticate', 'Negotiate'); $response->expects($this->at(4)) - ->method('_sendHeader')->with('Content-Length', 17); + ->method('_sendHeader')->with('Access-Control-Allow-Origin', 'domain1'); $response->expects($this->at(5)) + ->method('_sendHeader')->with('Access-Control-Allow-Origin', 'domain2'); + $response->expects($this->at(6)) + ->method('_sendHeader')->with('Content-Length', 17); + $response->expects($this->at(7)) ->method('_sendHeader')->with('Content-Type', 'text/html; charset=UTF-8'); $response->send(); } @@ -361,6 +370,7 @@ class CakeResponseTest extends CakeTestCase { /** * Tests the httpCodes method * + * @expectedException CakeException */ public function testHttpCodes() { $response = new CakeResponse(); @@ -372,16 +382,16 @@ class CakeResponseTest extends CakeTestCase { $this->assertEquals($expected, $result); $codes = array( - 1337 => 'Undefined Unicorn', - 1729 => 'Hardy-Ramanujan Located' + 381 => 'Unicorn Moved', + 555 => 'Unexpected Minotaur' ); $result = $response->httpCodes($codes); $this->assertTrue($result); $this->assertEquals(42, count($response->httpCodes())); - $result = $response->httpCodes(1337); - $expected = array(1337 => 'Undefined Unicorn'); + $result = $response->httpCodes(381); + $expected = array(381 => 'Unicorn Moved'); $this->assertEquals($expected, $result); $codes = array(404 => 'Sorry Bro'); @@ -392,6 +402,14 @@ class CakeResponseTest extends CakeTestCase { $result = $response->httpCodes(404); $expected = array(404 => 'Sorry Bro'); $this->assertEquals($expected, $result); + + //Throws exception + $response->httpCodes(array( + 0 => 'Nothing Here', + -1 => 'Reverse Infinity', + 12345 => 'Universal Password', + 'Hello' => 'World' + )); } /** @@ -1553,4 +1571,16 @@ class CakeResponseTest extends CakeTestCase { $result = $response->send(); } +/** + * Test the location method. + * + * @return void + */ + public function testLocation() { + $response = new CakeResponse(); + $this->assertNull($response->location(), 'No header should be set.'); + $this->assertNull($response->location('http://example.org'), 'Setting a location should return null'); + $this->assertEquals('http://example.org', $response->location(), 'Reading a location should return the value.'); + } + } diff --git a/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php b/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php index 5bcec3813..a64821cac 100644 --- a/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php +++ b/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php @@ -286,6 +286,69 @@ class CakeEmailTest extends CakeTestCase { $this->CakeEmail->addTo($value); } +/** + * test emailPattern method + * + * @return void + */ + public function testEmailPattern() { + $regex = '/.+@.+\..+/i'; + $this->assertNull($this->CakeEmail->emailPattern()); + $this->assertSame($regex, $this->CakeEmail->emailPattern($regex)->emailPattern()); + } + +/** + * Tests that it is possible to set email regex configuration to a CakeEmail object + * + * @return void + */ + public function testConfigEmailPattern() { + $regex = '/.+@.+\..+/i'; + $email = new CakeEmail(array('emailPattern' => $regex)); + $this->assertSame($regex, $email->emailPattern()); + } + +/** + * Tests that it is possible set custom email validation + */ + public function testCustomEmailValidation() { + $regex = '/^[\.a-z0-9!#$%&\'*+\/=?^_`{|}~-]+@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]{2,6}$/i'; + + $this->CakeEmail->emailPattern($regex)->to('pass.@example.com'); + $this->assertSame(array( + 'pass.@example.com' => 'pass.@example.com', + ), $this->CakeEmail->to()); + + $this->CakeEmail->addTo('pass..old.docomo@example.com'); + $this->assertSame(array( + 'pass.@example.com' => 'pass.@example.com', + 'pass..old.docomo@example.com' => 'pass..old.docomo@example.com', + ), $this->CakeEmail->to()); + + $this->CakeEmail->reset(); + $emails = array( + 'pass.@example.com', + 'pass..old.docomo@example.com' + ); + $additionalEmails = array( + '.extend.@example.com', + '.docomo@example.com' + ); + $this->CakeEmail->emailPattern($regex)->to($emails); + $this->assertSame(array( + 'pass.@example.com' => 'pass.@example.com', + 'pass..old.docomo@example.com' => 'pass..old.docomo@example.com', + ), $this->CakeEmail->to()); + + $this->CakeEmail->addTo($additionalEmails); + $this->assertSame(array( + 'pass.@example.com' => 'pass.@example.com', + 'pass..old.docomo@example.com' => 'pass..old.docomo@example.com', + '.extend.@example.com' => '.extend.@example.com', + '.docomo@example.com' => '.docomo@example.com', + ), $this->CakeEmail->to()); + } + /** * testFormatAddress method * @@ -908,6 +971,43 @@ class CakeEmailTest extends CakeTestCase { $this->assertContains($expected, $result['message']); } +/** + * Test send() with no template and data string attachment + * + * @return void + */ + + public function testSendNoTemplateWithDataStringAttachment() { + $this->CakeEmail->transport('debug'); + $this->CakeEmail->from('cake@cakephp.org'); + $this->CakeEmail->to('cake@cakephp.org'); + $this->CakeEmail->subject('My title'); + $this->CakeEmail->emailFormat('text'); + $data = file_get_contents(CAKE . 'Console/Templates/skel/webroot/img/cake.icon.png'); + $this->CakeEmail->attachments(array('cake.icon.png' => array( + 'data' => $data, + 'mimetype' => 'image/png' + ))); + $result = $this->CakeEmail->send('Hello'); + + $boundary = $this->CakeEmail->getBoundary(); + $this->assertContains('Content-Type: multipart/mixed; boundary="' . $boundary . '"', $result['headers']); + $expected = "--$boundary\r\n" . + "Content-Type: text/plain; charset=UTF-8\r\n" . + "Content-Transfer-Encoding: 8bit\r\n" . + "\r\n" . + "Hello" . + "\r\n" . + "\r\n" . + "\r\n" . + "--$boundary\r\n" . + "Content-Type: image/png\r\n" . + "Content-Transfer-Encoding: base64\r\n" . + "Content-Disposition: attachment; filename=\"cake.icon.png\"\r\n\r\n"; + $expected .= chunk_split(base64_encode($data), 76, "\r\n"); + $this->assertContains($expected, $result['message']); + } + /** * Test send() with no template as both * @@ -1051,7 +1151,7 @@ class CakeEmailTest extends CakeTestCase { */ public function testSendWithLog() { CakeLog::config('email', array( - 'engine' => 'FileLog', + 'engine' => 'File', 'path' => TMP )); CakeLog::drop('default'); @@ -1071,6 +1171,35 @@ class CakeEmailTest extends CakeTestCase { CakeLog::drop('email'); } +/** + * testSendWithLogAndScope method + * + * @return void + */ + public function testSendWithLogAndScope() { + CakeLog::config('email', array( + 'engine' => 'File', + 'path' => TMP, + 'types' => array('cake_test_emails'), + 'scopes' => array('email') + )); + CakeLog::drop('default'); + $this->CakeEmail->transport('Debug'); + $this->CakeEmail->to('me@cakephp.org'); + $this->CakeEmail->from('cake@cakephp.org'); + $this->CakeEmail->subject('My title'); + $this->CakeEmail->config(array('log' => array('level' => 'cake_test_emails', 'scope' => 'email'))); + $result = $this->CakeEmail->send("Logging This"); + + App::uses('File', 'Utility'); + $File = new File(TMP . 'cake_test_emails.log'); + $log = $File->read(); + $this->assertTrue(strpos($log, $result['headers']) !== false); + $this->assertTrue(strpos($log, $result['message']) !== false); + $File->delete(); + CakeLog::drop('email'); + } + /** * testSendRender method * @@ -1470,11 +1599,13 @@ class CakeEmailTest extends CakeTestCase { public function testReset() { $this->CakeEmail->to('cake@cakephp.org'); $this->CakeEmail->theme('TestTheme'); + $this->CakeEmail->emailPattern('/.+@.+\..+/i'); $this->assertSame($this->CakeEmail->to(), array('cake@cakephp.org' => 'cake@cakephp.org')); $this->CakeEmail->reset(); $this->assertSame($this->CakeEmail->to(), array()); $this->assertSame(null, $this->CakeEmail->theme()); + $this->assertSame(null, $this->CakeEmail->emailPattern()); } /** diff --git a/lib/Cake/Test/Case/Network/Http/HttpSocketTest.php b/lib/Cake/Test/Case/Network/Http/HttpSocketTest.php index 09abf6f6f..a910bcd14 100644 --- a/lib/Cake/Test/Case/Network/Http/HttpSocketTest.php +++ b/lib/Cake/Test/Case/Network/Http/HttpSocketTest.php @@ -256,6 +256,7 @@ class HttpSocketTest extends CakeTestCase { 'port' => 23, 'timeout' => 30, 'ssl_verify_peer' => true, + 'ssl_allow_self_signed' => false, 'ssl_verify_depth' => 5, 'ssl_verify_host' => true, 'request' => array( @@ -284,6 +285,7 @@ class HttpSocketTest extends CakeTestCase { 'port' => 80, 'timeout' => 30, 'ssl_verify_peer' => true, + 'ssl_allow_self_signed' => false, 'ssl_verify_depth' => 5, 'ssl_verify_host' => true, 'request' => array( @@ -322,6 +324,7 @@ class HttpSocketTest extends CakeTestCase { $context = array( 'ssl' => array( 'verify_peer' => true, + 'allow_self_signed' => false, 'verify_depth' => 5, 'CN_match' => 'www.cakephp.org', 'cafile' => CAKE . 'Config' . DS . 'cacert.pem' @@ -1148,6 +1151,30 @@ class HttpSocketTest extends CakeTestCase { $this->RequestSocket->put('http://www.google.com/', null, array('line' => 'Hey Server')); } +/** + * testPatch + * + * @return void + */ + public function testPatch() { + $this->RequestSocket->reset(); + $this->RequestSocket->expects($this->at(0)) + ->method('request') + ->with(array('method' => 'PATCH', 'uri' => 'http://www.google.com/', 'body' => array())); + + $this->RequestSocket->expects($this->at(1)) + ->method('request') + ->with(array('method' => 'PATCH', 'uri' => 'http://www.google.com/', 'body' => array('Foo' => 'bar'))); + + $this->RequestSocket->expects($this->at(2)) + ->method('request') + ->with(array('method' => 'PATCH', 'uri' => 'http://www.google.com/', 'body' => null, 'line' => 'Hey Server')); + + $this->RequestSocket->patch('http://www.google.com/'); + $this->RequestSocket->patch('http://www.google.com/', array('Foo' => 'bar')); + $this->RequestSocket->patch('http://www.google.com/', null, array('line' => 'Hey Server')); + } + /** * testDelete * @@ -1709,9 +1736,11 @@ class HttpSocketTest extends CakeTestCase { $this->Socket->reset(); $this->Socket->request('http://example.com'); $this->assertTrue($this->Socket->config['context']['ssl']['verify_peer']); + $this->assertFalse($this->Socket->config['context']['ssl']['allow_self_signed']); $this->assertEquals(5, $this->Socket->config['context']['ssl']['verify_depth']); $this->assertEquals('example.com', $this->Socket->config['context']['ssl']['CN_match']); $this->assertArrayNotHasKey('ssl_verify_peer', $this->Socket->config); + $this->assertArrayNotHasKey('ssl_allow_self_signed', $this->Socket->config); $this->assertArrayNotHasKey('ssl_verify_host', $this->Socket->config); $this->assertArrayNotHasKey('ssl_verify_depth', $this->Socket->config); } diff --git a/lib/Cake/Test/Case/Routing/RouterTest.php b/lib/Cake/Test/Case/Routing/RouterTest.php index 6b886e83b..57e480978 100644 --- a/lib/Cake/Test/Case/Routing/RouterTest.php +++ b/lib/Cake/Test/Case/Routing/RouterTest.php @@ -49,24 +49,37 @@ class RouterTest extends CakeTestCase { public function tearDown() { parent::tearDown(); CakePlugin::unload(); + Router::fullBaseUrl(''); + Configure::write('App.fullBaseUrl', 'http://localhost'); } /** - * testFullBaseURL method + * testFullBaseUrl method * * @return void */ - public function testFullBaseURL() { - $skip = PHP_SAPI === 'cli'; - if ($skip) { - $this->markTestSkipped('Cannot validate base URLs in CLI'); - } + public function testFullBaseUrl() { $this->assertRegExp('/^http(s)?:\/\//', Router::url('/', true)); $this->assertRegExp('/^http(s)?:\/\//', Router::url(null, true)); $this->assertRegExp('/^http(s)?:\/\//', Router::url(array('full_base' => true))); $this->assertSame(FULL_BASE_URL . '/', Router::url(array('full_base' => true))); } +/** + * Tests that the base URL can be changed at runtime. + * + * @return void + */ + public function testBaseUrl() { + $this->assertEquals(FULL_BASE_URL, Router::fullBaseUrl()); + Router::fullBaseUrl('http://example.com'); + $this->assertEquals('http://example.com/', Router::url('/', true)); + $this->assertEquals('http://example.com', Configure::read('App.fullBaseUrl')); + Router::fullBaseUrl('https://example.com'); + $this->assertEquals('https://example.com/', Router::url('/', true)); + $this->assertEquals('https://example.com', Configure::read('App.fullBaseUrl')); + } + /** * testRouteDefaultParams method * @@ -1053,6 +1066,12 @@ class RouterTest extends CakeTestCase { $result = Router::parse('/posts/view/foo:bar/routing:fun/answer:42'); $expected = array('pass' => array(), 'named' => array('foo' => 'bar', 'routing' => 'fun', 'answer' => '42'), 'plugin' => null, 'controller' => 'posts', 'action' => 'view'); $this->assertEquals($expected, $result); + + Router::reload(); + Router::connect('/posts/view/*', array('controller' => 'posts', 'action' => 'view'), array('named' => array('foo', 'answer'), 'greedyNamed' => true)); + $result = Router::parse('/posts/view/foo:bar/routing:fun/answer:42?id=123&tab=abc'); + $expected = array('pass' => array(), 'named' => array('foo' => 'bar', 'routing' => 'fun', 'answer' => '42'), 'plugin' => null, 'controller' => 'posts', 'action' => 'view', '?' => array('id' => '123', 'tab' => 'abc')); + $this->assertEquals($expected, $result); } /** @@ -1169,6 +1188,33 @@ class RouterTest extends CakeTestCase { $this->assertEquals($expected, $result); } +/** + * Test parse and reverse symmetry + * + * @return void + * @dataProvider parseReverseSymmetryData + */ + public function testParseReverseSymmetry($url) { + $this->assertSame($url, Router::reverse(Router::parse($url) + array('url' => array()))); + } + +/** + * Data for parse and reverse test + * + * @return array + */ + public function parseReverseSymmetryData() { + return array( + array('/'), + array('/controller/action'), + array('/controller/action/param'), + array('/controller/action?param1=value1¶m2=value2'), + array('/controller/action/param?param1=value1'), + array('/controller/action/named1:nv1'), + array('/controller/action/named1:nv1?param1=value1') + ); + } + /** * Test that Routing.prefixes are used when a Router instance is created * or reset @@ -1319,9 +1365,11 @@ class RouterTest extends CakeTestCase { $this->assertEquals($expected, $result); $result = Router::parse('/posts/view/1.rss?query=test'); + $expected['?'] = array('query' => 'test'); $this->assertEquals($expected, $result); $result = Router::parse('/posts/view/1.atom'); + unset($expected['?']); $expected['ext'] = 'atom'; $this->assertEquals($expected, $result); @@ -1335,7 +1383,7 @@ class RouterTest extends CakeTestCase { $this->assertEquals($expected, $result); $result = Router::parse('/posts.atom?hello=goodbye'); - $expected = array('plugin' => null, 'controller' => 'posts.atom', 'action' => 'index', 'pass' => array(), 'named' => array()); + $expected = array('plugin' => null, 'controller' => 'posts.atom', 'action' => 'index', 'pass' => array(), 'named' => array(), '?' => array('hello' => 'goodbye')); $this->assertEquals($expected, $result); Router::reload(); @@ -2542,6 +2590,9 @@ class RouterTest extends CakeTestCase { $url = '://example.com'; $this->assertEquals($url, Router::url($url)); + $url = '//example.com'; + $this->assertEquals($url, Router::url($url)); + $url = 'javascript:void(0)'; $this->assertEquals($url, Router::url($url)); @@ -2553,6 +2604,10 @@ class RouterTest extends CakeTestCase { $url = '#here'; $this->assertEquals($url, Router::url($url)); + + $url = '?param=0'; + $this->assertEquals($url, Router::url($url)); + $url = 'posts/index#here'; $expected = FULL_BASE_URL . '/posts/index#here'; $this->assertEquals($expected, Router::url($url, true)); diff --git a/lib/Cake/Test/Case/TestSuite/CakeTestCaseTest.php b/lib/Cake/Test/Case/TestSuite/CakeTestCaseTest.php index e5827089d..462ba78d3 100644 --- a/lib/Cake/Test/Case/TestSuite/CakeTestCaseTest.php +++ b/lib/Cake/Test/Case/TestSuite/CakeTestCaseTest.php @@ -353,6 +353,11 @@ class CakeTestCaseTest extends CakeTestCase { * @return void */ public function testGetMockForModel() { + App::build(array( + 'Model' => array( + CAKE . 'Test' . DS . 'test_app' . DS . 'Model' . DS + ) + ), App::RESET); $Post = $this->getMockForModel('Post'); $this->assertInstanceOf('Post', $Post); @@ -372,6 +377,13 @@ class CakeTestCaseTest extends CakeTestCase { * @return void */ public function testGetMockForModelWithPlugin() { + App::build(array( + 'Plugin' => array( + CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS + ) + ), App::RESET); + CakePlugin::load('TestPlugin'); + $this->getMockForModel('TestPlugin.TestPluginAppModel'); $TestPluginComment = $this->getMockForModel('TestPlugin.TestPluginComment'); $result = ClassRegistry::init('TestPlugin.TestPluginComment'); @@ -389,4 +401,37 @@ class CakeTestCaseTest extends CakeTestCase { $this->assertTrue($TestPluginComment->save(array())); $this->assertFalse($TestPluginComment->save(array())); } + +/** + * testGetMockForModelModel + * + * @return void + */ + public function testGetMockForModelModel() { + $Mock = $this->getMockForModel('Model', array('save'), array('name' => 'Comment')); + + $result = ClassRegistry::init('Comment'); + $this->assertInstanceOf('Model', $result); + + $Mock->expects($this->at(0)) + ->method('save') + ->will($this->returnValue(true)); + $Mock->expects($this->at(1)) + ->method('save') + ->will($this->returnValue(false)); + + $this->assertTrue($Mock->save(array())); + $this->assertFalse($Mock->save(array())); + } + +/** + * testGetMockForModelDoesNotExist + * + * @expectedException MissingModelException + * @expectedExceptionMessage Model IDoNotExist could not be found + * @return void + */ + public function testGetMockForModelDoesNotExist() { + $this->getMockForModel('IDoNotExist'); + } } diff --git a/lib/Cake/Test/Case/TestSuite/ControllerTestCaseTest.php b/lib/Cake/Test/Case/TestSuite/ControllerTestCaseTest.php index 301949a73..d139e7f18 100644 --- a/lib/Cake/Test/Case/TestSuite/ControllerTestCaseTest.php +++ b/lib/Cake/Test/Case/TestSuite/ControllerTestCaseTest.php @@ -210,6 +210,28 @@ class ControllerTestCaseTest extends CakeTestCase { $this->assertEquals('written!', $Posts->Auth->Session->write('something')); } +/** + * testGenerateWithComponentConfig + */ + public function testGenerateWithComponentConfig() { + $Tests = $this->Case->generate('TestConfigs', array( + )); + + $expected = array('some' => 'config'); + $settings = array_intersect_key($Tests->RequestHandler->settings, array('some' => 'foo')); + $this->assertSame($expected, $settings, 'A mocked component should have the same config as an unmocked component'); + + $Tests = $this->Case->generate('TestConfigs', array( + 'components' => array( + 'RequestHandler' => array('isPut') + ) + )); + + $expected = array('some' => 'config'); + $settings = array_intersect_key($Tests->RequestHandler->settings, array('some' => 'foo')); + $this->assertSame($expected, $settings, 'A mocked component should have the same config as an unmocked component'); + } + /** * Tests ControllerTestCase::generate() using classes from plugins */ diff --git a/lib/Cake/Test/Case/Utility/CakeNumberTest.php b/lib/Cake/Test/Case/Utility/CakeNumberTest.php index e782d9e2a..bb3fe92ee 100644 --- a/lib/Cake/Test/Case/Utility/CakeNumberTest.php +++ b/lib/Cake/Test/Case/Utility/CakeNumberTest.php @@ -81,6 +81,12 @@ class CakeNumberTest extends CakeTestCase { $result = $this->Number->format($value, array('places' => 1)); $expected = '$0.0'; $this->assertEquals($expected, $result); + + $value = 1.23; + $options = array('decimals' => ',', 'thousands' => '.', 'before' => '', 'after' => ' €'); + $result = $this->Number->format($value, $options); + $expected = '1,23 €'; + $this->assertEquals($expected, $result); } /** @@ -107,6 +113,10 @@ class CakeNumberTest extends CakeTestCase { $expected = '[-100,100,100.00]'; $this->assertEquals($expected, $result); + $result = $this->Number->formatDelta(-$value, array('before' => '[ ', 'after' => ' ]')); + $expected = '[ -100,100,100.00 ]'; + $this->assertEquals($expected, $result); + $value = 0; $result = $this->Number->formatDelta($value, array('places' => 1, 'before' => '[', 'after' => ']')); $expected = '[0.0]'; @@ -196,19 +206,22 @@ class CakeNumberTest extends CakeTestCase { $this->assertEquals($expected, $result); $result = $this->Number->currency($value, 'EUR'); - $expected = '€100.100.100,00'; + $expected = '€100.100.100,00'; $this->assertEquals($expected, $result); $result = $this->Number->currency($value, 'GBP'); - $expected = '£100,100,100.00'; + $expected = '£100,100,100.00'; $this->assertEquals($expected, $result); - $result = $this->Number->currency($value, '', array('thousands' => ' ', 'wholeSymbol' => '€', 'wholePosition' => 'after', 'decimals' => ',', 'zero' => 'Gratuit')); - $expected = '100 100 100,00€'; + $options = array('thousands' => ' ', 'wholeSymbol' => 'EUR ', 'wholePosition' => 'before', + 'decimals' => ',', 'zero' => 'Gratuit'); + $result = $this->Number->currency($value, '', $options); + $expected = 'EUR 100 100 100,00'; $this->assertEquals($expected, $result); - $result = $this->Number->currency(1000.45, null, array('after' => 'øre', 'before' => 'Kr. ', 'decimals' => ',', 'thousands' => '.')); - $expected = 'Kr. 1.000,45'; + $options = array('after' => 'øre', 'before' => 'Kr.', 'decimals' => ',', 'thousands' => '.'); + $result = $this->Number->currency(1000.45, null, $options); + $expected = 'Kr.1.000,45'; $this->assertEquals($expected, $result); $result = $this->Number->currency(0.5, 'USD'); @@ -227,12 +240,15 @@ class CakeNumberTest extends CakeTestCase { $expected = '1.00 $'; $this->assertEquals($expected, $result); - $result = $this->Number->currency(0.2, null, array('wholeSymbol' => ' $', 'wholePosition' => 'after', 'fractionSymbol' => 'cents')); - $expected = '20cents'; + $options = array('wholeSymbol' => '$', 'wholePosition' => 'after', 'fractionSymbol' => ' cents'); + $result = $this->Number->currency(0.2, null, $options); + $expected = '20 cents'; $this->assertEquals($expected, $result); - $result = $this->Number->currency(0.2, null, array('wholeSymbol' => ' $', 'wholePosition' => 'after', 'fractionSymbol' => 'cents', 'fractionPosition' => 'before')); - $expected = 'cents20'; + $options = array('wholeSymbol' => '$', 'wholePosition' => 'after', 'fractionSymbol' => 'cents ', + 'fractionPosition' => 'before'); + $result = $this->Number->currency(0.2, null, $options); + $expected = 'cents 20'; $this->assertEquals($expected, $result); $result = $this->Number->currency(311, 'USD', array('wholePosition' => 'after')); @@ -240,31 +256,82 @@ class CakeNumberTest extends CakeTestCase { $this->assertEquals($expected, $result); $result = $this->Number->currency(0.2, 'EUR'); - $expected = '€0,20'; + $expected = '€0,20'; $this->assertEquals($expected, $result); - $result = $this->Number->currency(12, null, array('wholeSymbol' => ' dollars', 'wholePosition' => 'after', 'fractionSymbol' => ' cents', 'fractionPosition' => 'after')); + $options = array('wholeSymbol' => ' dollars', 'wholePosition' => 'after', 'fractionSymbol' => ' cents', + 'fractionPosition' => 'after'); + $result = $this->Number->currency(12, null, $options); $expected = '12.00 dollars'; $this->assertEquals($expected, $result); - $result = $this->Number->currency(0.12, null, array('wholeSymbol' => ' dollars', 'wholePosition' => 'after', 'fractionSymbol' => ' cents', 'fractionPosition' => 'after')); + $options = array('wholeSymbol' => ' dollars', 'wholePosition' => 'after', 'fractionSymbol' => ' cents', + 'fractionPosition' => 'after'); + $result = $this->Number->currency(0.12, null, $options); $expected = '12 cents'; $this->assertEquals($expected, $result); - $result = $this->Number->currency(0.5, null, array('fractionSymbol' => false, 'fractionPosition' => 'before', 'wholeSymbol' => '$')); + $options = array('fractionSymbol' => false, 'fractionPosition' => 'before', 'wholeSymbol' => '$'); + $result = $this->Number->currency(0.5, null, $options); $expected = '$0.50'; $this->assertEquals($expected, $result); $result = $this->Number->currency(0, 'GBP'); - $expected = '£0.00'; + $expected = '£0.00'; $this->assertEquals($expected, $result); $result = $this->Number->currency(0.00000, 'GBP'); - $expected = '£0.00'; + $expected = '£0.00'; $this->assertEquals($expected, $result); $result = $this->Number->currency('0.00000', 'GBP'); - $expected = '£0.00'; + $expected = '£0.00'; + $this->assertEquals($expected, $result); + + $result = $this->Number->currency('-2.23300', 'JPY'); + $expected = '(¥2.23)'; + $this->assertEquals($expected, $result); + + $result = $this->Number->currency('22.389', 'CAD'); + $expected = '$22.39'; + $this->assertEquals($expected, $result); + + $result = $this->Number->currency('4.111', 'AUD'); + $expected = '$4.11'; + $this->assertEquals($expected, $result); + } + +/** + * Test currency format with places and fraction exponents. + * Places should only matter for non fraction values and vice versa. + * + * @return void + */ + public function testCurrencyWithFractionAndPlaces() { + $result = $this->Number->currency('1.23', 'GBP', array('places' => 3)); + $expected = '£1.230'; + $this->assertEquals($expected, $result); + + $result = $this->Number->currency('0.23', 'GBP', array('places' => 3)); + $expected = '23p'; + $this->assertEquals($expected, $result); + + $result = $this->Number->currency('0.001', 'GBP', array('places' => 3)); + $expected = '0p'; + $this->assertEquals($expected, $result); + + $this->Number->addFormat('BHD', array('before' => 'BD ', 'fractionSymbol' => ' fils', + 'fractionExponent' => 3)); + $result = $this->Number->currency('1.234', 'BHD', array('places' => 2)); + $expected = 'BD 1.23'; + $this->assertEquals($expected, $result); + + $result = $this->Number->currency('0.234', 'BHD', array('places' => 2)); + $expected = '234 fils'; + $this->assertEquals($expected, $result); + + $result = $this->Number->currency('0.001', 'BHD', array('places' => 2)); + $expected = '1 fils'; $this->assertEquals($expected, $result); } @@ -317,11 +384,11 @@ class CakeNumberTest extends CakeTestCase { $this->assertEquals($expected, $result); $this->Number->defaultCurrency('EUR'); $result = $this->Number->currency(1000); - $expected = '€1.000,00'; + $expected = '€1.000,00'; $this->assertEquals($expected, $result); $result = $this->Number->currency(2000); - $expected = '€2.000,00'; + $expected = '€2.000,00'; $this->assertEquals($expected, $result); $this->Number->defaultCurrency('USD'); @@ -352,11 +419,11 @@ class CakeNumberTest extends CakeTestCase { $this->assertEquals($expected, $result); $result = $this->Number->currency($value, 'EUR'); - $expected = '€100.100.100,00'; + $expected = '€100.100.100,00'; $this->assertEquals($expected, $result); $result = $this->Number->currency($value, 'GBP'); - $expected = '£100,100,100.00'; + $expected = '£100,100,100.00'; $this->assertEquals($expected, $result); } @@ -373,11 +440,11 @@ class CakeNumberTest extends CakeTestCase { $this->assertEquals($expected, $result); $result = $this->Number->currency($value, 'EUR'); - $expected = '(€100.100.100,00)'; + $expected = '(€100.100.100,00)'; $this->assertEquals($expected, $result); $result = $this->Number->currency($value, 'GBP'); - $expected = '(£100,100,100.00)'; + $expected = '(£100,100,100.00)'; $this->assertEquals($expected, $result); $result = $this->Number->currency($value, 'USD', array('negative' => '-')); @@ -385,11 +452,11 @@ class CakeNumberTest extends CakeTestCase { $this->assertEquals($expected, $result); $result = $this->Number->currency($value, 'EUR', array('negative' => '-')); - $expected = '-€100.100.100,00'; + $expected = '-€100.100.100,00'; $this->assertEquals($expected, $result); $result = $this->Number->currency($value, 'GBP', array('negative' => '-')); - $expected = '-£100,100,100.00'; + $expected = '-£100,100,100.00'; $this->assertEquals($expected, $result); } @@ -406,7 +473,7 @@ class CakeNumberTest extends CakeTestCase { $this->assertEquals($expected, $result); $result = $this->Number->currency($value, 'EUR'); - $expected = '€0,99'; + $expected = '€0,99'; $this->assertEquals($expected, $result); $result = $this->Number->currency($value, 'GBP'); @@ -427,7 +494,7 @@ class CakeNumberTest extends CakeTestCase { $this->assertEquals($expected, $result); $result = $this->Number->currency($value, 'EUR'); - $expected = '(€0,99)'; + $expected = '(€0,99)'; $this->assertEquals($expected, $result); $result = $this->Number->currency($value, 'GBP'); @@ -439,7 +506,7 @@ class CakeNumberTest extends CakeTestCase { $this->assertEquals($expected, $result); $result = $this->Number->currency($value, 'EUR', array('negative' => '-')); - $expected = '-€0,99'; + $expected = '-€0,99'; $this->assertEquals($expected, $result); $result = $this->Number->currency($value, 'GBP', array('negative' => '-')); @@ -460,11 +527,11 @@ class CakeNumberTest extends CakeTestCase { $this->assertEquals($expected, $result); $result = $this->Number->currency($value, 'EUR'); - $expected = '€0,00'; + $expected = '€0,00'; $this->assertEquals($expected, $result); $result = $this->Number->currency($value, 'GBP'); - $expected = '£0.00'; + $expected = '£0.00'; $this->assertEquals($expected, $result); $result = $this->Number->currency($value, 'GBP', array('zero' => 'FREE!')); @@ -485,7 +552,7 @@ class CakeNumberTest extends CakeTestCase { $this->assertEquals($expected, $result); $result = $this->Number->currency($value, 'GBP', array('places' => 0)); - $expected = '£1,234,568'; + $expected = '£1,234,568'; $this->assertEquals($expected, $result); $result = $this->Number->currency('1234567.8912345', null, array('before' => 'GBP', 'places' => 3)); @@ -496,15 +563,15 @@ class CakeNumberTest extends CakeTestCase { $expected = 'GBP650.1200'; $this->assertEquals($expected, $result); - $result = $this->Number->currency($value, 'GBP', array('escape' => true)); - $expected = '&#163;1,234,567.89'; + $result = $this->Number->currency($value, 'GBP', array('before' => '£ ', 'escape' => true)); + $expected = '&#163; 1,234,567.89'; $this->assertEquals($expected, $result); $result = $this->Number->currency('0.35', 'USD', array('after' => false)); $expected = '$0.35'; $this->assertEquals($expected, $result); - $result = $this->Number->currency('0.35', 'GBP', array('after' => false)); + $result = $this->Number->currency('0.35', 'GBP', array('before' => '£', 'after' => false, 'escape' => false)); $expected = '£0.35'; $this->assertEquals($expected, $result); @@ -513,7 +580,7 @@ class CakeNumberTest extends CakeTestCase { $this->assertEquals($expected, $result); $result = $this->Number->currency('0.35', 'EUR'); - $expected = '€0,35'; + $expected = '€0,35'; $this->assertEquals($expected, $result); } @@ -634,6 +701,30 @@ class CakeNumberTest extends CakeTestCase { $result = $this->Number->toPercentage(0, 4); $expected = '0.0000%'; $this->assertEquals($expected, $result); + + $result = $this->Number->toPercentage(45, 0, array('multiply' => false)); + $expected = '45%'; + $this->assertEquals($expected, $result); + + $result = $this->Number->toPercentage(45, 2, array('multiply' => false)); + $expected = '45.00%'; + $this->assertEquals($expected, $result); + + $result = $this->Number->toPercentage(0, 0, array('multiply' => false)); + $expected = '0%'; + $this->assertEquals($expected, $result); + + $result = $this->Number->toPercentage(0, 4, array('multiply' => false)); + $expected = '0.0000%'; + $this->assertEquals($expected, $result); + + $result = $this->Number->toPercentage(0.456, 0, array('multiply' => true)); + $expected = '46%'; + $this->assertEquals($expected, $result); + + $result = $this->Number->toPercentage(0.456, 2, array('multiply' => true)); + $expected = '45.60%'; + $this->assertEquals($expected, $result); } /** diff --git a/lib/Cake/Test/Case/Utility/CakeTimeTest.php b/lib/Cake/Test/Case/Utility/CakeTimeTest.php index 95370c25b..983325719 100644 --- a/lib/Cake/Test/Case/Utility/CakeTimeTest.php +++ b/lib/Cake/Test/Case/Utility/CakeTimeTest.php @@ -187,6 +187,27 @@ class CakeTimeTest extends CakeTestCase { $this->assertEquals($expected, $result); } +/** + * test the custom string options for timeAgoInWords + * + * @return void + */ + public function testTimeAgoInWordsCustomStrings() { + $result = $this->Time->timeAgoInWords( + strtotime('-8 years -4 months -2 weeks -3 days'), + array('relativeString' => 'at least %s ago', 'accuracy' => array('year' => 'year'), 'end' => '+10 years') + ); + $expected = 'at least 8 years ago'; + $this->assertEquals($expected, $result); + + $result = $this->Time->timeAgoInWords( + strtotime('+4 months +2 weeks +3 days'), + array('absoluteString' => 'exactly on %s', 'accuracy' => array('year' => 'year'), 'end' => '+2 months') + ); + $expected = 'exactly on ' . date('j/n/y', strtotime('+4 months +2 weeks +3 days')); + $this->assertEquals($expected, $result); + } + /** * Test the accuracy option for timeAgoInWords() * @@ -630,6 +651,40 @@ class CakeTimeTest extends CakeTestCase { $this->assertFalse($result); } +/** + * testIsFuture method + * + * @return void + */ + public function testIsFuture() { + $this->assertTrue($this->Time->isFuture('+1 month')); + $this->assertTrue($this->Time->isFuture('+1 days')); + $this->assertTrue($this->Time->isFuture('+1 minute')); + $this->assertTrue($this->Time->isFuture('+1 second')); + + $this->assertFalse($this->Time->isFuture('-1 second')); + $this->assertFalse($this->Time->isFuture('-1 day')); + $this->assertFalse($this->Time->isFuture('-1 week')); + $this->assertFalse($this->Time->isFuture('-1 month')); + } + +/** + * testIsPast method + * + * @return void + */ + public function testIsPast() { + $this->assertFalse($this->Time->isPast('+1 month')); + $this->assertFalse($this->Time->isPast('+1 days')); + $this->assertFalse($this->Time->isPast('+1 minute')); + $this->assertFalse($this->Time->isPast('+1 second')); + + $this->assertTrue($this->Time->isPast('-1 second')); + $this->assertTrue($this->Time->isPast('-1 day')); + $this->assertTrue($this->Time->isPast('-1 week')); + $this->assertTrue($this->Time->isPast('-1 month')); + } + /** * testIsThisWeek method * diff --git a/lib/Cake/Test/Case/Utility/DebuggerTest.php b/lib/Cake/Test/Case/Utility/DebuggerTest.php index dfb823e8d..845cb30b5 100644 --- a/lib/Cake/Test/Case/Utility/DebuggerTest.php +++ b/lib/Cake/Test/Case/Utility/DebuggerTest.php @@ -334,6 +334,8 @@ object(View) { response => object(CakeResponse) {} elementCache => 'default' elementCacheSettings => array() + Html => object(HtmlHelper) {} + Form => object(FormHelper) {} int => (int) 2 float => (float) 1.333 @@ -358,7 +360,6 @@ TEXT; ) [protected] _scripts => array() [protected] _paths => array() - [protected] _helpersLoaded => false [protected] _parents => array() [protected] _current => null [protected] _currentType => '' @@ -488,8 +489,11 @@ TEXT; ob_start(); Debugger::dump($var); $result = ob_get_clean(); + + $open = php_sapi_name() == 'cli' ? "\n" : '
';
+		$close = php_sapi_name() == 'cli' ? "\n" : '
'; $expected = <<array( +{$open}array( 'People' => array( (int) 0 => array( 'name' => 'joeseph', @@ -502,7 +506,7 @@ TEXT; 'hair' => 'black' ) ) -) +){$close} TEXT; $this->assertTextEquals($expected, $result); } diff --git a/lib/Cake/Test/Case/Utility/InflectorTest.php b/lib/Cake/Test/Case/Utility/InflectorTest.php index dc923d63f..337830deb 100644 --- a/lib/Cake/Test/Case/Utility/InflectorTest.php +++ b/lib/Cake/Test/Case/Utility/InflectorTest.php @@ -117,6 +117,7 @@ class InflectorTest extends CakeTestCase { $this->assertEquals(Inflector::singularize('teeth'), 'tooth'); $this->assertEquals(Inflector::singularize('geese'), 'goose'); $this->assertEquals(Inflector::singularize('feet'), 'foot'); + $this->assertEquals(Inflector::singularize('objectives'), 'objective'); $this->assertEquals(Inflector::singularize(''), ''); } @@ -177,6 +178,7 @@ class InflectorTest extends CakeTestCase { $this->assertEquals(Inflector::pluralize('tooth'), 'teeth'); $this->assertEquals(Inflector::pluralize('goose'), 'geese'); $this->assertEquals(Inflector::pluralize('foot'), 'feet'); + $this->assertEquals(Inflector::pluralize('objective'), 'objectives'); $this->assertEquals(Inflector::pluralize(''), ''); } diff --git a/lib/Cake/Test/Case/Utility/ValidationTest.php b/lib/Cake/Test/Case/Utility/ValidationTest.php index 6a3311207..04d0884e5 100644 --- a/lib/Cake/Test/Case/Utility/ValidationTest.php +++ b/lib/Cake/Test/Case/Utility/ValidationTest.php @@ -1426,6 +1426,45 @@ class ValidationTest extends CakeTestCase { $this->assertFalse(Validation::date('12 06', array('my'))); } +/** + * testDateYmNumeric method + * + * @return void + */ + public function testDateYmNumeric() { + $this->assertTrue(Validation::date('2006/12', array('ym'))); + $this->assertTrue(Validation::date('2006-12', array('ym'))); + $this->assertTrue(Validation::date('2006-12', array('ym'))); + $this->assertTrue(Validation::date('2006 12', array('ym'))); + $this->assertTrue(Validation::date('2006 12', array('ym'))); + $this->assertTrue(Validation::date('1900-01', array('ym'))); + $this->assertTrue(Validation::date('2153-01', array('ym'))); + $this->assertFalse(Validation::date('2006/12 ', array('ym'))); + $this->assertFalse(Validation::date('2006/12/', array('ym'))); + $this->assertFalse(Validation::date('06/12', array('ym'))); + $this->assertFalse(Validation::date('06-12', array('ym'))); + $this->assertFalse(Validation::date('06-12', array('ym'))); + $this->assertFalse(Validation::date('06 12', array('ym'))); + } + +/** + * testDateY method + * + * @return void + */ + public function testDateY() { + $this->assertTrue(Validation::date('1900', array('y'))); + $this->assertTrue(Validation::date('1984', array('y'))); + $this->assertTrue(Validation::date('2006', array('y'))); + $this->assertTrue(Validation::date('2008', array('y'))); + $this->assertTrue(Validation::date('2013', array('y'))); + $this->assertTrue(Validation::date('2104', array('y'))); + $this->assertFalse(Validation::date('20009', array('y'))); + $this->assertFalse(Validation::date(' 2012', array('y'))); + $this->assertFalse(Validation::date('3000', array('y'))); + $this->assertFalse(Validation::date('1899', array('y'))); + } + /** * Test validating dates with multiple formats * @@ -2090,8 +2129,28 @@ class ValidationTest extends CakeTestCase { $this->assertFalse(Validation::phone('(055) 999-9999')); $this->assertFalse(Validation::phone('(155) 999-9999')); $this->assertFalse(Validation::phone('(595) 999-9999')); - $this->assertFalse(Validation::phone('(555) 099-9999')); - $this->assertFalse(Validation::phone('(555) 199-9999')); + $this->assertFalse(Validation::phone('(213) 099-9999')); + $this->assertFalse(Validation::phone('(213) 199-9999')); + + // invalid area-codes + $this->assertFalse(Validation::phone('1-(511)-999-9999')); + $this->assertFalse(Validation::phone('1-(379)-999-9999')); + $this->assertFalse(Validation::phone('1-(962)-999-9999')); + $this->assertFalse(Validation::phone('1-(295)-999-9999')); + $this->assertFalse(Validation::phone('1-(555)-999-9999')); + + // invalid exhange + $this->assertFalse(Validation::phone('1-(222)-511-9999')); + + // invalid phone number + $this->assertFalse(Validation::phone('1-(222)-555-0199')); + $this->assertFalse(Validation::phone('1-(222)-555-0122')); + + // valid phone numbers + $this->assertTrue(Validation::phone('1-(369)-333-4444')); + $this->assertTrue(Validation::phone('1-(973)-333-4444')); + $this->assertTrue(Validation::phone('1-(313)-555-9999')); + $this->assertTrue(Validation::phone('1-(222)-555-0299')); $this->assertTrue(Validation::phone('1 (222) 333 4444')); $this->assertTrue(Validation::phone('+1 (222) 333 4444')); diff --git a/lib/Cake/Test/Case/Utility/XmlTest.php b/lib/Cake/Test/Case/Utility/XmlTest.php index 7896ed4c9..fc9c7c265 100644 --- a/lib/Cake/Test/Case/Utility/XmlTest.php +++ b/lib/Cake/Test/Case/Utility/XmlTest.php @@ -427,6 +427,87 @@ XML; $this->assertXmlStringEqualsXmlString($expected, $obj->asXML()); } +/** + * testFromArrayPretty method + * + * @return void + */ + public function testFromArrayPretty() { + $xml = array( + 'tags' => array( + 'tag' => array( + array( + 'id' => '1', + 'name' => 'defect' + ), + array( + 'id' => '2', + 'name' => 'enhancement' + ) + ) + ) + ); + + $expected = << +1defect2enhancement + +XML; + $xmlResponse = Xml::fromArray($xml, array('pretty' => false)); + $this->assertEquals($expected, $xmlResponse->asXML()); + + $expected = << + + + 1 + defect + + + 2 + enhancement + + + +XML; + $xmlResponse = Xml::fromArray($xml, array('pretty' => true)); + $this->assertEquals($expected, $xmlResponse->asXML()); + + $xml = array( + 'tags' => array( + 'tag' => array( + array( + 'id' => '1', + 'name' => 'defect' + ), + array( + 'id' => '2', + 'name' => 'enhancement' + ) + ) + ) + ); + + $expected = << + + +XML; + $xmlResponse = Xml::fromArray($xml, array('pretty' => false, 'format' => 'attributes')); + $this->assertEquals($expected, $xmlResponse->asXML()); + + $expected = << + + + + + +XML; + $xmlResponse = Xml::fromArray($xml, array('pretty' => true, 'format' => 'attributes')); + $this->assertEquals($expected, $xmlResponse->asXML()); + } + /** * data provider for fromArray() failures * diff --git a/lib/Cake/Test/Case/View/Helper/FormHelperTest.php b/lib/Cake/Test/Case/View/Helper/FormHelperTest.php index eb4d100b3..a09c713bf 100644 --- a/lib/Cake/Test/Case/View/Helper/FormHelperTest.php +++ b/lib/Cake/Test/Case/View/Helper/FormHelperTest.php @@ -4218,6 +4218,32 @@ class FormHelperTest extends CakeTestCase { '/select' ); $this->assertTags($result, $expected); + + $result = $this->Form->select('Contact.required_one', array('option A')); + $expected = array( + 'select' => array( + 'name' => 'data[Contact][required_one]', + 'id' => 'ContactRequiredOne', + 'required' => 'required' + ), + array('option' => array('value' => '')), '/option', + array('option' => array('value' => '0')), 'option A', '/option', + '/select' + ); + $this->assertTags($result, $expected); + + $result = $this->Form->select('Contact.required_one', array('option A'), array('disabled' => true)); + $expected = array( + 'select' => array( + 'name' => 'data[Contact][required_one]', + 'id' => 'ContactRequiredOne', + 'disabled' => 'disabled' + ), + array('option' => array('value' => '')), '/option', + array('option' => array('value' => '0')), 'option A', '/option', + '/select' + ); + $this->assertTags($result, $expected); } /** @@ -4471,6 +4497,26 @@ class FormHelperTest extends CakeTestCase { '/select' ); $this->assertTags($result, $expected); + + $result = $this->Form->select('Contact.required_one', array( + '1' => 'option A', + '2' => 'option B' + ), array('multiple' => true)); + $expected = array( + 'input' => array( + 'type' => 'hidden', 'name' => 'data[Contact][required_one]', 'value' => '', 'id' => 'ContactRequiredOne_' + ), + 'select' => array( + 'name' => 'data[Contact][required_one][]', + 'id' => 'ContactRequiredOne', + 'required' => 'required', + 'multiple' => 'multiple' + ), + array('option' => array('value' => '1')), 'option A', '/option', + array('option' => array('value' => '2')), 'option B', '/option', + '/select' + ); + $this->assertTags($result, $expected); } /** @@ -5831,6 +5877,33 @@ class FormHelperTest extends CakeTestCase { $this->assertRegExp('/]+value="35"[^<>]+selected="selected"[^>]*>35<\/option>/', $result); } +/** + * Test dateTime with rounding + * + * @return void + */ + public function testDateTimeRounding() { + $this->Form->request->data['Contact'] = array( + 'date' => array( + 'day' => '13', + 'month' => '12', + 'year' => '2010', + 'hour' => '04', + 'min' => '19', + 'meridian' => 'AM' + ) + ); + + $result = $this->Form->dateTime('Contact.date', 'DMY', '12', array('interval' => 15)); + $this->assertTextContains('', $result); + + $result = $this->Form->dateTime('Contact.date', 'DMY', '12', array('interval' => 15, 'round' => 'up')); + $this->assertTextContains('', $result); + + $result = $this->Form->dateTime('Contact.date', 'DMY', '12', array('interval' => 5, 'round' => 'down')); + $this->assertTextContains('', $result); + } + /** * Test that empty values don't trigger errors. * @@ -6972,7 +7045,20 @@ class FormHelperTest extends CakeTestCase { ), 'input' => array('type' => 'hidden', 'name' => '_method', 'value' => 'POST'), '/form', - 'a' => array('href' => '#', 'onclick' => 'preg:/if \(confirm\('Confirm\?'\)\) \{ document\.post_\w+\.submit\(\); \} event\.returnValue = false; return false;/'), + 'a' => array('href' => '#', 'onclick' => 'preg:/if \(confirm\("Confirm\?"\)\) \{ document\.post_\w+\.submit\(\); \} event\.returnValue = false; return false;/'), + 'Delete', + '/a' + )); + + $result = $this->Form->postLink('Delete', '/posts/delete/1', array('escape' => false), '\'Confirm\' this "deletion"?'); + $this->assertTags($result, array( + 'form' => array( + 'method' => 'post', 'action' => '/posts/delete/1', + 'name' => 'preg:/post_\w+/', 'id' => 'preg:/post_\w+/', 'style' => 'display:none;' + ), + 'input' => array('type' => 'hidden', 'name' => '_method', 'value' => 'POST'), + '/form', + 'a' => array('href' => '#', 'onclick' => 'preg:/if \(confirm\("'Confirm' this \\\\"deletion\\\\"\?"\)\) \{ document\.post_\w+\.submit\(\); \} event\.returnValue = false; return false;/'), 'Delete', '/a' )); @@ -7006,7 +7092,7 @@ class FormHelperTest extends CakeTestCase { ), 'input' => array('type' => 'hidden', 'name' => '_method', 'value' => 'POST'), '/form', - 'a' => array('class' => 'btn btn-danger', 'href' => '#', 'onclick' => 'preg:/if \(confirm\(\'Confirm thing\'\)\) \{ document\.post_\w+\.submit\(\); \} event\.returnValue = false; return false;/'), + 'a' => array('class' => 'btn btn-danger', 'href' => '#', 'onclick' => 'preg:/if \(confirm\(\"\;Confirm thing\"\;\)\) \{ document\.post_\w+\.submit\(\); \} event\.returnValue = false; return false;/'), '/a' )); } diff --git a/lib/Cake/Test/Case/View/Helper/HtmlHelperTest.php b/lib/Cake/Test/Case/View/Helper/HtmlHelperTest.php index ee3243972..cafbeab07 100644 --- a/lib/Cake/Test/Case/View/Helper/HtmlHelperTest.php +++ b/lib/Cake/Test/Case/View/Helper/HtmlHelperTest.php @@ -210,12 +210,20 @@ class HtmlHelperTest extends CakeTestCase { Router::reload(); $result = $this->Html->link('Posts', array('controller' => 'posts', 'action' => 'index', 'full_base' => true)); - $expected = array('a' => array('href' => FULL_BASE_URL . '/posts'), 'Posts', '/a'); + $expected = array('a' => array('href' => Router::fullBaseUrl() . '/posts'), 'Posts', '/a'); $this->assertTags($result, $expected); $result = $this->Html->link('Home', '/home', array('confirm' => 'Are you sure you want to do this?')); $expected = array( - 'a' => array('href' => '/home', 'onclick' => 'return confirm('Are you sure you want to do this?');'), + 'a' => array('href' => '/home', 'onclick' => 'if (confirm("Are you sure you want to do this?")) { return true; } return false;'), + 'Home', + '/a' + ); + $this->assertTags($result, $expected); + + $result = $this->Html->link('Home', '/home', array('escape' => false, 'confirm' => 'Confirm\'s "nightmares"')); + $expected = array( + 'a' => array('href' => '/home', 'onclick' => 'if (confirm("Confirm's \"nightmares\"")) { return true; } return false;'), 'Home', '/a' ); @@ -291,6 +299,17 @@ class HtmlHelperTest extends CakeTestCase { ); $this->assertTags($result, $expected); + $result = $this->Html->link('Next >', '#', array( + 'title' => 'Next >', + 'escapeTitle' => false + )); + $expected = array( + 'a' => array('href' => '#', 'title' => 'Next >'), + 'Next >', + '/a' + ); + $this->assertTags($result, $expected); + $result = $this->Html->link('Original size', array( 'controller' => 'images', 'action' => 'view', 3, '?' => array('height' => 100, 'width' => 200) )); @@ -311,6 +330,17 @@ class HtmlHelperTest extends CakeTestCase { ); $this->assertTags($result, $expected); + $result = $this->Html->link($this->Html->image('test.gif'), '#', array( + 'title' => 'hey "howdy"', + 'escapeTitle' => false + )); + $expected = array( + 'a' => array('href' => '#', 'title' => 'hey "howdy"'), + 'img' => array('src' => 'img/test.gif', 'alt' => ''), + '/a' + ); + $this->assertTags($result, $expected); + $result = $this->Html->image('test.gif', array('url' => '#')); $expected = array( 'a' => array('href' => '#'), @@ -378,6 +408,9 @@ class HtmlHelperTest extends CakeTestCase { $result = $this->Html->image('http://google.com/logo.gif'); $this->assertTags($result, array('img' => array('src' => 'http://google.com/logo.gif', 'alt' => ''))); + $result = $this->Html->image('//google.com/logo.gif'); + $this->assertTags($result, array('img' => array('src' => '//google.com/logo.gif', 'alt' => ''))); + $result = $this->Html->image(array('controller' => 'test', 'action' => 'view', 1, 'ext' => 'gif')); $this->assertTags($result, array('img' => array('src' => '/test/view/1.gif', 'alt' => ''))); @@ -386,6 +419,21 @@ class HtmlHelperTest extends CakeTestCase { $result = $this->Html->image('test.gif?one=two&three=four'); $this->assertTags($result, array('img' => array('src' => 'img/test.gif?one=two&three=four', 'alt' => ''))); + + $result = $this->Html->image('test.gif', array('pathPrefix' => '/my/custom/path/')); + $this->assertTags($result, array('img' => array('src' => '/my/custom/path/test.gif', 'alt' => ''))); + + $result = $this->Html->image('test.gif', array('pathPrefix' => 'http://cakephp.org/assets/img/')); + $this->assertTags($result, array('img' => array('src' => 'http://cakephp.org/assets/img/test.gif', 'alt' => ''))); + + $result = $this->Html->image('test.gif', array('pathPrefix' => '//cakephp.org/assets/img/')); + $this->assertTags($result, array('img' => array('src' => '//cakephp.org/assets/img/test.gif', 'alt' => ''))); + + $previousConfig = Configure::read('App.imageBaseUrl'); + Configure::write('App.imageBaseUrl', '//cdn.cakephp.org/img/'); + $result = $this->Html->image('test.gif'); + $this->assertTags($result, array('img' => array('src' => '//cdn.cakephp.org/img/test.gif', 'alt' => ''))); + Configure::write('App.imageBaseUrl', $previousConfig); } /** @@ -550,7 +598,7 @@ class HtmlHelperTest extends CakeTestCase { $this->assertTags($result, $expected); CakePlugin::load('TestPlugin'); - $result = $this->Html->css('TestPlugin.style', null, array('plugin' => false)); + $result = $this->Html->css('TestPlugin.style', array('plugin' => false)); $expected['link']['href'] = 'preg:/.*css\/TestPlugin\.style\.css/'; $this->assertTags($result, $expected); CakePlugin::unload('TestPlugin'); @@ -571,6 +619,21 @@ class HtmlHelperTest extends CakeTestCase { $expected['link']['href'] = 'preg:/http:\/\/.*\/screen\.css\?1234/'; $this->assertTags($result, $expected); + $result = $this->Html->css('cake.generic', array('pathPrefix' => '/my/custom/path/')); + $expected['link']['href'] = '/my/custom/path/cake.generic.css'; + $this->assertTags($result, $expected); + + $result = $this->Html->css('cake.generic', array('pathPrefix' => 'http://cakephp.org/assets/css/')); + $expected['link']['href'] = 'http://cakephp.org/assets/css/cake.generic.css'; + $this->assertTags($result, $expected); + + $previousConfig = Configure::read('App.cssBaseUrl'); + Configure::write('App.cssBaseUrl', '//cdn.cakephp.org/css/'); + $result = $this->Html->css('cake.generic'); + $expected['link']['href'] = '//cdn.cakephp.org/css/cake.generic.css'; + $this->assertTags($result, $expected); + Configure::write('App.cssBaseUrl', $previousConfig); + Configure::write('Asset.filter.css', 'css.php'); $result = $this->Html->css('cake.generic'); $expected['link']['href'] = 'preg:/.*ccss\/cake\.generic\.css/'; @@ -597,6 +660,49 @@ class HtmlHelperTest extends CakeTestCase { ->method('append') ->with('css', $this->matchesRegularExpression('/more_css_in_head.css/')); + $result = $this->Html->css('css_in_head', array('inline' => false)); + $this->assertNull($result); + + $result = $this->Html->css('more_css_in_head', array('inline' => false)); + $this->assertNull($result); + + $result = $this->Html->css('screen', array('rel' => 'import')); + $expected = array( + 'style' => array('type' => 'text/css'), + 'preg:/@import url\(.*css\/screen\.css\);/', + '/style' + ); + $this->assertTags($result, $expected); + } + +/** + * Test css link BC usage + * + * @return void + */ + public function testCssLinkBC() { + Configure::write('Asset.filter.css', false); + + CakePlugin::load('TestPlugin'); + $result = $this->Html->css('TestPlugin.style', null, array('plugin' => false)); + $expected = array( + 'link' => array( + 'rel' => 'stylesheet', + 'type' => 'text/css', + 'href' => 'preg:/.*css\/TestPlugin\.style\.css/' + ) + ); + $this->assertTags($result, $expected); + CakePlugin::unload('TestPlugin'); + + $result = $this->Html->css('screen', 'import'); + $expected = array( + 'style' => array('type' => 'text/css'), + 'preg:/@import url\(.*css\/screen\.css\);/', + '/style' + ); + $this->assertTags($result, $expected); + $result = $this->Html->css('css_in_head', null, array('inline' => false)); $this->assertNull($result); @@ -861,6 +967,27 @@ class HtmlHelperTest extends CakeTestCase { ); $this->assertTags($result, $expected); + $result = $this->Html->script('foo2', array('pathPrefix' => '/my/custom/path/')); + $expected = array( + 'script' => array('type' => 'text/javascript', 'src' => '/my/custom/path/foo2.js') + ); + $this->assertTags($result, $expected); + + $result = $this->Html->script('foo3', array('pathPrefix' => 'http://cakephp.org/assets/js/')); + $expected = array( + 'script' => array('type' => 'text/javascript', 'src' => 'http://cakephp.org/assets/js/foo3.js') + ); + $this->assertTags($result, $expected); + + $previousConfig = Configure::read('App.jsBaseUrl'); + Configure::write('App.jsBaseUrl', '//cdn.cakephp.org/js/'); + $result = $this->Html->script('foo4'); + $expected = array( + 'script' => array('type' => 'text/javascript', 'src' => '//cdn.cakephp.org/js/foo4.js') + ); + $this->assertTags($result, $expected); + Configure::write('App.jsBaseUrl', $previousConfig); + $result = $this->Html->script('foo'); $this->assertNull($result, 'Script returned upon duplicate inclusion %s'); @@ -1713,9 +1840,6 @@ class HtmlHelperTest extends CakeTestCase { $result = $this->Html->tag('div', 'text'); $this->assertTags($result, 'Html->tag('div', '', 'class-name'); - $this->assertTags($result, array('div' => array('class' => 'class-name'), 'preg://', '/div')); - $result = $this->Html->tag('div', '', array('class' => 'class-name', 'escape' => true)); $this->assertTags($result, array('div' => array('class' => 'class-name'), '<text>', '/div')); @@ -1812,7 +1936,7 @@ class HtmlHelperTest extends CakeTestCase { array('pathPrefix' => 'videos/', 'poster' => 'poster.jpg', 'text' => 'Your browser does not support the HTML5 Video element.') ); $expected = array( - 'video' => array('poster' => IMAGES_URL . 'poster.jpg'), + 'video' => array('poster' => Configure::read('App.imageBaseUrl') . 'poster.jpg'), array('source' => array('src' => 'videos/video.webm', 'type' => 'video/webm')), array('source' => array('src' => 'videos/video.ogv', 'type' => 'video/ogg; codecs='theora, vorbis'')), 'Your browser does not support the HTML5 Video element.', diff --git a/lib/Cake/Test/Case/View/Helper/PaginatorHelperTest.php b/lib/Cake/Test/Case/View/Helper/PaginatorHelperTest.php index d463f7d9c..294c81367 100644 --- a/lib/Cake/Test/Case/View/Helper/PaginatorHelperTest.php +++ b/lib/Cake/Test/Case/View/Helper/PaginatorHelperTest.php @@ -158,7 +158,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->sort('title'); $expected = array( - 'a' => array('href' => '/officespace/accounts/index/param/page:1/sort:title/direction:asc'), + 'a' => array('href' => '/officespace/accounts/index/param/sort:title/direction:asc'), 'Title', '/a' ); @@ -166,7 +166,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->sort('date'); $expected = array( - 'a' => array('href' => '/officespace/accounts/index/param/page:1/sort:date/direction:desc', 'class' => 'asc'), + 'a' => array('href' => '/officespace/accounts/index/param/sort:date/direction:desc', 'class' => 'asc'), 'Date', '/a' ); @@ -174,7 +174,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->sort('title', 'TestTitle'); $expected = array( - 'a' => array('href' => '/officespace/accounts/index/param/page:1/sort:title/direction:asc'), + 'a' => array('href' => '/officespace/accounts/index/param/sort:title/direction:asc'), 'TestTitle', '/a' ); @@ -182,7 +182,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->sort('title', array('asc' => 'ascending', 'desc' => 'descending')); $expected = array( - 'a' => array('href' => '/officespace/accounts/index/param/page:1/sort:title/direction:asc'), + 'a' => array('href' => '/officespace/accounts/index/param/sort:title/direction:asc'), 'ascending', '/a' ); @@ -191,7 +191,7 @@ class PaginatorHelperTest extends CakeTestCase { $this->Paginator->request->params['paging']['Article']['options']['sort'] = 'title'; $result = $this->Paginator->sort('title', array('asc' => 'ascending', 'desc' => 'descending')); $expected = array( - 'a' => array('href' => '/officespace/accounts/index/param/page:1/sort:title/direction:desc', 'class' => 'asc'), + 'a' => array('href' => '/officespace/accounts/index/param/sort:title/direction:desc', 'class' => 'asc'), 'descending', '/a' ); @@ -200,37 +200,37 @@ class PaginatorHelperTest extends CakeTestCase { $this->Paginator->request->params['paging']['Article']['options']['order'] = array('Article.title' => 'desc'); $this->Paginator->request->params['paging']['Article']['options']['sort'] = null; $result = $this->Paginator->sort('title'); - $this->assertRegExp('/\/accounts\/index\/param\/page:1\/sort:title\/direction:asc" class="desc">Title<\/a>$/', $result); + $this->assertRegExp('/\/accounts\/index\/param\/sort:title\/direction:asc" class="desc">Title<\/a>$/', $result); $this->Paginator->request->params['paging']['Article']['options']['order'] = array('Article.title' => 'asc'); $this->Paginator->request->params['paging']['Article']['options']['sort'] = null; $result = $this->Paginator->sort('title'); - $this->assertRegExp('/\/accounts\/index\/param\/page:1\/sort:title\/direction:desc" class="asc">Title<\/a>$/', $result); + $this->assertRegExp('/\/accounts\/index\/param\/sort:title\/direction:desc" class="asc">Title<\/a>$/', $result); $this->Paginator->request->params['paging']['Article']['options']['order'] = array('Article.title' => 'desc'); $this->Paginator->request->params['paging']['Article']['options']['sort'] = null; $result = $this->Paginator->sort('title', 'Title', array('direction' => 'desc')); - $this->assertRegExp('/\/accounts\/index\/param\/page:1\/sort:title\/direction:asc" class="desc">Title<\/a>$/', $result); + $this->assertRegExp('/\/accounts\/index\/param\/sort:title\/direction:asc" class="desc">Title<\/a>$/', $result); $this->Paginator->request->params['paging']['Article']['options']['order'] = array('Article.title' => 'desc'); $this->Paginator->request->params['paging']['Article']['options']['sort'] = null; $result = $this->Paginator->sort('title', 'Title', array('direction' => 'asc')); - $this->assertRegExp('/\/accounts\/index\/param\/page:1\/sort:title\/direction:asc" class="desc">Title<\/a>$/', $result); + $this->assertRegExp('/\/accounts\/index\/param\/sort:title\/direction:asc" class="desc">Title<\/a>$/', $result); $this->Paginator->request->params['paging']['Article']['options']['order'] = array('Article.title' => 'asc'); $this->Paginator->request->params['paging']['Article']['options']['sort'] = null; $result = $this->Paginator->sort('title', 'Title', array('direction' => 'asc')); - $this->assertRegExp('/\/accounts\/index\/param\/page:1\/sort:title\/direction:desc" class="asc">Title<\/a>$/', $result); + $this->assertRegExp('/\/accounts\/index\/param\/sort:title\/direction:desc" class="asc">Title<\/a>$/', $result); $this->Paginator->request->params['paging']['Article']['options']['order'] = array('Article.title' => 'asc'); $this->Paginator->request->params['paging']['Article']['options']['sort'] = null; $result = $this->Paginator->sort('title', 'Title', array('direction' => 'desc')); - $this->assertRegExp('/\/accounts\/index\/param\/page:1\/sort:title\/direction:desc" class="asc">Title<\/a>$/', $result); + $this->assertRegExp('/\/accounts\/index\/param\/sort:title\/direction:desc" class="asc">Title<\/a>$/', $result); $this->Paginator->request->params['paging']['Article']['options']['order'] = array('Article.title' => 'asc'); $this->Paginator->request->params['paging']['Article']['options']['sort'] = null; $result = $this->Paginator->sort('title', 'Title', array('direction' => 'desc', 'class' => 'foo')); - $this->assertRegExp('/\/accounts\/index\/param\/page:1\/sort:title\/direction:desc" class="foo asc">Title<\/a>$/', $result); + $this->assertRegExp('/\/accounts\/index\/param\/sort:title\/direction:desc" class="foo asc">Title<\/a>$/', $result); } /** @@ -247,7 +247,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->sort('Article.full_name'); $expected = array( - 'a' => array('href' => '/accounts/index/page:1/sort:Article.full_name/direction:desc', 'class' => 'asc'), + 'a' => array('href' => '/accounts/index/sort:Article.full_name/direction:desc', 'class' => 'asc'), 'Article Full Name', '/a' ); @@ -255,7 +255,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->sort('full_name'); $expected = array( - 'a' => array('href' => '/accounts/index/page:1/sort:full_name/direction:desc', 'class' => 'asc'), + 'a' => array('href' => '/accounts/index/sort:full_name/direction:desc', 'class' => 'asc'), 'Full Name', '/a' ); @@ -264,7 +264,7 @@ class PaginatorHelperTest extends CakeTestCase { $this->Paginator->request->params['paging']['Article']['options']['order'] = array('full_name' => 'desc'); $result = $this->Paginator->sort('Article.full_name'); $expected = array( - 'a' => array('href' => '/accounts/index/page:1/sort:Article.full_name/direction:asc', 'class' => 'desc'), + 'a' => array('href' => '/accounts/index/sort:Article.full_name/direction:asc', 'class' => 'desc'), 'Article Full Name', '/a' ); @@ -272,7 +272,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->sort('full_name'); $expected = array( - 'a' => array('href' => '/accounts/index/page:1/sort:full_name/direction:asc', 'class' => 'desc'), + 'a' => array('href' => '/accounts/index/sort:full_name/direction:asc', 'class' => 'desc'), 'Full Name', '/a' ); @@ -296,7 +296,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->sort('title', 'TestTitle', array('direction' => 'desc')); $expected = array( - 'a' => array('href' => '/accounts/index/param/page:1/sort:title/direction:desc'), + 'a' => array('href' => '/accounts/index/param/sort:title/direction:desc'), 'TestTitle', '/a' ); @@ -304,7 +304,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->sort('title', array('asc' => 'ascending', 'desc' => 'descending'), array('direction' => 'desc')); $expected = array( - 'a' => array('href' => '/accounts/index/param/page:1/sort:title/direction:desc'), + 'a' => array('href' => '/accounts/index/param/sort:title/direction:desc'), 'descending', '/a' ); @@ -327,7 +327,7 @@ class PaginatorHelperTest extends CakeTestCase { $this->Paginator->request->params['paging']['Article']['options']['order'] = array('Article.title' => 'desc'); $result = $this->Paginator->sort('Article.title'); $expected = array( - 'a' => array('href' => '/officespace/accounts/index/page:1/sort:Article.title/direction:asc', 'class' => 'desc'), + 'a' => array('href' => '/officespace/accounts/index/sort:Article.title/direction:asc', 'class' => 'desc'), 'Article Title', '/a' ); @@ -336,7 +336,7 @@ class PaginatorHelperTest extends CakeTestCase { $this->Paginator->request->params['paging']['Article']['options']['order'] = array('Article.title' => 'desc'); $result = $this->Paginator->sort('Article.title', 'Title'); $expected = array( - 'a' => array('href' => '/officespace/accounts/index/page:1/sort:Article.title/direction:asc', 'class' => 'desc'), + 'a' => array('href' => '/officespace/accounts/index/sort:Article.title/direction:asc', 'class' => 'desc'), 'Title', '/a' ); @@ -345,7 +345,7 @@ class PaginatorHelperTest extends CakeTestCase { $this->Paginator->request->params['paging']['Article']['options']['order'] = array('Article.title' => 'asc'); $result = $this->Paginator->sort('Article.title', 'Title'); $expected = array( - 'a' => array('href' => '/officespace/accounts/index/page:1/sort:Article.title/direction:desc', 'class' => 'asc'), + 'a' => array('href' => '/officespace/accounts/index/sort:Article.title/direction:desc', 'class' => 'asc'), 'Title', '/a' ); @@ -354,7 +354,7 @@ class PaginatorHelperTest extends CakeTestCase { $this->Paginator->request->params['paging']['Article']['options']['order'] = array('Account.title' => 'asc'); $result = $this->Paginator->sort('title'); $expected = array( - 'a' => array('href' => '/officespace/accounts/index/page:1/sort:title/direction:asc'), + 'a' => array('href' => '/officespace/accounts/index/sort:title/direction:asc'), 'Title', '/a' ); @@ -535,7 +535,7 @@ class PaginatorHelperTest extends CakeTestCase { $this->Paginator->options(array('url' => array('param'))); $result = $this->Paginator->sort('title'); $expected = array( - 'a' => array('href' => '/admin/test/index/param/page:1/sort:title/direction:asc'), + 'a' => array('href' => '/admin/test/index/param/sort:title/direction:asc'), 'Title', '/a' ); @@ -544,7 +544,7 @@ class PaginatorHelperTest extends CakeTestCase { $this->Paginator->options(array('url' => array('param'))); $result = $this->Paginator->sort('Article.title', 'Title'); $expected = array( - 'a' => array('href' => '/admin/test/index/param/page:1/sort:Article.title/direction:asc'), + 'a' => array('href' => '/admin/test/index/param/sort:Article.title/direction:asc'), 'Title', '/a' ); @@ -559,14 +559,14 @@ class PaginatorHelperTest extends CakeTestCase { public function testUrlGeneration() { $result = $this->Paginator->sort('controller'); $expected = array( - 'a' => array('href' => '/index/page:1/sort:controller/direction:asc'), + 'a' => array('href' => '/index/sort:controller/direction:asc'), 'Controller', '/a' ); $this->assertTags($result, $expected); $result = $this->Paginator->url(); - $this->assertEquals('/index/page:1', $result); + $this->assertEquals('/', $result); $this->Paginator->request->params['paging']['Article']['options']['page'] = 2; $result = $this->Paginator->url(); @@ -628,7 +628,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->prev('prev', array('url' => $options)); $expected = array( 'span' => array('class' => 'prev'), - 'a' => array('href' => '/members/posts/index/page:1', 'rel' => 'prev'), + 'a' => array('href' => '/members/posts', 'rel' => 'prev'), 'prev', '/a', '/span' @@ -725,7 +725,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->sort('title'); $expected = array( - 'a' => array('href' => '/articles/index/2/page:1/foo:bar/sort:title/direction:asc?x=y'), + 'a' => array('href' => '/articles/index/2/foo:bar/sort:title/direction:asc?x=y'), 'Title', '/a' ); @@ -792,7 +792,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->sort('title'); $expected = array( - 'a' => array('href' => '/articles/index/2/foo:bar?x=y&page=1&sort=title&direction=asc'), + 'a' => array('href' => '/articles/index/2/foo:bar?x=y&sort=title&direction=asc'), 'Title', '/a' ); @@ -868,7 +868,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->prev('<< Previous', null, null, array('class' => 'disabled')); $expected = array( 'span' => array('class' => 'prev'), - 'a' => array('href' => '/index/page:1', 'rel' => 'prev'), + 'a' => array('href' => '/', 'rel' => 'prev'), '<< Previous', '/a', '/span' @@ -877,7 +877,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->prev('<< Previous', array('tag' => false), null, array('class' => 'disabled')); $expected = array( - 'a' => array('href' => '/index/page:1', 'rel' => 'prev', 'class' => 'prev'), + 'a' => array('href' => '/', 'rel' => 'prev', 'class' => 'prev'), '<< Previous', '/a' ); @@ -891,7 +891,7 @@ class PaginatorHelperTest extends CakeTestCase { ); $expected = array( 'span' => array('class' => 'prev'), - 'a' => array('href' => '/index/page:1', 'rel' => 'prev'), + 'a' => array('href' => '/', 'rel' => 'prev'), '<< Previous', '/a', '/span' @@ -929,7 +929,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->prev('<< Previous', array('escape' => true)); $expected = array( 'span' => array('class' => 'prev'), - 'a' => array('href' => '/index/page:1', 'rel' => 'prev'), + 'a' => array('href' => '/', 'rel' => 'prev'), '<< Previous', '/a', '/span' @@ -939,7 +939,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->prev('<< Previous', array('escape' => false)); $expected = array( 'span' => array('class' => 'prev'), - 'a' => array('href' => '/index/page:1', 'rel' => 'prev'), + 'a' => array('href' => '/', 'rel' => 'prev'), 'preg:/<< Previous/', '/a', '/span' @@ -1043,7 +1043,7 @@ class PaginatorHelperTest extends CakeTestCase { $expected = array( 'span' => array('class' => 'prev'), 'a' => array( - 'href' => '/index/page:1/limit:3/sort:Client.name/direction:DESC', + 'href' => '/index/limit:3/sort:Client.name/direction:DESC', 'rel' => 'prev' ), '<< Previous', @@ -1085,7 +1085,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->prev('Prev'); $expected = array( 'span' => array('class' => 'prev'), - 'a' => array('href' => '/index/page:1/limit:10', 'rel' => 'prev'), + 'a' => array('href' => '/index/limit:10', 'rel' => 'prev'), 'Prev', '/a', '/span' @@ -1115,7 +1115,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->prev('Prev', array('url' => array('foo' => 'bar'))); $expected = array( 'span' => array('class' => 'prev'), - 'a' => array('href' => '/index/12/page:1/limit:10/foo:bar', 'rel' => 'prev'), + 'a' => array('href' => '/index/12/limit:10/foo:bar', 'rel' => 'prev'), 'Prev', '/a', '/span' @@ -1255,13 +1255,13 @@ class PaginatorHelperTest extends CakeTestCase { */ public function testGenericLinksWithPresetOptions() { $result = $this->Paginator->link('Foo!', array('page' => 1)); - $this->assertTags($result, array('a' => array('href' => '/index/page:1'), 'Foo!', '/a')); + $this->assertTags($result, array('a' => array('href' => '/'), 'Foo!', '/a')); $this->Paginator->options(array('sort' => 'title', 'direction' => 'desc')); $result = $this->Paginator->link('Foo!', array('page' => 1)); $this->assertTags($result, array( 'a' => array( - 'href' => '/index/page:1', + 'href' => '/', 'sort' => 'title', 'direction' => 'desc' ), @@ -1271,7 +1271,7 @@ class PaginatorHelperTest extends CakeTestCase { $this->Paginator->options(array('sort' => null, 'direction' => null)); $result = $this->Paginator->link('Foo!', array('page' => 1)); - $this->assertTags($result, array('a' => array('href' => '/index/page:1'), 'Foo!', '/a')); + $this->assertTags($result, array('a' => array('href' => '/'), 'Foo!', '/a')); $this->Paginator->options(array('url' => array( 'sort' => 'title', @@ -1279,7 +1279,7 @@ class PaginatorHelperTest extends CakeTestCase { ))); $result = $this->Paginator->link('Foo!', array('page' => 1)); $this->assertTags($result, array( - 'a' => array('href' => '/index/page:1/sort:title/direction:desc'), + 'a' => array('href' => '/index/sort:title/direction:desc'), 'Foo!', '/a' )); @@ -1365,7 +1365,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->numbers(true); $expected = array( - array('span' => array()), array('a' => array('href' => '/index/page:1', 'rel' => 'first')), 'first', '/a', '/span', + array('span' => array()), array('a' => array('href' => '/', 'rel' => 'first')), 'first', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:4')), '4', '/a', '/span', ' | ', @@ -1478,7 +1478,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->numbers(array('first' => 1, 'class' => 'page-link')); $expected = array( - array('span' => array('class' => 'page-link')), array('a' => array('href' => '/index/page:1')), '1', '/a', '/span', + array('span' => array('class' => 'page-link')), array('a' => array('href' => '/')), '1', '/a', '/span', ' | ', array('span' => array('class' => 'current page-link')), '2', '/span', ' | ', @@ -1500,7 +1500,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->numbers(array('first' => 1, 'currentClass' => 'active')); $expected = array( - array('span' => array()), array('a' => array('href' => '/index/page:1')), '1', '/a', '/span', + array('span' => array()), array('a' => array('href' => '/')), '1', '/a', '/span', ' | ', array('span' => array('class' => 'active')), '2', '/span', ' | ', @@ -1522,7 +1522,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->numbers(array('first' => 1, 'tag' => 'li', 'currentClass' => 'active', 'currentTag' => 'a')); $expected = array( - array('li' => array()), array('a' => array('href' => '/index/page:1')), '1', '/a', '/li', + array('li' => array()), array('a' => array('href' => '/')), '1', '/a', '/li', ' | ', array('li' => array('class' => 'active')), array('a' => array()), '2', '/a', '/li', ' | ', @@ -1544,7 +1544,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->numbers(array('first' => 1, 'class' => 'page-link', 'currentClass' => 'active')); $expected = array( - array('span' => array('class' => 'page-link')), array('a' => array('href' => '/index/page:1')), '1', '/a', '/span', + array('span' => array('class' => 'page-link')), array('a' => array('href' => '/')), '1', '/a', '/span', ' | ', array('span' => array('class' => 'active page-link')), '2', '/span', ' | ', @@ -1566,7 +1566,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->numbers(array('last' => 1)); $expected = array( - array('span' => array()), array('a' => array('href' => '/index/page:1')), '1', '/a', '/span', + array('span' => array()), array('a' => array('href' => '/')), '1', '/a', '/span', ' | ', array('span' => array('class' => 'current')), '2', '/span', ' | ', @@ -1603,7 +1603,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->numbers(array('first' => 1)); $expected = array( - array('span' => array()), array('a' => array('href' => '/index/page:1')), '1', '/a', '/span', + array('span' => array()), array('a' => array('href' => '/')), '1', '/a', '/span', '...', array('span' => array()), array('a' => array('href' => '/index/page:7')), '7', '/a', '/span', ' | ', @@ -1643,7 +1643,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->numbers(array('first' => 1, 'last' => 1)); $expected = array( - array('span' => array()), array('a' => array('href' => '/index/page:1')), '1', '/a', '/span', + array('span' => array()), array('a' => array('href' => '/')), '1', '/a', '/span', '...', array('span' => array()), array('a' => array('href' => '/index/page:6')), '6', '/a', '/span', ' | ', @@ -1684,7 +1684,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->numbers(array('first' => 1, 'last' => 1)); $expected = array( - array('span' => array()), array('a' => array('href' => '/index/page:1')), '1', '/a', '/span', + array('span' => array()), array('a' => array('href' => '/')), '1', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:2')), '2', '/a', '/span', ' | ', @@ -1725,7 +1725,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->numbers(array('first' => 1, 'last' => 1)); $expected = array( - array('span' => array()), array('a' => array('href' => '/index/page:1')), '1', '/a', '/span', + array('span' => array()), array('a' => array('href' => '/')), '1', '/a', '/span', '...', array('span' => array()), array('a' => array('href' => '/index/page:33')), '33', '/a', '/span', ' | ', @@ -1801,7 +1801,7 @@ class PaginatorHelperTest extends CakeTestCase { ); $result = $this->Paginator->numbers(array('class' => 'page-link')); $expected = array( - array('span' => array('class' => 'page-link')), array('a' => array('href' => '/index/page:1/sort:Client.name/direction:DESC')), '1', '/a', '/span', + array('span' => array('class' => 'page-link')), array('a' => array('href' => '/index/sort:Client.name/direction:DESC')), '1', '/a', '/span', ' | ', array('span' => array('class' => 'current page-link')), '2', '/span', ' | ', @@ -1828,7 +1828,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->numbers(array('first' => 2, 'modulus' => 2, 'last' => 2)); $expected = array( - array('span' => array()), array('a' => array('href' => '/index/page:1')), '1', '/a', '/span', + array('span' => array()), array('a' => array('href' => '/')), '1', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:2')), '2', '/a', '/span', '...', @@ -1846,7 +1846,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->numbers(array('first' => 2, 'modulus' => 2, 'last' => 2)); $expected = array( - array('span' => array()), array('a' => array('href' => '/index/page:1')), '1', '/a', '/span', + array('span' => array()), array('a' => array('href' => '/')), '1', '/a', '/span', ' | ', array('span' => array()), array('a' => array('href' => '/index/page:2')), '2', '/a', '/span', ' | ', @@ -1862,7 +1862,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->numbers(array('first' => 2, 'modulus' => 2, 'last' => 2, 'separator' => ' - ')); $expected = array( - array('span' => array()), array('a' => array('href' => '/index/page:1')), '1', '/a', '/span', + array('span' => array()), array('a' => array('href' => '/')), '1', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:2')), '2', '/a', '/span', ' - ', @@ -1878,7 +1878,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->numbers(array('first' => 5, 'modulus' => 5, 'last' => 5, 'separator' => ' - ')); $expected = array( - array('span' => array()), array('a' => array('href' => '/index/page:1')), '1', '/a', '/span', + array('span' => array()), array('a' => array('href' => '/')), '1', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:2')), '2', '/a', '/span', ' - ', @@ -1905,7 +1905,7 @@ class PaginatorHelperTest extends CakeTestCase { $this->Paginator->request->params['paging']['Client']['page'] = 4893; $result = $this->Paginator->numbers(array('first' => 5, 'modulus' => 4, 'last' => 5, 'separator' => ' - ')); $expected = array( - array('span' => array()), array('a' => array('href' => '/index/page:1')), '1', '/a', '/span', + array('span' => array()), array('a' => array('href' => '/')), '1', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:2')), '2', '/a', '/span', ' - ', @@ -1934,7 +1934,7 @@ class PaginatorHelperTest extends CakeTestCase { $this->Paginator->request->params['paging']['Client']['page'] = 58; $result = $this->Paginator->numbers(array('first' => 5, 'modulus' => 4, 'last' => 5, 'separator' => ' - ')); $expected = array( - array('span' => array()), array('a' => array('href' => '/index/page:1')), '1', '/a', '/span', + array('span' => array()), array('a' => array('href' => '/')), '1', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:2')), '2', '/a', '/span', ' - ', @@ -1969,7 +1969,7 @@ class PaginatorHelperTest extends CakeTestCase { $this->Paginator->request->params['paging']['Client']['page'] = 5; $result = $this->Paginator->numbers(array('first' => 5, 'modulus' => 4, 'last' => 5, 'separator' => ' - ')); $expected = array( - array('span' => array()), array('a' => array('href' => '/index/page:1')), '1', '/a', '/span', + array('span' => array()), array('a' => array('href' => '/')), '1', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:2')), '2', '/a', '/span', ' - ', @@ -1998,7 +1998,7 @@ class PaginatorHelperTest extends CakeTestCase { $this->Paginator->request->params['paging']['Client']['page'] = 3; $result = $this->Paginator->numbers(array('first' => 2, 'modulus' => 2, 'last' => 2, 'separator' => ' - ', 'ellipsis' => ' ~~~ ')); $expected = array( - array('span' => array()), array('a' => array('href' => '/index/page:1')), '1', '/a', '/span', + array('span' => array()), array('a' => array('href' => '/')), '1', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:2')), '2', '/a', '/span', ' - ', @@ -2015,7 +2015,7 @@ class PaginatorHelperTest extends CakeTestCase { $this->Paginator->request->params['paging']['Client']['page'] = 3; $result = $this->Paginator->numbers(array('first' => 2, 'modulus' => 2, 'last' => 2, 'separator' => ' - ', 'ellipsis' => '...')); $expected = array( - array('span' => array()), array('a' => array('href' => '/index/page:1')), '1', '/a', '/span', + array('span' => array()), array('a' => array('href' => '/')), '1', '/a', '/span', ' - ', array('span' => array()), array('a' => array('href' => '/index/page:2')), '2', '/a', '/span', ' - ', @@ -2028,6 +2028,78 @@ class PaginatorHelperTest extends CakeTestCase { array('span' => array()), array('a' => array('href' => '/index/page:4897')), '4897', '/a', '/span', ); $this->assertTags($result, $expected); + + $this->Paginator->request->params['paging'] = array( + 'Client' => array( + 'page' => 2, + 'current' => 2, + 'count' => 30, + 'prevPage' => false, + 'nextPage' => 3, + 'pageCount' => 3, + 'options' => array( + 'page' => 1, + ), + 'paramType' => 'named' + ) + ); + + $request = new CakeRequest(); + $request->addParams(array( + 'controller' => 'clients', 'action' => 'index', 'plugin' => null, 'page' => 2 + )); + $request->base = ''; + $request->here = '/clients/index/page:2'; + $request->webroot = '/'; + + Router::setRequestInfo($request); + + $result = $this->Paginator->numbers(); + $expected = array( + array('span' => array()), array('a' => array('href' => '/clients')), '1', '/a', '/span', + ' | ', + array('span' => array('class' => 'current')), '2', '/span', + ' | ', + array('span' => array()), array('a' => array('href' => '/clients/index/page:3')), '3', '/a', '/span', + + ); + $this->assertTags($result, $expected); + + $this->Paginator->request->params['paging'] = array( + 'Client' => array( + 'page' => 2, + 'current' => 2, + 'count' => 30, + 'prevPage' => false, + 'nextPage' => 3, + 'pageCount' => 3, + 'options' => array( + 'page' => 1, + ), + 'paramType' => 'querystring' + ) + ); + + $request = new CakeRequest(); + $request->addParams(array( + 'controller' => 'clients', 'action' => 'index', 'plugin' => null + )); + $request->base = ''; + $request->here = '/clients?page=2'; + $request->webroot = '/'; + + Router::setRequestInfo($request); + + $result = $this->Paginator->numbers(); + $expected = array( + array('span' => array()), array('a' => array('href' => '/clients')), '1', '/a', '/span', + ' | ', + array('span' => array('class' => 'current')), '2', '/span', + ' | ', + array('span' => array()), array('a' => array('href' => '/clients?page=3')), '3', '/a', '/span', + + ); + $this->assertTags($result, $expected); } /** @@ -2039,7 +2111,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->first('<<', array('tag' => 'li', 'class' => 'first')); $expected = array( 'li' => array('class' => 'first'), - 'a' => array('href' => '/index/page:1', 'rel' => 'first'), + 'a' => array('href' => '/', 'rel' => 'first'), '<<', '/a', '/li' @@ -2101,7 +2173,7 @@ class PaginatorHelperTest extends CakeTestCase { $expected = array( ' array( - 'href' => FULL_BASE_URL . '/index/page:1/sort:Article.title/direction:DESC', 'rel' => 'first' + 'href' => FULL_BASE_URL . '/index/sort:Article.title/direction:DESC', 'rel' => 'first' )), '<< first', '/a', @@ -2119,7 +2191,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->first(); $expected = array( ' array('href' => '/index/page:1', 'rel' => 'first'), + 'a' => array('href' => '/', 'rel' => 'first'), '<< first', '/a', '/span' @@ -2129,7 +2201,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->first(2); $expected = array( ' array('href' => '/index/page:1')), '1', '/a', + array('a' => array('href' => '/')), '1', '/a', '/span', ' | ', 'Paginator->params(); + $this->assertArrayHasKey('page', $result); + $this->assertArrayHasKey('pageCount', $result); + } + +/** + * test param() method + * + * @return void + */ + public function testParam() { + $result = $this->Paginator->param('count'); + $this->assertIdentical(62, $result); + + $result = $this->Paginator->param('imaginary'); + $this->assertNull($result); + } + +/** + * test last() method * * @return void */ @@ -2483,6 +2579,14 @@ class PaginatorHelperTest extends CakeTestCase { */ public function testQuerystringUrlGeneration() { $this->Paginator->request->params['paging']['Article']['paramType'] = 'querystring'; + $result = $this->Paginator->url(array('page' => '1')); + $expected = '/'; + $this->assertEquals($expected, $result); + + $result = $this->Paginator->url(array('page' => '1', 'limit' => 10, 'something' => 'else')); + $expected = '/index/something:else?limit=10'; + $this->assertEquals($expected, $result); + $result = $this->Paginator->url(array('page' => '4')); $expected = '/?page=4'; $this->assertEquals($expected, $result); @@ -2516,7 +2620,7 @@ class PaginatorHelperTest extends CakeTestCase { $result = $this->Paginator->prev('Prev'); $expected = array( 'span' => array('class' => 'prev'), - 'a' => array('href' => '/?page=1', 'rel' => 'prev'), + 'a' => array('href' => '/', 'rel' => 'prev'), 'Prev', '/a', '/span' diff --git a/lib/Cake/Test/Case/View/Helper/TextHelperTest.php b/lib/Cake/Test/Case/View/Helper/TextHelperTest.php index 5b2ec873d..887f84372 100644 --- a/lib/Cake/Test/Case/View/Helper/TextHelperTest.php +++ b/lib/Cake/Test/Case/View/Helper/TextHelperTest.php @@ -364,4 +364,58 @@ class TextHelperTest extends CakeTestCase { $this->assertEquals($expected, $result); } +/** + * testAutoParagraph method + * + * @return void + */ + public function testAutoParagraph() { + $text = 'This is a test text'; + $expected = <<This is a test text

+ +TEXT; + $result = $this->Text->autoParagraph($text); + $text = 'This is a

test text'; + $expected = <<This is a

+

test text

+ +TEXT; + $result = $this->Text->autoParagraph($text); + $this->assertEquals($expected, $result); + $result = $this->Text->autoParagraph($text); + $text = 'This is a

test text'; + $expected = <<This is a

+

test text

+ +TEXT; + $result = $this->Text->autoParagraph($text); + $this->assertEquals($expected, $result); + $text = <<This is a test text.
+This is a line return.

+ +TEXT; + $result = $this->Text->autoParagraph($text); + $this->assertEquals($expected, $result); + $text = <<This is a test text.

+

This is a new line.

+ +TEXT; + $result = $this->Text->autoParagraph($text); + $this->assertEquals($expected, $result); + } + } diff --git a/lib/Cake/Test/Case/View/Helper/TimeHelperTest.php b/lib/Cake/Test/Case/View/Helper/TimeHelperTest.php index 3fb7c13ef..248317f20 100644 --- a/lib/Cake/Test/Case/View/Helper/TimeHelperTest.php +++ b/lib/Cake/Test/Case/View/Helper/TimeHelperTest.php @@ -87,7 +87,7 @@ class TimeHelperTest extends CakeTestCase { 'nice', 'niceShort', 'daysAsSql', 'dayAsSql', 'isToday', 'isThisMonth', 'isThisYear', 'wasYesterday', 'isTomorrow', 'toQuarter', 'toUnix', 'toAtom', 'toRSS', - 'timeAgoInWords', 'wasWithinLast', 'gmt', 'format', 'i18nFormat', + 'wasWithinLast', 'gmt', 'format', 'i18nFormat', ); $CakeTime = $this->getMock('CakeTimeMock', $methods); $Time = new TimeHelperTestObject($this->View, array('engine' => 'CakeTimeMock')); @@ -96,6 +96,12 @@ class TimeHelperTest extends CakeTestCase { $CakeTime->expects($this->at(0))->method($method); $Time->{$method}('who', 'what', 'when', 'where', 'how'); } + + $CakeTime = $this->getMock('CakeTimeMock', array('timeAgoInWords')); + $Time = new TimeHelperTestObject($this->View, array('engine' => 'CakeTimeMock')); + $Time->attach($CakeTime); + $CakeTime->expects($this->at(0))->method('timeAgoInWords'); + $Time->timeAgoInWords('who', array('what'), array('when'), array('where'), array('how')); } /** diff --git a/lib/Cake/Test/Case/View/HelperTest.php b/lib/Cake/Test/Case/View/HelperTest.php index 2a0d4c0a1..0f9b8b7f8 100644 --- a/lib/Cake/Test/Case/View/HelperTest.php +++ b/lib/Cake/Test/Case/View/HelperTest.php @@ -600,8 +600,8 @@ class HelperTest extends CakeTestCase { public function testAssetTimestamp() { Configure::write('Foo.bar', 'test'); Configure::write('Asset.timestamp', false); - $result = $this->Helper->assetTimestamp(CSS_URL . 'cake.generic.css'); - $this->assertEquals(CSS_URL . 'cake.generic.css', $result); + $result = $this->Helper->assetTimestamp(Configure::read('App.cssBaseUrl') . 'cake.generic.css'); + $this->assertEquals(Configure::read('App.cssBaseUrl') . 'cake.generic.css', $result); Configure::write('Asset.timestamp', true); Configure::write('debug', 0); @@ -609,25 +609,25 @@ class HelperTest extends CakeTestCase { $result = $this->Helper->assetTimestamp('/%3Cb%3E/cake.generic.css'); $this->assertEquals('/%3Cb%3E/cake.generic.css', $result); - $result = $this->Helper->assetTimestamp(CSS_URL . 'cake.generic.css'); - $this->assertEquals(CSS_URL . 'cake.generic.css', $result); + $result = $this->Helper->assetTimestamp(Configure::read('App.cssBaseUrl') . 'cake.generic.css'); + $this->assertEquals(Configure::read('App.cssBaseUrl') . 'cake.generic.css', $result); Configure::write('Asset.timestamp', true); Configure::write('debug', 2); - $result = $this->Helper->assetTimestamp(CSS_URL . 'cake.generic.css'); - $this->assertRegExp('/' . preg_quote(CSS_URL . 'cake.generic.css?', '/') . '[0-9]+/', $result); + $result = $this->Helper->assetTimestamp(Configure::read('App.cssBaseUrl') . 'cake.generic.css'); + $this->assertRegExp('/' . preg_quote(Configure::read('App.cssBaseUrl') . 'cake.generic.css?', '/') . '[0-9]+/', $result); Configure::write('Asset.timestamp', 'force'); Configure::write('debug', 0); - $result = $this->Helper->assetTimestamp(CSS_URL . 'cake.generic.css'); - $this->assertRegExp('/' . preg_quote(CSS_URL . 'cake.generic.css?', '/') . '[0-9]+/', $result); + $result = $this->Helper->assetTimestamp(Configure::read('App.cssBaseUrl') . 'cake.generic.css'); + $this->assertRegExp('/' . preg_quote(Configure::read('App.cssBaseUrl') . 'cake.generic.css?', '/') . '[0-9]+/', $result); - $result = $this->Helper->assetTimestamp(CSS_URL . 'cake.generic.css?someparam'); - $this->assertEquals(CSS_URL . 'cake.generic.css?someparam', $result); + $result = $this->Helper->assetTimestamp(Configure::read('App.cssBaseUrl') . 'cake.generic.css?someparam'); + $this->assertEquals(Configure::read('App.cssBaseUrl') . 'cake.generic.css?someparam', $result); $this->Helper->request->webroot = '/some/dir/'; - $result = $this->Helper->assetTimestamp('/some/dir/' . CSS_URL . 'cake.generic.css'); - $this->assertRegExp('/' . preg_quote(CSS_URL . 'cake.generic.css?', '/') . '[0-9]+/', $result); + $result = $this->Helper->assetTimestamp('/some/dir/' . Configure::read('App.cssBaseUrl') . 'cake.generic.css'); + $this->assertRegExp('/' . preg_quote(Configure::read('App.cssBaseUrl') . 'cake.generic.css?', '/') . '[0-9]+/', $result); } /** @@ -644,13 +644,13 @@ class HelperTest extends CakeTestCase { ), array('fullBase' => true) ); - $this->assertEquals(FULL_BASE_URL . '/js/post.js', $result); + $this->assertEquals(Router::fullBaseUrl() . '/js/post.js', $result); $result = $this->Helper->assetUrl('foo.jpg', array('pathPrefix' => 'img/')); $this->assertEquals('img/foo.jpg', $result); $result = $this->Helper->assetUrl('foo.jpg', array('fullBase' => true)); - $this->assertEquals(FULL_BASE_URL . '/foo.jpg', $result); + $this->assertEquals(Router::fullBaseUrl() . '/foo.jpg', $result); $result = $this->Helper->assetUrl('style', array('ext' => '.css')); $this->assertEquals('style.css', $result); @@ -660,6 +660,9 @@ class HelperTest extends CakeTestCase { $result = $this->Helper->assetUrl('foo.jpg?one=two&three=four'); $this->assertEquals('foo.jpg?one=two&three=four', $result); + + $result = $this->Helper->assetUrl('dir/big+tall/image', array('ext' => '.jpg')); + $this->assertEquals('dir/big%2Btall/image.jpg', $result); } /** @@ -705,8 +708,8 @@ class HelperTest extends CakeTestCase { $this->Helper->webroot = ''; Configure::write('Asset.timestamp', 'force'); - $result = $this->Helper->assetUrl('cake.generic.css', array('pathPrefix' => CSS_URL)); - $this->assertRegExp('/' . preg_quote(CSS_URL . 'cake.generic.css?', '/') . '[0-9]+/', $result); + $result = $this->Helper->assetUrl('cake.generic.css', array('pathPrefix' => Configure::read('App.cssBaseUrl'))); + $this->assertRegExp('/' . preg_quote(Configure::read('App.cssBaseUrl') . 'cake.generic.css?', '/') . '[0-9]+/', $result); } /** @@ -972,7 +975,7 @@ class HelperTest extends CakeTestCase { $Helper->OtherHelper; $result = $this->View->Helpers->enabled(); - $expected = array(); + $expected = array('Html'); $this->assertEquals($expected, $result, 'Helper helpers were attached to the collection.'); } diff --git a/lib/Cake/Test/Case/View/JsonViewTest.php b/lib/Cake/Test/Case/View/JsonViewTest.php index 9c99b1c67..0f82f68a4 100644 --- a/lib/Cake/Test/Case/View/JsonViewTest.php +++ b/lib/Cake/Test/Case/View/JsonViewTest.php @@ -30,6 +30,11 @@ App::uses('JsonView', 'View'); */ class JsonViewTest extends CakeTestCase { + public function setUp() { + parent::setUp(); + Configure::write('debug', 0); + } + /** * testRenderWithoutView method * @@ -48,6 +53,25 @@ class JsonViewTest extends CakeTestCase { $this->assertSame('application/json', $Response->type()); } +/** + * Test that rendering with _serialize does not load helpers + * + * @return void + */ + public function testRenderSerializeNoHelpers() { + $Request = new CakeRequest(); + $Response = new CakeResponse(); + $Controller = new Controller($Request, $Response); + $Controller->helpers = array('Html'); + $Controller->set(array( + '_serialize' => 'tags', + 'tags' => array('cakephp', 'framework') + )); + $View = new JsonView($Controller); + $View->render(); + $this->assertFalse(isset($View->Html), 'No helper loaded.'); + } + /** * Test render with an array in _serialize * @@ -67,6 +91,55 @@ class JsonViewTest extends CakeTestCase { $this->assertSame('application/json', $Response->type()); } +/** + * Test render with an array in _serialize and alias + * + * @return void + */ + public function testRenderWithoutViewMultipleAndAlias() { + $Request = new CakeRequest(); + $Response = new CakeResponse(); + $Controller = new Controller($Request, $Response); + $data = array('original_name' => 'my epic name', 'user' => 'fake', 'list' => array('item1', 'item2')); + $Controller->set($data); + $Controller->set('_serialize', array('new_name' => 'original_name', 'user')); + $View = new JsonView($Controller); + $output = $View->render(false); + + $this->assertSame(json_encode(array('new_name' => $data['original_name'], 'user' => $data['user'])), $output); + $this->assertSame('application/json', $Response->type()); + } + +/** + * testJsonpResponse method + * + * @return void + */ + public function testJsonpResponse() { + $Request = new CakeRequest(); + $Response = new CakeResponse(); + $Controller = new Controller($Request, $Response); + $data = array('user' => 'fake', 'list' => array('item1', 'item2')); + $Controller->set(array('data' => $data, '_serialize' => 'data', '_jsonp' => true)); + $View = new JsonView($Controller); + $output = $View->render(false); + + $this->assertSame(json_encode($data), $output); + $this->assertSame('application/json', $Response->type()); + + $View->request->query = array('callback' => 'jfunc'); + $output = $View->render(false); + $expected = 'jfunc(' . json_encode($data) . ')'; + $this->assertSame($expected, $output); + $this->assertSame('application/javascript', $Response->type()); + + $View->request->query = array('jsonCallback' => 'jfunc'); + $View->viewVars['_jsonp'] = 'jsonCallback'; + $output = $View->render(false); + $expected = 'jfunc(' . json_encode($data) . ')'; + $this->assertSame($expected, $output); + } + /** * testRenderWithView method * @@ -92,13 +165,23 @@ class JsonViewTest extends CakeTestCase { ) ); $Controller->set('user', $data); + $Controller->helpers = array('Paginator'); + $View = new JsonView($Controller); + $output = $View->render('index'); + + $expected = array('user' => 'fake', 'list' => array('item1', 'item2'), 'paging' => array('page' => 2)); + $this->assertSame(json_encode($expected), $output); + $this->assertSame('application/json', $Response->type()); + + $View->request->query = array('jsonCallback' => 'jfunc'); + $Controller->set('_jsonp', 'jsonCallback'); $View = new JsonView($Controller); $View->helpers = array('Paginator'); $output = $View->render('index'); - - $expected = json_encode(array('user' => 'fake', 'list' => array('item1', 'item2'), 'paging' => array('page' => 2))); + $expected['paging']['?']['jsonCallback'] = 'jfunc'; + $expected = 'jfunc(' . json_encode($expected) . ')'; $this->assertSame($expected, $output); - $this->assertSame('application/json', $Response->type()); + $this->assertSame('application/javascript', $Response->type()); } } diff --git a/lib/Cake/Test/Case/View/ViewTest.php b/lib/Cake/Test/Case/View/ViewTest.php index 0a05a32b8..0a5d15e93 100644 --- a/lib/Cake/Test/Case/View/ViewTest.php +++ b/lib/Cake/Test/Case/View/ViewTest.php @@ -219,6 +219,27 @@ class TestAfterHelper extends Helper { } +/** + * Class TestObjectWithToString + * + * An object with the magic method __toString() for testing with view blocks. + */ +class TestObjectWithToString { + + public function __toString() { + return "I'm ObjectWithToString"; + } + +} + +/** + * Class TestObjectWithoutToString + * + * An object without the magic method __toString() for testing with view blocks. + */ +class TestObjectWithoutToString { +} + /** * ViewTest class * @@ -282,7 +303,7 @@ class ViewTest extends CakeTestCase { } /** - * testGetTemplate method + * Test getViewFileName method * * @return void */ @@ -320,7 +341,7 @@ class ViewTest extends CakeTestCase { } /** - * testPluginGetTemplate method + * Test getLayoutFileName method on plugin * * @return void */ @@ -342,7 +363,7 @@ class ViewTest extends CakeTestCase { } /** - * testPluginGetTemplate method + * Test getViewFileName method on plugin * * @return void */ @@ -368,7 +389,7 @@ class ViewTest extends CakeTestCase { } /** - * test that plugin/$plugin_name is only appended to the paths it should be. + * Test that plugin/$plugin_name is only appended to the paths it should be. * * @return void */ @@ -396,7 +417,7 @@ class ViewTest extends CakeTestCase { } /** - * test that CamelCase plugins still find their view files. + * Test that CamelCase'd plugins still find their view files. * * @return void */ @@ -423,7 +444,7 @@ class ViewTest extends CakeTestCase { } /** - * testGetTemplate method + * Test getViewFileName method * * @return void */ @@ -516,7 +537,7 @@ class ViewTest extends CakeTestCase { } /** - * testMissingView method + * Test for missing views * * @expectedException MissingViewException * @return void @@ -545,7 +566,7 @@ class ViewTest extends CakeTestCase { } /** - * testMissingLayout method + * Test for missing layouts * * @expectedException MissingLayoutException * @return void @@ -572,7 +593,7 @@ class ViewTest extends CakeTestCase { } /** - * testViewVars method + * Test viewVars method * * @return void */ @@ -581,7 +602,7 @@ class ViewTest extends CakeTestCase { } /** - * testUUIDGeneration method + * Test generation of UUIDs method * * @return void */ @@ -595,7 +616,7 @@ class ViewTest extends CakeTestCase { } /** - * testAddInlineScripts method + * Test addInlineScripts method * * @return void */ @@ -610,7 +631,7 @@ class ViewTest extends CakeTestCase { } /** - * testElementExists method + * Test elementExists method * * @return void */ @@ -633,7 +654,7 @@ class ViewTest extends CakeTestCase { } /** - * testElement method + * Test element method * * @return void */ @@ -656,7 +677,7 @@ class ViewTest extends CakeTestCase { } /** - * testElementInexistent method + * Test elementInexistent method * * @expectedException PHPUnit_Framework_Error_Notice * @return void @@ -666,7 +687,7 @@ class ViewTest extends CakeTestCase { } /** - * testElementInexistent2 method + * Test elementInexistent2 method * * @expectedException PHPUnit_Framework_Error_Notice * @return void @@ -676,7 +697,7 @@ class ViewTest extends CakeTestCase { } /** - * testElementInexistent3 method + * Test elementInexistent3 method * * @expectedException PHPUnit_Framework_Error_Notice * @return void @@ -686,8 +707,7 @@ class ViewTest extends CakeTestCase { } /** - * test that elements can have callbacks - * + * Test that elements can have callbacks */ public function testElementCallbacks() { $this->getMock('Helper', array(), array($this->View), 'ElementCallbackMockHtmlHelper'); @@ -702,7 +722,7 @@ class ViewTest extends CakeTestCase { } /** - * test that additional element viewVars don't get overwritten with helpers. + * Test that additional element viewVars don't get overwritten with helpers. * * @return void */ @@ -720,7 +740,7 @@ class ViewTest extends CakeTestCase { } /** - * testElementCacheHelperNoCache method + * Test elementCacheHelperNoCache method * * @return void */ @@ -733,7 +753,7 @@ class ViewTest extends CakeTestCase { } /** - * testElementCache method + * Test elementCache method * * @return void */ @@ -787,7 +807,7 @@ class ViewTest extends CakeTestCase { } /** - * test __get allowing access to helpers. + * Test __get allowing access to helpers. * * @return void */ @@ -798,7 +818,7 @@ class ViewTest extends CakeTestCase { } /** - * test that ctp is used as a fallback file extension for elements + * Test that ctp is used as a fallback file extension for elements * * @return void */ @@ -813,7 +833,7 @@ class ViewTest extends CakeTestCase { } /** - * testLoadHelpers method + * Test loadHelpers method * * @return void */ @@ -828,7 +848,7 @@ class ViewTest extends CakeTestCase { } /** - * test lazy loading helpers + * Test lazy loading helpers * * @return void */ @@ -841,7 +861,7 @@ class ViewTest extends CakeTestCase { } /** - * test the correct triggering of helper callbacks + * Test the correct triggering of helper callbacks * * @return void */ @@ -924,7 +944,7 @@ class ViewTest extends CakeTestCase { } /** - * testBeforeLayout method + * Test beforeLayout method * * @return void */ @@ -936,7 +956,7 @@ class ViewTest extends CakeTestCase { } /** - * testAfterLayout method + * Test afterLayout method * * @return void */ @@ -954,7 +974,7 @@ class ViewTest extends CakeTestCase { } /** - * testRenderLoadHelper method + * Test renderLoadHelper method * * @return void */ @@ -980,7 +1000,7 @@ class ViewTest extends CakeTestCase { } /** - * testRender method + * Test render method * * @return void */ @@ -1023,7 +1043,7 @@ class ViewTest extends CakeTestCase { } /** - * test that View::$view works + * Test that View::$view works * * @return void */ @@ -1054,7 +1074,7 @@ class ViewTest extends CakeTestCase { } /** - * test that view vars can replace the local helper variables + * Test that view vars can replace the local helper variables * and not overwrite the $this->Helper references * * @return void @@ -1071,7 +1091,7 @@ class ViewTest extends CakeTestCase { } /** - * testGetViewFileName method + * Test getViewFileName method * * @return void */ @@ -1099,7 +1119,7 @@ class ViewTest extends CakeTestCase { } /** - * testRenderCache method + * Test renderCache method * * @return void */ @@ -1323,29 +1343,124 @@ class ViewTest extends CakeTestCase { } /** - * Test appending to a block with append. + * Test setting a block's content to null * * @return void + * @link https://cakephp.lighthouseapp.com/projects/42648/tickets/3938-this-redirectthis-auth-redirecturl-broken */ - public function testBlockAppend() { - $this->View->assign('test', 'Block'); - $this->View->append('test', ' content'); - - $result = $this->View->fetch('test'); - $this->assertEquals('Block content', $result); + public function testBlockSetNull() { + $this->View->assign('testWithNull', null); + $result = $this->View->fetch('testWithNull'); + $this->assertSame('', $result); } /** - * Test prepending to a block with append. + * Test setting a block's content to an object with __toString magic method + * + * @return void + */ + public function testBlockSetObjectWithToString() { + $objectWithToString = new TestObjectWithToString(); + $this->View->assign('testWithObjectWithToString', $objectWithToString); + $result = $this->View->fetch('testWithObjectWithToString'); + $this->assertSame("I'm ObjectWithToString", $result); + } + +/** + * Test setting a block's content to an object without __toString magic method + * + * This should produce a "Object of class TestObjectWithoutToString could not be converted to string" error + * which gets thrown as a PHPUnit_Framework_Error Exception by PHPUnit. + * + * @expectedException PHPUnit_Framework_Error + * @return void + */ + public function testBlockSetObjectWithoutToString() { + $objectWithToString = new TestObjectWithoutToString(); + $this->View->assign('testWithObjectWithoutToString', $objectWithToString); + } + +/** + * Test setting a block's content to a decimal * * @return void */ - public function testBlockPrepend() { + public function testBlockSetDecimal() { + $this->View->assign('testWithDecimal', 1.23456789); + $result = $this->View->fetch('testWithDecimal'); + $this->assertEqual('1.23456789', $result); + } + +/** + * Data provider for block related tests. + * + * @return array + */ + public static function blockValueProvider() { + return array( + 'string' => array('A string value'), + 'null' => array(null), + 'decimal' => array(1.23456), + 'object with __toString' => array(new TestObjectWithToString()), + ); + } + +/** + * Test appending to a block with append. + * + * @dataProvider blockValueProvider + * @return void + */ + public function testBlockAppend($value) { + $this->View->assign('testBlock', 'Block'); + $this->View->append('testBlock', $value); + + $result = $this->View->fetch('testBlock'); + $this->assertSame('Block' . $value, $result); + } + + +/** + * Test appending an object without __toString magic method to a block with append. + * + * This should produce a "Object of class TestObjectWithoutToString could not be converted to string" error + * which gets thrown as a PHPUnit_Framework_Error Exception by PHPUnit. + * + * @expectedException PHPUnit_Framework_Error + * @return void + */ + public function testBlockAppendObjectWithoutToString() { + $object = new TestObjectWithoutToString(); + $this->View->assign('testBlock', 'Block '); + $this->View->append('testBlock', $object); + } + +/** + * Test prepending to a block with prepend. + * + * @dataProvider blockValueProvider + * @return void + */ + public function testBlockPrepend($value) { $this->View->assign('test', 'Block'); - $this->View->prepend('test', 'Before '); + $this->View->prepend('test', $value); $result = $this->View->fetch('test'); - $this->assertEquals('Before Block', $result); + $this->assertEquals($value . 'Block', $result); + } +/** + * Test prepending an object without __toString magic method to a block with prepend. + * + * This should produce a "Object of class TestObjectWithoutToString could not be converted to string" error + * which gets thrown as a PHPUnit_Framework_Error Exception by PHPUnit. + * + * @expectedException PHPUnit_Framework_Error + * @return void + */ + public function testBlockPrependObjectWithoutToString() { + $object = new TestObjectWithoutToString(); + $this->View->assign('test', 'Block '); + $this->View->prepend('test', $object); } /** @@ -1370,26 +1485,6 @@ class ViewTest extends CakeTestCase { $this->assertEquals('Unknown', $result); } -/** - * setting an array should cause an exception. - * - * @expectedException CakeException - * @return void - */ - public function testBlockSetArrayException() { - $this->View->assign('test', array(1, 2, 3)); - } - -/** - * Appending an array should cause an exception. - * - * @expectedException CakeException - * @return void - */ - public function testBlockAppendArrayException() { - $this->View->append('test', array(1, 2, 3)); - } - /** * Test getting block names * @@ -1542,7 +1637,7 @@ TEXT; } /** - * test memory leaks that existed in _paths at one point. + * Test memory leaks that existed in _paths at one point. * * @return void */ @@ -1565,7 +1660,7 @@ TEXT; } /** - * tests that a vew block uses default value when not assigned and uses assigned value when it is + * Tests that a vew block uses default value when not assigned and uses assigned value when it is * * @return void */ diff --git a/lib/Cake/Test/Case/View/XmlViewTest.php b/lib/Cake/Test/Case/View/XmlViewTest.php index bf6bf3dcb..e48b0e325 100644 --- a/lib/Cake/Test/Case/View/XmlViewTest.php +++ b/lib/Cake/Test/Case/View/XmlViewTest.php @@ -30,6 +30,11 @@ App::uses('XmlView', 'View'); */ class XmlViewTest extends CakeTestCase { + public function setUp() { + parent::setUp(); + Configure::write('debug', 0); + } + /** * testRenderWithoutView method * @@ -74,6 +79,25 @@ class XmlViewTest extends CakeTestCase { $this->assertSame($expected, $output); } +/** + * Test that rendering with _serialize does not load helpers + * + * @return void + */ + public function testRenderSerializeNoHelpers() { + $Request = new CakeRequest(); + $Response = new CakeResponse(); + $Controller = new Controller($Request, $Response); + $Controller->helpers = array('Html'); + $Controller->set(array( + '_serialize' => 'tags', + 'tags' => array('cakephp', 'framework') + )); + $View = new XmlView($Controller); + $View->render(); + $this->assertFalse(isset($View->Html), 'No helper loaded.'); + } + /** * Test render with an array in _serialize * @@ -103,6 +127,35 @@ class XmlViewTest extends CakeTestCase { $this->assertSame(Xml::build($expected)->asXML(), $output); } +/** + * Test render with an array in _serialize and alias + * + * @return void + */ + public function testRenderWithoutViewMultipleAndAlias() { + $Request = new CakeRequest(); + $Response = new CakeResponse(); + $Controller = new Controller($Request, $Response); + $data = array('original_name' => 'my epic name', 'user' => 'fake', 'list' => array('item1', 'item2')); + $Controller->set($data); + $Controller->set('_serialize', array('new_name' => 'original_name', 'user')); + $View = new XmlView($Controller); + $this->assertSame('application/xml', $Response->type()); + $output = $View->render(false); + $expected = array( + 'response' => array('new_name' => $data['original_name'], 'user' => $data['user']) + ); + $this->assertSame(Xml::build($expected)->asXML(), $output); + + $Controller->set('_rootNode', 'custom_name'); + $View = new XmlView($Controller); + $output = $View->render(false); + $expected = array( + 'custom_name' => array('new_name' => $data['original_name'], 'user' => $data['user']) + ); + $this->assertSame(Xml::build($expected)->asXML(), $output); + } + /** * testRenderWithView method * diff --git a/lib/Cake/Test/test_app/Controller/PagesController.php b/lib/Cake/Test/test_app/Controller/PagesController.php index 98b2ed779..5b2a03453 100644 --- a/lib/Cake/Test/test_app/Controller/PagesController.php +++ b/lib/Cake/Test/test_app/Controller/PagesController.php @@ -51,6 +51,8 @@ class PagesController extends AppController { * * @param mixed What page to display * @return void + * @throws NotFoundException When the view file could not be found + * or MissingViewException in debug mode. */ public function display() { $path = func_get_args(); @@ -75,7 +77,15 @@ class PagesController extends AppController { 'subpage' => $subpage, 'title_for_layout' => $titleForLayout )); - $this->render(implode('/', $path)); + + try { + $this->render(implode('/', $path)); + } catch (MissingViewException $e) { + if (Configure::read('debug')) { + throw $e; + } + throw new NotFoundException(); + } } } diff --git a/lib/Cake/Test/test_app/Controller/TestConfigsController.php b/lib/Cake/Test/test_app/Controller/TestConfigsController.php new file mode 100644 index 000000000..aa8400ea9 --- /dev/null +++ b/lib/Cake/Test/test_app/Controller/TestConfigsController.php @@ -0,0 +1,13 @@ + array( + 'some' => 'config' + ) + ); + +} diff --git a/lib/Cake/TestSuite/CakeTestCase.php b/lib/Cake/TestSuite/CakeTestCase.php index 9073cbf2d..55a0d0b4c 100644 --- a/lib/Cake/TestSuite/CakeTestCase.php +++ b/lib/Cake/TestSuite/CakeTestCase.php @@ -689,17 +689,21 @@ abstract class CakeTestCase extends PHPUnit_Framework_TestCase { * * @param string $model * @param mixed $methods - * @param mixed $config + * @param array $config + * @throws MissingModelException * @return Model */ - public function getMockForModel($model, $methods = array(), $config = null) { - if ($config === null) { - $config = ClassRegistry::config('Model'); - } + public function getMockForModel($model, $methods = array(), $config = array()) { + $config += ClassRegistry::config('Model'); list($plugin, $name) = pluginSplit($model, true); App::uses($name, $plugin . 'Model'); $config = array_merge((array)$config, array('name' => $name)); + + if (!class_exists($name)) { + throw new MissingModelException(array($model)); + } + $mock = $this->getMock($name, $methods, array($config)); ClassRegistry::removeObject($name); ClassRegistry::addObject($name, $mock); diff --git a/lib/Cake/TestSuite/ControllerTestCase.php b/lib/Cake/TestSuite/ControllerTestCase.php index 28227a437..7aaff1b00 100644 --- a/lib/Cake/TestSuite/ControllerTestCase.php +++ b/lib/Cake/TestSuite/ControllerTestCase.php @@ -364,7 +364,8 @@ abstract class ControllerTestCase extends CakeTestCase { 'class' => $componentClass )); } - $componentObj = $this->getMock($componentClass, $methods, array($controllerObj->Components)); + $config = isset($controllerObj->components[$component]) ? $controllerObj->components[$component] : array(); + $componentObj = $this->getMock($componentClass, $methods, array($controllerObj->Components, $config)); $controllerObj->Components->set($name, $componentObj); $controllerObj->Components->enable($name); } diff --git a/lib/Cake/TestSuite/templates/header.php b/lib/Cake/TestSuite/templates/header.php index b637612cc..1ec4ea52f 100644 --- a/lib/Cake/TestSuite/templates/header.php +++ b/lib/Cake/TestSuite/templates/header.php @@ -22,7 +22,7 @@ - CakePHP Test Suite 2.3 + CakePHP Test Suite <?php echo Configure::version(); ?>