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 ##########
'
+
+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