Merge remote-tracking branch 'origin/2.5' into k-halaburda-master

This commit is contained in:
Jose Lorenzo Rodriguez 2013-10-12 01:05:02 +02:00
commit df549898ad
45 changed files with 2654 additions and 225 deletions

View file

@ -11,6 +11,9 @@ env:
- DB=pgsql
- DB=sqlite
services:
- memcached
matrix:
include:
- php: 5.4
@ -29,6 +32,7 @@ before_script:
- sudo apt-get install lighttpd
- sh -c "if [ '$PHPCS' = '1' ]; then pear channel-discover pear.cakephp.org; fi"
- sh -c "if [ '$PHPCS' = '1' ]; then pear install --alldeps cakephp/CakePHP_CodeSniffer; fi"
- echo "extension = memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
- phpenv rehash
- set +H
- echo "<?php

View file

@ -309,18 +309,20 @@
* 'password' => 'password', //plaintext password (xcache.admin.pass)
* ));
*
* Memcache (http://www.danga.com/memcached/)
* Memcached (http://www.danga.com/memcached/)
*
* Uses the memcached extension. See http://php.net/memcached
*
* Cache::config('default', array(
* 'engine' => 'Memcache', //[required]
* 'engine' => 'Memcached', //[required]
* 'duration' => 3600, //[optional]
* 'probability' => 100, //[optional]
* 'prefix' => Inflector::slug(APP_DIR) . '_', //[optional] prefix every cache file with this string
* 'servers' => array(
* '127.0.0.1:11211' // localhost, default port 11211
* ), //[optional]
* 'persistent' => true, // [optional] set this to false for non-persistent connections
* 'compress' => false, // [optional] compress data in Memcache (slower, but uses less memory)
* 'persistent' => 'my_connection', // [optional] The name of the persistent connection.
* 'compress' => false, // [optional] compress data in Memcached (slower, but uses less memory)
* ));
*
* Wincache (http://php.net/wincache)

View file

@ -542,4 +542,38 @@ class Cache {
throw new CacheException(__d('cake_dev', 'Invalid cache group %s', $group));
}
/**
* Provides the ability to easily do read-through caching.
*
* When called if the $key is not set in $config, the $callable function
* will be invoked. The results will then be stored into the cache config
* at key.
*
* Examples:
*
* Using a Closure to provide data, assume $this is a Model:
*
* {{{
* $model = $this;
* $results = Cache::remember('all_articles', function() use ($model) {
* return $model->find('all');
* });
* }}}
*
* @param string $key The cache key to read/store data at.
* @param callable $callable The callable that provides data in the case when
* the cache key is empty. Can be any callable type supported by your PHP.
* @param string $config The cache configuration to use for this operation.
* Defaults to default.
*/
public static function remember($key, $callable, $config = 'default') {
$existing = self::read($key, $config);
if ($existing !== false) {
return $existing;
}
$results = call_user_func($callable);
self::write($key, $results, $config);
return $results;
}
}

View file

@ -24,7 +24,8 @@
* control you have over expire times far in the future. See MemcacheEngine::write() for
* more information.
*
* @package Cake.Cache.Engine
* @package Cake.Cache.Engine
* @deprecated You should use the Memcached adapter instead.
*/
class MemcacheEngine extends CacheEngine {

View file

@ -0,0 +1,317 @@
<?php
/**
* 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.5.0
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
/**
* Memcached storage engine for cache. Memcached has some limitations in the amount of
* control you have over expire times far in the future. See MemcachedEngine::write() for
* more information.
*
* Main advantage of this Memcached engine over the memcached engine is
* support of binary protocol, and igbibnary serialization
* (if memcached extension compiled with --enable-igbinary)
* Compressed keys can also be incremented/decremented
*
* @package Cake.Cache.Engine
*/
class MemcachedEngine extends CacheEngine {
/**
* memcached wrapper.
*
* @var Memcache
*/
protected $_Memcached = null;
/**
* Settings
*
* - servers = string or array of memcached servers, default => 127.0.0.1. If an
* array MemcacheEngine will use them as a pool.
* - compress = boolean, default => false
* - persistent = string The name of the persistent connection. All configurations using
* the same persistent value will share a single underlying connection.
* - serialize = string, default => php. The serializer engine used to serialize data.
* Available engines are php, igbinary and json. Beside php, the memcached extension
* must be compiled with the appropriate serializer support.
*
* @var array
*/
public $settings = array();
/**
* List of available serializer engines
*
* Memcached must be compiled with json and igbinary support to use these engines
*
* @var array
*/
protected $_serializers = array(
'igbinary' => Memcached::SERIALIZER_IGBINARY,
'json' => Memcached::SERIALIZER_JSON,
'php' => Memcached::SERIALIZER_PHP
);
/**
* Initialize the Cache Engine
*
* Called automatically by the cache frontend
* To reinitialize the settings call Cache::engine('EngineName', [optional] settings = array());
*
* @param array $settings array of setting for the engine
* @return boolean True if the engine has been successfully initialized, false if not
* @throws CacheException when you try use authentication without Memcached compiled with SASL support
*/
public function init($settings = array()) {
if (!class_exists('Memcached')) {
return false;
}
if (!isset($settings['prefix'])) {
$settings['prefix'] = Inflector::slug(APP_DIR) . '_';
}
$settings += array(
'engine' => 'Memcached',
'servers' => array('127.0.0.1'),
'compress' => false,
'persistent' => false,
'login' => null,
'password' => null,
'serialize' => 'php'
);
parent::init($settings);
if (!is_array($this->settings['servers'])) {
$this->settings['servers'] = array($this->settings['servers']);
}
if (isset($this->_Memcached)) {
return true;
}
$this->_Memcached = new Memcached($this->settings['persistent'] ? (string)$this->settings['persistent'] : null);
$this->_setOptions();
if (count($this->_Memcached->getServerList())) {
return true;
}
$servers = array();
foreach ($this->settings['servers'] as $server) {
$servers[] = $this->_parseServerString($server);
}
if (!$this->_Memcached->addServers($servers)) {
return false;
}
if ($this->settings['login'] !== null && $this->settings['password'] !== null) {
if (!method_exists($this->_Memcached, 'setSaslAuthData')) {
throw new CacheException(
__d('cake_dev', 'Memcached extension is not build with SASL support')
);
}
$this->_Memcached->setSaslAuthData($this->settings['login'], $this->settings['password']);
}
return true;
}
/**
* Settings the memcached instance
*
* @throws CacheException when the Memcached extension is not built with the desired serializer engine
*/
protected function _setOptions() {
$this->_Memcached->setOption(Memcached::OPT_LIBKETAMA_COMPATIBLE, true);
$serializer = strtolower($this->settings['serialize']);
if (!isset($this->_serializers[$serializer])) {
throw new CacheException(
__d('cake_dev', '%s is not a valid serializer engine for Memcached', $serializer)
);
}
if ($serializer !== 'php' && !constant('Memcached::HAVE_' . strtoupper($serializer))) {
throw new CacheException(
__d('cake_dev', 'Memcached extension is not compiled with %s support', $serializer)
);
}
$this->_Memcached->setOption(Memcached::OPT_SERIALIZER, $this->_serializers[$serializer]);
// Check for Amazon ElastiCache instance
if (defined('Memcached::OPT_CLIENT_MODE') && defined('Memcached::DYNAMIC_CLIENT_MODE')) {
$this->_Memcached->setOption(Memcached::OPT_CLIENT_MODE, Memcached::DYNAMIC_CLIENT_MODE);
}
$this->_Memcached->setOption(Memcached::OPT_COMPRESSION, (bool)$this->settings['compress']);
}
/**
* Parses the server address into the host/port. Handles both IPv6 and IPv4
* addresses and Unix sockets
*
* @param string $server The server address string.
* @return array Array containing host, port
*/
protected function _parseServerString($server) {
if ($server[0] === 'u') {
return array($server, 0);
}
if (substr($server, 0, 1) === '[') {
$position = strpos($server, ']:');
if ($position !== false) {
$position++;
}
} else {
$position = strpos($server, ':');
}
$port = 11211;
$host = $server;
if ($position !== false) {
$host = substr($server, 0, $position);
$port = substr($server, $position + 1);
}
return array($host, (int)$port);
}
/**
* Write data for key into cache. When using memcached as your cache engine
* remember that the Memcached pecl extension does not support cache expiry times greater
* than 30 days in the future. Any duration greater than 30 days will be treated as never expiring.
*
* @param string $key Identifier for the data
* @param mixed $value Data to be cached
* @param integer $duration How long to cache the data, in seconds
* @return boolean True if the data was successfully cached, false on failure
* @see http://php.net/manual/en/memcache.set.php
*/
public function write($key, $value, $duration) {
if ($duration > 30 * DAY) {
$duration = 0;
}
return $this->_Memcached->set($key, $value, $duration);
}
/**
* Read a key from the cache
*
* @param string $key Identifier for the data
* @return mixed The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it
*/
public function read($key) {
return $this->_Memcached->get($key);
}
/**
* Increments the value of an integer cached key
*
* @param string $key Identifier for the data
* @param integer $offset How much to increment
* @return New incremented value, false otherwise
* @throws CacheException when you try to increment with compress = true
*/
public function increment($key, $offset = 1) {
return $this->_Memcached->increment($key, $offset);
}
/**
* Decrements the value of an integer cached key
*
* @param string $key Identifier for the data
* @param integer $offset How much to subtract
* @return New decremented value, false otherwise
* @throws CacheException when you try to decrement with compress = true
*/
public function decrement($key, $offset = 1) {
return $this->_Memcached->decrement($key, $offset);
}
/**
* Delete a key from the cache
*
* @param string $key Identifier for the data
* @return boolean True if the value was successfully deleted, false if it didn't exist or couldn't be removed
*/
public function delete($key) {
return $this->_Memcached->delete($key);
}
/**
* Delete all keys from the cache
*
* @param boolean $check
* @return boolean True if the cache was successfully cleared, false otherwise
*/
public function clear($check) {
if ($check) {
return true;
}
$keys = $this->_Memcached->getAllKeys();
foreach ($keys as $key) {
if (strpos($key, $this->settings['prefix']) === 0) {
$this->_Memcached->delete($key);
}
}
return true;
}
/**
* Returns the `group value` for each of the configured groups
* If the group initial value was not found, then it initializes
* the group accordingly.
*
* @return array
*/
public function groups() {
if (empty($this->_compiledGroupNames)) {
foreach ($this->settings['groups'] as $group) {
$this->_compiledGroupNames[] = $this->settings['prefix'] . $group;
}
}
$groups = $this->_Memcached->getMulti($this->_compiledGroupNames);
if (count($groups) !== count($this->settings['groups'])) {
foreach ($this->_compiledGroupNames as $group) {
if (!isset($groups[$group])) {
$this->_Memcached->set($group, 1, 0);
$groups[$group] = 1;
}
}
ksort($groups);
}
$result = array();
$groups = array_values($groups);
foreach ($this->settings['groups'] as $i => $group) {
$result[] = $group . $groups[$i];
}
return $result;
}
/**
* Increments the group value to simulate deletion of all keys under a group
* old values will remain in storage until they expire.
*
* @return boolean success
*/
public function clearGroup($group) {
return (bool)$this->_Memcached->increment($this->settings['prefix'] . $group);
}
}

View file

@ -24,6 +24,13 @@ App::uses('Inflector', 'Utility');
*/
class CommandListShell extends AppShell {
/**
* Contains tasks to load and instantiate
*
* @var array
*/
public $tasks = array('Command');
/**
* startup
*
@ -55,7 +62,7 @@ class CommandListShell extends AppShell {
$this->out(__d('cake_console', "<info>Available Shells:</info>"), 2);
}
$shellList = $this->_getShellList();
$shellList = $this->Command->getShellList();
if (empty($shellList)) {
return;
}
@ -67,48 +74,6 @@ class CommandListShell extends AppShell {
}
}
/**
* Gets the shell command listing.
*
* @return array
*/
protected function _getShellList() {
$skipFiles = array('AppShell');
$plugins = CakePlugin::loaded();
$shellList = array_fill_keys($plugins, null) + array('CORE' => null, 'app' => null);
$corePath = App::core('Console/Command');
$shells = App::objects('file', $corePath[0]);
$shells = array_diff($shells, $skipFiles);
$this->_appendShells('CORE', $shells, $shellList);
$appShells = App::objects('Console/Command', null, false);
$appShells = array_diff($appShells, $shells, $skipFiles);
$this->_appendShells('app', $appShells, $shellList);
foreach ($plugins as $plugin) {
$pluginShells = App::objects($plugin . '.Console/Command');
$this->_appendShells($plugin, $pluginShells, $shellList);
}
return array_filter($shellList);
}
/**
* Scan the provided paths for shells, and append them into $shellList
*
* @param string $type
* @param array $shells
* @param array $shellList
* @return void
*/
protected function _appendShells($type, $shells, &$shellList) {
foreach ($shells as $shell) {
$shellList[$type][] = Inflector::underscore(str_replace('Shell', '', $shell));
}
}
/**
* Output text.
*

View file

@ -0,0 +1,155 @@
<?php
/**
* 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 Project
* @package Cake.Console.Command
* @since CakePHP v 2.5
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
App::uses('AppShell', 'Console/Command');
/**
* Provide command completion shells such as bash.
*
* @package Cake.Console.Command
*/
class CompletionShell extends AppShell {
/**
* Contains tasks to load and instantiate
*
* @var array
*/
public $tasks = array('Command');
/**
* Echo no header by overriding the startup method
*
* @return void
*/
public function startup() {
}
/**
* Not called by the autocomplete shell - this is for curious users
*
* @return void
*/
public function main() {
return $this->out($this->getOptionParser()->help());
}
/**
* list commands
*
* @return void
*/
public function commands() {
$options = $this->Command->commands();
return $this->_output($options);
}
/**
* list options for the named command
*
* @return void
*/
public function options() {
$commandName = '';
if (!empty($this->args[0])) {
$commandName = $this->args[0];
}
$options = $this->Command->options($commandName);
return $this->_output($options);
}
/**
* list subcommands for the named command
*
* @return void
*/
public function subCommands() {
if (!$this->args) {
return $this->_output();
}
$options = $this->Command->subCommands($this->args[0]);
return $this->_output($options);
}
/**
* Guess autocomplete from the whole argument string
*
* @return void
*/
public function fuzzy() {
return $this->_output();
}
/**
* getOptionParser for _this_ shell
*
* @return ConsoleOptionParser
*/
public function getOptionParser() {
$parser = parent::getOptionParser();
$parser->description(__d('cake_console', 'Used by shells like bash to autocomplete command name, options and arguments'))
->addSubcommand('commands', array(
'help' => __d('cake_console', 'Output a list of available commands'),
'parser' => array(
'description' => __d('cake_console', 'List all availables'),
'arguments' => array(
)
)
))->addSubcommand('subcommands', array(
'help' => __d('cake_console', 'Output a list of available subcommands'),
'parser' => array(
'description' => __d('cake_console', 'List subcommands for a command'),
'arguments' => array(
'command' => array(
'help' => __d('cake_console', 'The command name'),
'required' => true,
)
)
)
))->addSubcommand('options', array(
'help' => __d('cake_console', 'Output a list of available options'),
'parser' => array(
'description' => __d('cake_console', 'List options'),
'arguments' => array(
'command' => array(
'help' => __d('cake_console', 'The command name'),
'required' => false,
)
)
)
))->epilog(
array(
__d('cake_console', 'This command is not intended to be called manually'),
)
);
return $parser;
}
/**
* Emit results as a string, space delimited
*
* @param array $options
* @return void
*/
protected function _output($options = array()) {
if ($options) {
return $this->out(implode($options, ' '));
}
}
}

View file

@ -333,7 +333,10 @@ class SchemaShell extends AppShell {
$this->out("\n" . __d('cake_console', 'The following table(s) will be dropped.'));
$this->out(array_keys($drop));
if ($this->in(__d('cake_console', 'Are you sure you want to drop the table(s)?'), array('y', 'n'), 'n') === 'y') {
if (
!empty($this->params['yes']) ||
$this->in(__d('cake_console', 'Are you sure you want to drop the table(s)?'), array('y', 'n'), 'n') === 'y'
) {
$this->out(__d('cake_console', 'Dropping table(s).'));
$this->_run($drop, 'drop', $Schema);
}
@ -341,7 +344,10 @@ class SchemaShell extends AppShell {
$this->out("\n" . __d('cake_console', 'The following table(s) will be created.'));
$this->out(array_keys($create));
if ($this->in(__d('cake_console', 'Are you sure you want to create the table(s)?'), array('y', 'n'), 'y') === 'y') {
if (
!empty($this->params['yes']) ||
$this->in(__d('cake_console', 'Are you sure you want to create the table(s)?'), array('y', 'n'), 'y') === 'y'
) {
$this->out(__d('cake_console', 'Creating table(s).'));
$this->_run($create, 'create', $Schema);
}
@ -392,7 +398,10 @@ class SchemaShell extends AppShell {
$this->out("\n" . __d('cake_console', 'The following statements will run.'));
$this->out(array_map('trim', $contents));
if ($this->in(__d('cake_console', 'Are you sure you want to alter the tables?'), array('y', 'n'), 'n') === 'y') {
if (
!empty($this->params['yes']) ||
$this->in(__d('cake_console', 'Are you sure you want to alter the tables?'), array('y', 'n'), 'n') === 'y'
) {
$this->out();
$this->out(__d('cake_console', 'Updating Database...'));
$this->_run($contents, 'update', $Schema);
@ -471,7 +480,9 @@ class SchemaShell extends AppShell {
'default' => 'schema.php'
);
$name = array(
'help' => __d('cake_console', 'Classname to use. If its Plugin.class, both name and plugin options will be set.')
'help' => __d('cake_console',
'Classname to use. If its Plugin.class, both name and plugin options will be set.'
)
);
$snapshot = array(
'short' => 's',
@ -482,7 +493,9 @@ class SchemaShell extends AppShell {
'help' => __d('cake_console', 'Specify models as comma separated list.'),
);
$dry = array(
'help' => __d('cake_console', 'Perform a dry run on create and update commands. Queries will be output instead of run.'),
'help' => __d('cake_console',
'Perform a dry run on create and update commands. Queries will be output instead of run.'
),
'boolean' => true
);
$force = array(
@ -496,10 +509,17 @@ class SchemaShell extends AppShell {
$exclude = array(
'help' => __d('cake_console', 'Tables to exclude as comma separated list.')
);
$yes = array(
'short' => 'y',
'help' => __d('cake_console', 'Do not prompt for confirmation. Be careful!'),
'boolean' => true
);
$parser = parent::getOptionParser();
$parser->description(
__d('cake_console', 'The Schema Shell generates a schema object from the database and updates the database from the schema.')
__d('cake_console',
'The Schema Shell generates a schema object from the database and updates the database from the schema.'
)
)->addSubcommand('view', array(
'help' => __d('cake_console', 'Read and output the contents of a schema file'),
'parser' => array(
@ -523,7 +543,7 @@ class SchemaShell extends AppShell {
))->addSubcommand('create', array(
'help' => __d('cake_console', 'Drop and create tables based on the schema file.'),
'parser' => array(
'options' => compact('plugin', 'path', 'file', 'name', 'connection', 'dry', 'snapshot'),
'options' => compact('plugin', 'path', 'file', 'name', 'connection', 'dry', 'snapshot', 'yes'),
'args' => array(
'name' => array(
'help' => __d('cake_console', 'Name of schema to use.')
@ -536,7 +556,7 @@ class SchemaShell extends AppShell {
))->addSubcommand('update', array(
'help' => __d('cake_console', 'Alter the tables based on the schema file.'),
'parser' => array(
'options' => compact('plugin', 'path', 'file', 'name', 'connection', 'dry', 'snapshot', 'force'),
'options' => compact('plugin', 'path', 'file', 'name', 'connection', 'dry', 'snapshot', 'force', 'yes'),
'args' => array(
'name' => array(
'help' => __d('cake_console', 'Name of schema to use.')

View file

@ -0,0 +1,183 @@
<?php
/**
* 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.5
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
App::uses('AppShell', 'Console/Command');
/**
* Base class for Shell Command reflection.
*
* @package Cake.Console.Command.Task
*/
class CommandTask extends AppShell {
/**
* Gets the shell command listing.
*
* @return array
*/
public function getShellList() {
$skipFiles = array('AppShell');
$plugins = CakePlugin::loaded();
$shellList = array_fill_keys($plugins, null) + array('CORE' => null, 'app' => null);
$corePath = App::core('Console/Command');
$shells = App::objects('file', $corePath[0]);
$shells = array_diff($shells, $skipFiles);
$this->_appendShells('CORE', $shells, $shellList);
$appShells = App::objects('Console/Command', null, false);
$appShells = array_diff($appShells, $shells, $skipFiles);
$this->_appendShells('app', $appShells, $shellList);
foreach ($plugins as $plugin) {
$pluginShells = App::objects($plugin . '.Console/Command');
$this->_appendShells($plugin, $pluginShells, $shellList);
}
return array_filter($shellList);
}
/**
* Scan the provided paths for shells, and append them into $shellList
*
* @param string $type
* @param array $shells
* @param array $shellList
* @return void
*/
protected function _appendShells($type, $shells, &$shellList) {
foreach ($shells as $shell) {
$shellList[$type][] = Inflector::underscore(str_replace('Shell', '', $shell));
}
}
/**
* Return a list of all commands
*
* @return array
*/
public function commands() {
$shellList = $this->getShellList();
$options = array();
foreach ($shellList as $type => $commands) {
$prefix = '';
if (!in_array(strtolower($type), array('app', 'core'))) {
$prefix = $type . '.';
}
foreach ($commands as $shell) {
$options[] = $prefix . $shell;
}
}
return $options;
}
/**
* Return a list of subcommands for a given command
*
* @param string $commandName
* @return array
*/
public function subCommands($commandName) {
$Shell = $this->getShell($commandName);
if (!$Shell) {
return array();
}
$taskMap = TaskCollection::normalizeObjectArray((array)$Shell->tasks);
$return = array_keys($taskMap);
$return = array_map('Inflector::underscore', $return);
$ShellReflection = new ReflectionClass('AppShell');
$shellMethods = $ShellReflection->getMethods(ReflectionMethod::IS_PUBLIC);
$shellMethodNames = array('main', 'help');
foreach ($shellMethods as $method) {
$shellMethodNames[] = $method->getName();
}
$Reflection = new ReflectionClass($Shell);
$methods = $Reflection->getMethods(ReflectionMethod::IS_PUBLIC);
$methodNames = array();
foreach ($methods as $method) {
$methodNames[] = $method->getName();
}
$return += array_diff($methodNames, $shellMethodNames);
sort($return);
return $return;
}
/**
* Get Shell instance for the given command
*
* @param mixed $commandName
* @return mixed
*/
public function getShell($commandName) {
list($pluginDot, $name) = pluginSplit($commandName, true);
if (in_array(strtolower($pluginDot), array('app.', 'core.'))) {
$commandName = $name;
$pluginDot = '';
}
if (!in_array($commandName, $this->commands())) {
return false;
}
$name = Inflector::camelize($name);
$pluginDot = Inflector::camelize($pluginDot);
$class = $name . 'Shell';
APP::uses($class, $pluginDot . 'Console/Command');
$Shell = new $class();
$Shell->plugin = trim($pluginDot, '.');
$Shell->initialize();
return $Shell;
}
/**
* Get Shell instance for the given command
*
* @param mixed $commandName
* @return array
*/
public function options($commandName) {
$Shell = $this->getShell($commandName);
if (!$Shell) {
$parser = new ConsoleOptionParser();
} else {
$parser = $Shell->getOptionParser();
}
$options = array();
$array = $parser->options();
foreach ($array as $name => $obj) {
$options[] = "--$name";
$short = $obj->short();
if ($short) {
$options[] = "-$short";
}
}
return $options;
}
}

View file

@ -132,7 +132,9 @@ class CookieComponent extends Component {
* Type of encryption to use.
*
* Currently two methods are available: cipher and rijndael
* Defaults to Security::cipher();
* Defaults to Security::cipher(). Cipher is horribly insecure and only
* the default because of backwards compatibility. In new applications you should
* always change this to 'aes' or 'rijndael'.
*
* @var string
*/
@ -364,10 +366,11 @@ class CookieComponent extends Component {
public function type($type = 'cipher') {
$availableTypes = array(
'cipher',
'rijndael'
'rijndael',
'aes'
);
if (!in_array($type, $availableTypes)) {
trigger_error(__d('cake_dev', 'You must use cipher or rijndael for cookie encryption type'), E_USER_WARNING);
trigger_error(__d('cake_dev', 'You must use cipher, rijndael or aes for cookie encryption type'), E_USER_WARNING);
$type = 'cipher';
}
$this->_type = $type;
@ -455,12 +458,20 @@ class CookieComponent extends Component {
if (is_array($value)) {
$value = $this->_implode($value);
}
if ($this->_encrypted === true) {
$type = $this->_type;
$value = "Q2FrZQ==." . base64_encode(Security::$type($value, $this->key, 'encrypt'));
if (!$this->_encrypted) {
return $value;
}
return $value;
$prefix = "Q2FrZQ==.";
if ($this->_type === 'rijndael') {
$cipher = Security::rijndael($value, $this->key, 'encrypt');
}
if ($this->_type === 'cipher') {
$cipher = Security::cipher($value, $this->key);
}
if ($this->_type === 'aes') {
$cipher = Security::encrypt($value, $this->key);
}
return $prefix . base64_encode($cipher);
}
/**
@ -476,27 +487,40 @@ class CookieComponent extends Component {
foreach ((array)$values as $name => $value) {
if (is_array($value)) {
foreach ($value as $key => $val) {
$pos = strpos($val, 'Q2FrZQ==.');
$decrypted[$name][$key] = $this->_explode($val);
if ($pos !== false) {
$val = substr($val, 8);
$decrypted[$name][$key] = $this->_explode(Security::$type(base64_decode($val), $this->key, 'decrypt'));
}
$decrypted[$name][$key] = $this->_decode($val);
}
} else {
$pos = strpos($value, 'Q2FrZQ==.');
$decrypted[$name] = $this->_explode($value);
if ($pos !== false) {
$value = substr($value, 8);
$decrypted[$name] = $this->_explode(Security::$type(base64_decode($value), $this->key, 'decrypt'));
}
$decrypted[$name] = $this->_decode($value);
}
}
return $decrypted;
}
/**
* Decodes and decrypts a single value.
*
* @param string $value The value to decode & decrypt.
* @return string Decoded value.
*/
protected function _decode($value) {
$prefix = 'Q2FrZQ==.';
$pos = strpos($value, $prefix);
if ($pos === false) {
return $this->_explode($value);
}
$value = base64_decode(substr($value, strlen($prefix)));
if ($this->_type === 'rijndael') {
$plain = Security::rijndael($value, $this->key, 'decrypt');
}
if ($this->_type === 'cipher') {
$plain = Security::cipher($value, $this->key);
}
if ($this->_type === 'aes') {
$plain = Security::decrypt($value, $this->key);
}
return $this->_explode($plain);
}
/**
* Implode method to keep keys are multidimensional arrays
*

View file

@ -372,18 +372,6 @@ class CakeLog {
return false;
}
/**
* Configures the automatic/default stream a FileLog.
*
* @return void
*/
protected static function _autoConfig() {
self::$_Collection->load('default', array(
'engine' => 'File',
'path' => LOGS,
));
}
/**
* Writes the given message and type to all of the configured log adapters.
* Configured adapters are passed both the $type and $message variables. $type
@ -455,11 +443,7 @@ class CakeLog {
$logged = true;
}
}
if (!$logged) {
self::_autoConfig();
self::stream('default')->write($type, $message);
}
return true;
return $logged;
}
/**

View file

@ -682,7 +682,7 @@ class TreeBehavior extends ModelBehavior {
$children = $Model->find('all', $params);
$hasChildren = (bool)$children;
if (!is_null($parentId)) {
if ($parentId !== null) {
if ($hasChildren) {
$Model->updateAll(
array($this->settings[$Model->alias]['left'] => $counter),
@ -713,7 +713,7 @@ class TreeBehavior extends ModelBehavior {
$children = $Model->find('all', $params);
}
if (!is_null($parentId) && $hasChildren) {
if ($parentId !== null && $hasChildren) {
$Model->updateAll(
array($this->settings[$Model->alias]['right'] => $counter),
array($Model->escapeField() => $parentId)

View file

@ -1918,7 +1918,7 @@ class Model extends Object implements CakeEventListener {
}
foreach ((array)$data as $row) {
if ((is_string($row) && (strlen($row) == 36 || strlen($row) == 16)) || is_numeric($row)) {
if ((is_string($row) && (strlen($row) === 36 || strlen($row) === 16)) || is_numeric($row)) {
$newJoins[] = $row;
$values = array($id, $row);

View file

@ -249,7 +249,15 @@ class ModelValidator implements ArrayAccess, IteratorAggregate, Countable {
return $model->validationErrors;
}
$fieldList = isset($options['fieldList']) ? $options['fieldList'] : array();
$fieldList = $model->whitelist;
if (empty($fieldList) && !empty($options['fieldList'])) {
if (!empty($options['fieldList'][$model->alias]) && is_array($options['fieldList'][$model->alias])) {
$fieldList = $options['fieldList'][$model->alias];
} else {
$fieldList = $options['fieldList'];
}
}
$exists = $model->exists();
$methods = $this->getMethods();
$fields = $this->_validationList($fieldList);
@ -376,32 +384,19 @@ class ModelValidator implements ArrayAccess, IteratorAggregate, Countable {
}
/**
* Processes the Model's whitelist or passed fieldList and returns the list of fields
* to be validated
* Processes the passed fieldList and returns the list of fields to be validated
*
* @param array $fieldList list of fields to be used for validation
* @return array List of validation rules to be applied
*/
protected function _validationList($fieldList = array()) {
$model = $this->getModel();
$whitelist = $model->whitelist;
if (!empty($fieldList)) {
if (!empty($fieldList[$model->alias]) && is_array($fieldList[$model->alias])) {
$whitelist = $fieldList[$model->alias];
} else {
$whitelist = $fieldList;
}
}
unset($fieldList);
if (empty($whitelist) || Hash::dimensions($whitelist) > 1) {
if (empty($fieldList) || Hash::dimensions($fieldList) > 1) {
return $this->_fields;
}
$validateList = array();
$this->validationErrors = array();
foreach ((array)$whitelist as $f) {
foreach ((array)$fieldList as $f) {
if (!empty($this->_fields[$f])) {
$validateList[$f] = $this->_fields[$f];
}

View file

@ -516,8 +516,13 @@ class CakeRequest implements ArrayAccess {
}
if (isset($detect['param'])) {
$key = $detect['param'];
$value = $detect['value'];
return isset($this->params[$key]) ? $this->params[$key] == $value : false;
if (isset($detect['value'])) {
$value = $detect['value'];
return isset($this->params[$key]) ? $this->params[$key] == $value : false;
}
if (isset($detect['options'])) {
return isset($this->params[$key]) ? in_array($this->params[$key], $detect['options']) : false;
}
}
if (isset($detect['callback']) && is_callable($detect['callback'])) {
return call_user_func($detect['callback'], $this);
@ -576,7 +581,13 @@ class CakeRequest implements ArrayAccess {
*
* Allows for custom detectors on the request parameters.
*
* e.g `addDetector('post', array('param' => 'requested', 'value' => 1)`
* e.g `addDetector('requested', array('param' => 'requested', 'value' => 1)`
*
* You can also make parameter detectors that accept multiple values
* using the `options` key. This is useful when you want to check
* if a request parameter is in a list of options.
*
* `addDetector('extension', array('param' => 'ext', 'options' => array('pdf', 'csv'))`
*
* @param string $name The name of the detector.
* @param array $options The options for the detector definition. See above.

View file

@ -570,7 +570,7 @@ class CakeResponse {
if (is_numeric($header)) {
list($header, $value) = array($value, null);
}
if (is_null($value)) {
if ($value === null) {
list($header, $value) = explode(':', $header, 2);
}
$this->_headers[$header] = is_array($value) ? array_map('trim', $value) : trim($value);

View file

@ -500,11 +500,14 @@ class Router {
public static function mapResources($controller, $options = array()) {
$hasPrefix = isset($options['prefix']);
$options = array_merge(array(
'connectOptions' => array(),
'prefix' => '/',
'id' => self::ID . '|' . self::UUID
), $options);
$prefix = $options['prefix'];
$connectOptions = $options['connectOptions'];
unset($options['connectOptions']);
foreach ((array)$controller as $name) {
list($plugin, $name) = pluginSplit($name);
@ -524,7 +527,10 @@ class Router {
'action' => $params['action'],
'[method]' => $params['method']
),
array('id' => $options['id'], 'pass' => array('id'))
array_merge(
array('id' => $options['id'], 'pass' => array('id')),
$connectOptions
)
);
}
self::$_resourceMapped[] = $urlName;
@ -1035,7 +1041,7 @@ class Router {
}
$addition = http_build_query($q, null, $join);
if ($out && $addition && substr($out, strlen($join) * -1, strlen($join)) != $join) {
if ($out && $addition && substr($out, strlen($join) * -1, strlen($join)) !== $join) {
$out .= $join;
}

View file

@ -27,6 +27,8 @@ App::uses('Cache', 'Cache');
*/
class CacheTest extends CakeTestCase {
protected $_count = 0;
/**
* setUp method
*
@ -491,4 +493,28 @@ class CacheTest extends CakeTestCase {
$this->assertEquals('test_file_', $settings['prefix']);
$this->assertEquals(strtotime('+1 year') - time(), $settings['duration']);
}
/**
* test remember method.
*
* @return void
*/
public function testRemember() {
$expected = 'This is some data 0';
$result = Cache::remember('test_key', array($this, 'cacher'), 'default');
$this->assertEquals($expected, $result);
$this->_count = 1;
$result = Cache::remember('test_key', array($this, 'cacher'), 'default');
$this->assertEquals($expected, $result);
}
/**
* Method for testing Cache::remember()
*
* @return string
*/
public function cacher() {
return 'This is some data ' . $this->_count;
}
}

View file

@ -0,0 +1,728 @@
<?php
/**
* MemcachedEngineTest file
*
* PHP 5
*
* CakePHP(tm) Tests <http://book.cakephp.org/2.0/en/development/testing.html>
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests
* @package Cake.Test.Case.Cache.Engine
* @since CakePHP(tm) v 2.5.0
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
App::uses('Cache', 'Cache');
App::uses('MemcachedEngine', 'Cache/Engine');
/**
* Class TestMemcachedEngine
*
* @package Cake.Test.Case.Cache.Engine
*/
class TestMemcachedEngine extends MemcachedEngine {
/**
* public accessor to _parseServerString
*
* @param string $server
* @return array
*/
public function parseServerString($server) {
return $this->_parseServerString($server);
}
public function setMemcached($memcached) {
$this->_Memcached = $memcached;
}
public function getMemcached() {
return $this->_Memcached;
}
}
/**
* MemcachedEngineTest class
*
* @package Cake.Test.Case.Cache.Engine
*/
class MemcachedEngineTest extends CakeTestCase {
/**
* setUp method
*
* @return void
*/
public function setUp() {
parent::setUp();
$this->skipIf(!class_exists('Memcached'), 'Memcached is not installed or configured properly.');
Cache::config('memcached', array(
'engine' => 'Memcached',
'prefix' => 'cake_',
'duration' => 3600
));
}
/**
* tearDown method
*
* @return void
*/
public function tearDown() {
parent::tearDown();
Cache::drop('memcached');
Cache::drop('memcached_groups');
Cache::drop('memcached_helper');
Cache::config('default');
}
/**
* testSettings method
*
* @return void
*/
public function testSettings() {
$settings = Cache::settings('memcached');
unset($settings['path']);
$expecting = array(
'prefix' => 'cake_',
'duration' => 3600,
'probability' => 100,
'servers' => array('127.0.0.1'),
'persistent' => false,
'compress' => false,
'engine' => 'Memcached',
'login' => null,
'password' => null,
'groups' => array(),
'serialize' => 'php'
);
$this->assertEquals($expecting, $settings);
}
/**
* testCompressionSetting method
*
* @return void
*/
public function testCompressionSetting() {
$Memcached = new TestMemcachedEngine();
$Memcached->init(array(
'engine' => 'Memcached',
'servers' => array('127.0.0.1:11211'),
'compress' => false
));
$this->assertFalse($Memcached->getMemcached()->getOption(Memcached::OPT_COMPRESSION));
$MemcachedCompressed = new TestMemcachedEngine();
$MemcachedCompressed->init(array(
'engine' => 'Memcached',
'servers' => array('127.0.0.1:11211'),
'compress' => true
));
$this->assertTrue($MemcachedCompressed->getMemcached()->getOption(Memcached::OPT_COMPRESSION));
}
/**
* test accepts only valid serializer engine
*
* @return void
*/
public function testInvalidSerializerSetting() {
$Memcached = new TestMemcachedEngine();
$settings = array(
'engine' => 'Memcached',
'servers' => array('127.0.0.1:11211'),
'persistent' => false,
'serialize' => 'invalid_serializer'
);
$this->setExpectedException(
'CacheException', 'invalid_serializer is not a valid serializer engine for Memcached'
);
$Memcached->init($settings);
}
/**
* testPhpSerializerSetting method
*
* @return void
*/
public function testPhpSerializerSetting() {
$Memcached = new TestMemcachedEngine();
$settings = array(
'engine' => 'Memcached',
'servers' => array('127.0.0.1:11211'),
'persistent' => false,
'serialize' => 'php'
);
$Memcached->init($settings);
$this->assertEquals(Memcached::SERIALIZER_PHP, $Memcached->getMemcached()->getOption(Memcached::OPT_SERIALIZER));
}
/**
* testJsonSerializerSetting method
*
* @return void
*/
public function testJsonSerializerSetting() {
$this->skipIf(
!Memcached::HAVE_JSON,
'Memcached extension is not compiled with json support'
);
$Memcached = new TestMemcachedEngine();
$settings = array(
'engine' => 'Memcached',
'servers' => array('127.0.0.1:11211'),
'persistent' => false,
'serialize' => 'json'
);
$Memcached->init($settings);
$this->assertEquals(Memcached::SERIALIZER_JSON, $Memcached->getMemcached()->getOption(Memcached::OPT_SERIALIZER));
}
/**
* testIgbinarySerializerSetting method
*
* @return void
*/
public function testIgbinarySerializerSetting() {
$this->skipIf(
!Memcached::HAVE_IGBINARY,
'Memcached extension is not compiled with igbinary support'
);
$Memcached = new TestMemcachedEngine();
$settings = array(
'engine' => 'Memcached',
'servers' => array('127.0.0.1:11211'),
'persistent' => false,
'serialize' => 'igbinary'
);
$Memcached->init($settings);
$this->assertEquals(Memcached::SERIALIZER_IGBINARY, $Memcached->getMemcached()->getOption(Memcached::OPT_SERIALIZER));
}
/**
* testJsonSerializerThrowException method
*
* @return void
*/
public function testJsonSerializerThrowException() {
$this->skipIf(
Memcached::HAVE_JSON,
'Memcached extension is compiled with json support'
);
$Memcached = new TestMemcachedEngine();
$settings = array(
'engine' => 'Memcached',
'servers' => array('127.0.0.1:11211'),
'persistent' => false,
'serialize' => 'json'
);
$this->setExpectedException(
'CacheException', 'Memcached extension is not compiled with json support'
);
$Memcached->init($settings);
}
/**
* testIgbinarySerializerThrowException method
*
* @return void
*/
public function testIgbinarySerializerThrowException() {
$this->skipIf(
Memcached::HAVE_IGBINARY,
'Memcached extension is compiled with igbinary support'
);
$Memcached = new TestMemcachedEngine();
$settings = array(
'engine' => 'Memcached',
'servers' => array('127.0.0.1:11211'),
'persistent' => false,
'serialize' => 'igbinary'
);
$this->setExpectedException(
'CacheException', 'Memcached extension is not compiled with igbinary support'
);
$Memcached->init($settings);
}
/**
* test using authentication without memcached installed with SASL support
* throw an exception
*
* @return void
*/
public function testSaslAuthException() {
$Memcached = new TestMemcachedEngine();
$settings = array(
'engine' => 'Memcached',
'servers' => array('127.0.0.1:11211'),
'persistent' => false,
'login' => 'test',
'password' => 'password'
);
$this->skipIf(
method_exists($Memcached->getMemcached(), 'setSaslAuthData'),
'Memcached extension is installed with SASL support'
);
$this->setExpectedException(
'CacheException', 'Memcached extension is not build with SASL support'
);
$Memcached->init($settings);
}
/**
* testSettings method
*
* @return void
*/
public function testMultipleServers() {
$servers = array('127.0.0.1:11211', '127.0.0.1:11222');
$available = true;
$Memcached = new Memcached();
foreach ($servers as $server) {
list($host, $port) = explode(':', $server);
//@codingStandardsIgnoreStart
if (!$Memcached->addServer($host, $port)) {
$available = false;
}
//@codingStandardsIgnoreEnd
}
$this->skipIf(!$available, 'Need memcached servers at ' . implode(', ', $servers) . ' to run this test.');
$Memcached = new MemcachedEngine();
$Memcached->init(array('engine' => 'Memcached', 'servers' => $servers));
$settings = $Memcached->settings();
$this->assertEquals($settings['servers'], $servers);
Cache::drop('dual_server');
}
/**
* test connecting to an ipv6 server.
*
* @return void
*/
public function testConnectIpv6() {
$Memcached = new MemcachedEngine();
$result = $Memcached->init(array(
'prefix' => 'cake_',
'duration' => 200,
'engine' => 'Memcached',
'servers' => array(
'[::1]:11211'
)
));
$this->assertTrue($result);
}
/**
* test non latin domains.
*
* @return void
*/
public function testParseServerStringNonLatin() {
$Memcached = new TestMemcachedEngine();
$result = $Memcached->parseServerString('schülervz.net:13211');
$this->assertEquals(array('schülervz.net', '13211'), $result);
$result = $Memcached->parseServerString('sülül:1111');
$this->assertEquals(array('sülül', '1111'), $result);
}
/**
* test unix sockets.
*
* @return void
*/
public function testParseServerStringUnix() {
$Memcached = new TestMemcachedEngine();
$result = $Memcached->parseServerString('unix:///path/to/memcachedd.sock');
$this->assertEquals(array('unix:///path/to/memcachedd.sock', 0), $result);
}
/**
* testReadAndWriteCache method
*
* @return void
*/
public function testReadAndWriteCache() {
Cache::set(array('duration' => 1), null, 'memcached');
$result = Cache::read('test', 'memcached');
$expecting = '';
$this->assertEquals($expecting, $result);
$data = 'this is a test of the emergency broadcasting system';
$result = Cache::write('test', $data, 'memcached');
$this->assertTrue($result);
$result = Cache::read('test', 'memcached');
$expecting = $data;
$this->assertEquals($expecting, $result);
Cache::delete('test', 'memcached');
}
/**
* testExpiry method
*
* @return void
*/
public function testExpiry() {
Cache::set(array('duration' => 1), 'memcached');
$result = Cache::read('test', 'memcached');
$this->assertFalse($result);
$data = 'this is a test of the emergency broadcasting system';
$result = Cache::write('other_test', $data, 'memcached');
$this->assertTrue($result);
sleep(2);
$result = Cache::read('other_test', 'memcached');
$this->assertFalse($result);
Cache::set(array('duration' => "+1 second"), 'memcached');
$data = 'this is a test of the emergency broadcasting system';
$result = Cache::write('other_test', $data, 'memcached');
$this->assertTrue($result);
sleep(3);
$result = Cache::read('other_test', 'memcached');
$this->assertFalse($result);
Cache::config('memcached', array('duration' => '+1 second'));
$result = Cache::read('other_test', 'memcached');
$this->assertFalse($result);
Cache::config('memcached', array('duration' => '+29 days'));
$data = 'this is a test of the emergency broadcasting system';
$result = Cache::write('long_expiry_test', $data, 'memcached');
$this->assertTrue($result);
sleep(2);
$result = Cache::read('long_expiry_test', 'memcached');
$expecting = $data;
$this->assertEquals($expecting, $result);
Cache::config('memcached', array('duration' => 3600));
}
/**
* testDeleteCache method
*
* @return void
*/
public function testDeleteCache() {
$data = 'this is a test of the emergency broadcasting system';
$result = Cache::write('delete_test', $data, 'memcached');
$this->assertTrue($result);
$result = Cache::delete('delete_test', 'memcached');
$this->assertTrue($result);
}
/**
* testDecrement method
*
* @return void
*/
public function testDecrement() {
$result = Cache::write('test_decrement', 5, 'memcached');
$this->assertTrue($result);
$result = Cache::decrement('test_decrement', 1, 'memcached');
$this->assertEquals(4, $result);
$result = Cache::read('test_decrement', 'memcached');
$this->assertEquals(4, $result);
$result = Cache::decrement('test_decrement', 2, 'memcached');
$this->assertEquals(2, $result);
$result = Cache::read('test_decrement', 'memcached');
$this->assertEquals(2, $result);
Cache::delete('test_decrement', 'memcached');
}
/**
* test decrementing compressed keys
*
* @return void
*/
public function testDecrementCompressedKeys() {
Cache::config('compressed_memcached', array(
'engine' => 'Memcached',
'duration' => '+2 seconds',
'servers' => array('127.0.0.1:11211'),
'compress' => true
));
$result = Cache::write('test_decrement', 5, 'compressed_memcached');
$this->assertTrue($result);
$result = Cache::decrement('test_decrement', 1, 'compressed_memcached');
$this->assertEquals(4, $result);
$result = Cache::read('test_decrement', 'compressed_memcached');
$this->assertEquals(4, $result);
$result = Cache::decrement('test_decrement', 2, 'compressed_memcached');
$this->assertEquals(2, $result);
$result = Cache::read('test_decrement', 'compressed_memcached');
$this->assertEquals(2, $result);
Cache::delete('test_decrement', 'compressed_memcached');
}
/**
* testIncrement method
*
* @return void
*/
public function testIncrement() {
$result = Cache::write('test_increment', 5, 'memcached');
$this->assertTrue($result);
$result = Cache::increment('test_increment', 1, 'memcached');
$this->assertEquals(6, $result);
$result = Cache::read('test_increment', 'memcached');
$this->assertEquals(6, $result);
$result = Cache::increment('test_increment', 2, 'memcached');
$this->assertEquals(8, $result);
$result = Cache::read('test_increment', 'memcached');
$this->assertEquals(8, $result);
Cache::delete('test_increment', 'memcached');
}
/**
* test incrementing compressed keys
*
* @return void
*/
public function testIncrementCompressedKeys() {
Cache::config('compressed_memcached', array(
'engine' => 'Memcached',
'duration' => '+2 seconds',
'servers' => array('127.0.0.1:11211'),
'compress' => true
));
$result = Cache::write('test_increment', 5, 'compressed_memcached');
$this->assertTrue($result);
$result = Cache::increment('test_increment', 1, 'compressed_memcached');
$this->assertEquals(6, $result);
$result = Cache::read('test_increment', 'compressed_memcached');
$this->assertEquals(6, $result);
$result = Cache::increment('test_increment', 2, 'compressed_memcached');
$this->assertEquals(8, $result);
$result = Cache::read('test_increment', 'compressed_memcached');
$this->assertEquals(8, $result);
Cache::delete('test_increment', 'compressed_memcached');
}
/**
* test that configurations don't conflict, when a file engine is declared after a memcached one.
*
* @return void
*/
public function testConfigurationConflict() {
Cache::config('long_memcached', array(
'engine' => 'Memcached',
'duration' => '+2 seconds',
'servers' => array('127.0.0.1:11211'),
));
Cache::config('short_memcached', array(
'engine' => 'Memcached',
'duration' => '+1 seconds',
'servers' => array('127.0.0.1:11211'),
));
Cache::config('some_file', array('engine' => 'File'));
$this->assertTrue(Cache::write('duration_test', 'yay', 'long_memcached'));
$this->assertTrue(Cache::write('short_duration_test', 'boo', 'short_memcached'));
$this->assertEquals('yay', Cache::read('duration_test', 'long_memcached'), 'Value was not read %s');
$this->assertEquals('boo', Cache::read('short_duration_test', 'short_memcached'), 'Value was not read %s');
sleep(1);
$this->assertEquals('yay', Cache::read('duration_test', 'long_memcached'), 'Value was not read %s');
sleep(2);
$this->assertFalse(Cache::read('short_duration_test', 'short_memcached'), 'Cache was not invalidated %s');
$this->assertFalse(Cache::read('duration_test', 'long_memcached'), 'Value did not expire %s');
Cache::delete('duration_test', 'long_memcached');
Cache::delete('short_duration_test', 'short_memcached');
}
/**
* test clearing memcached.
*
* @return void
*/
public function testClear() {
Cache::config('memcached2', array(
'engine' => 'Memcached',
'prefix' => 'cake2_',
'duration' => 3600
));
Cache::write('some_value', 'cache1', 'memcached');
$result = Cache::clear(true, 'memcached');
$this->assertTrue($result);
$this->assertEquals('cache1', Cache::read('some_value', 'memcached'));
Cache::write('some_value', 'cache2', 'memcached2');
$result = Cache::clear(false, 'memcached');
$this->assertTrue($result);
$this->assertFalse(Cache::read('some_value', 'memcached'));
$this->assertEquals('cache2', Cache::read('some_value', 'memcached2'));
Cache::clear(false, 'memcached2');
}
/**
* test that a 0 duration can successfully write.
*
* @return void
*/
public function testZeroDuration() {
Cache::config('memcached', array('duration' => 0));
$result = Cache::write('test_key', 'written!', 'memcached');
$this->assertTrue($result);
$result = Cache::read('test_key', 'memcached');
$this->assertEquals('written!', $result);
}
/**
* test that durations greater than 30 days never expire
*
* @return void
*/
public function testLongDurationEqualToZero() {
$memcached = new TestMemcachedEngine();
$memcached->settings['compress'] = false;
$mock = $this->getMock('Memcached');
$memcached->setMemcached($mock);
$mock->expects($this->once())
->method('set')
->with('key', 'value', 0);
$value = 'value';
$memcached->write('key', $value, 50 * DAY);
}
/**
* Tests that configuring groups for stored keys return the correct values when read/written
* Shows that altering the group value is equivalent to deleting all keys under the same
* group
*
* @return void
*/
public function testGroupReadWrite() {
Cache::config('memcached_groups', array(
'engine' => 'Memcached',
'duration' => 3600,
'groups' => array('group_a', 'group_b'),
'prefix' => 'test_'
));
Cache::config('memcached_helper', array(
'engine' => 'Memcached',
'duration' => 3600,
'prefix' => 'test_'
));
$this->assertTrue(Cache::write('test_groups', 'value', 'memcached_groups'));
$this->assertEquals('value', Cache::read('test_groups', 'memcached_groups'));
Cache::increment('group_a', 1, 'memcached_helper');
$this->assertFalse(Cache::read('test_groups', 'memcached_groups'));
$this->assertTrue(Cache::write('test_groups', 'value2', 'memcached_groups'));
$this->assertEquals('value2', Cache::read('test_groups', 'memcached_groups'));
Cache::increment('group_b', 1, 'memcached_helper');
$this->assertFalse(Cache::read('test_groups', 'memcached_groups'));
$this->assertTrue(Cache::write('test_groups', 'value3', 'memcached_groups'));
$this->assertEquals('value3', Cache::read('test_groups', 'memcached_groups'));
}
/**
* Tests that deleteing from a groups-enabled config is possible
*
* @return void
*/
public function testGroupDelete() {
Cache::config('memcached_groups', array(
'engine' => 'Memcached',
'duration' => 3600,
'groups' => array('group_a', 'group_b')
));
$this->assertTrue(Cache::write('test_groups', 'value', 'memcached_groups'));
$this->assertEquals('value', Cache::read('test_groups', 'memcached_groups'));
$this->assertTrue(Cache::delete('test_groups', 'memcached_groups'));
$this->assertFalse(Cache::read('test_groups', 'memcached_groups'));
}
/**
* Test clearing a cache group
*
* @return void
*/
public function testGroupClear() {
Cache::config('memcached_groups', array(
'engine' => 'Memcached',
'duration' => 3600,
'groups' => array('group_a', 'group_b')
));
$this->assertTrue(Cache::write('test_groups', 'value', 'memcached_groups'));
$this->assertTrue(Cache::clearGroup('group_a', 'memcached_groups'));
$this->assertFalse(Cache::read('test_groups', 'memcached_groups'));
$this->assertTrue(Cache::write('test_groups', 'value2', 'memcached_groups'));
$this->assertTrue(Cache::clearGroup('group_b', 'memcached_groups'));
$this->assertFalse(Cache::read('test_groups', 'memcached_groups'));
}
}

View file

@ -22,6 +22,7 @@ App::uses('CommandListShell', 'Console/Command');
App::uses('ConsoleOutput', 'Console');
App::uses('ConsoleInput', 'Console');
App::uses('Shell', 'Console');
App::uses('CommandTask', 'Console/Command/Task');
/**
* Class TestStringOutput
@ -70,6 +71,12 @@ class CommandListShellTest extends CakeTestCase {
array('in', '_stop', 'clear'),
array($out, $out, $in)
);
$this->Shell->Command = $this->getMock(
'CommandTask',
array('in', '_stop', 'clear'),
array($out, $out, $in)
);
}
/**
@ -98,7 +105,7 @@ class CommandListShellTest extends CakeTestCase {
$expected = "/\[.*TestPluginTwo.*\] example, welcome/";
$this->assertRegExp($expected, $output);
$expected = "/\[.*CORE.*\] acl, api, bake, command_list, console, i18n, schema, server, test, testsuite, upgrade/";
$expected = "/\[.*CORE.*\] acl, api, bake, command_list, completion, console, i18n, schema, server, test, testsuite, upgrade/";
$this->assertRegExp($expected, $output);
$expected = "/\[.*app.*\] sample/";

View file

@ -0,0 +1,261 @@
<?php
/**
* CompletionShellTest file
*
* PHP 5
*
* CakePHP : 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 Project
* @package Cake.Test.Case.Console.Command
* @since CakePHP v 2.5
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
App::uses('CompletionShell', 'Console/Command');
App::uses('ConsoleOutput', 'Console');
App::uses('ConsoleInput', 'Console');
App::uses('Shell', 'Console');
App::uses('CommandTask', 'Console/Command/Task');
/**
* Class TestCompletionStringOutput
*
* @package Cake.Test.Case.Console.Command
*/
class TestCompletionStringOutput extends ConsoleOutput {
public $output = '';
protected function _write($message) {
$this->output .= $message;
}
}
/**
* Class CompletionShellTest
*
* @package Cake.Test.Case.Console.Command
*/
class CompletionShellTest extends CakeTestCase {
/**
* setUp method
*
* @return void
*/
public function setUp() {
parent::setUp();
App::build(array(
'Plugin' => array(
CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS
),
'Console/Command' => array(
CAKE . 'Test' . DS . 'test_app' . DS . 'Console' . DS . 'Command' . DS
)
), App::RESET);
CakePlugin::load(array('TestPlugin', 'TestPluginTwo'));
$out = new TestCompletionStringOutput();
$in = $this->getMock('ConsoleInput', array(), array(), '', false);
$this->Shell = $this->getMock(
'CompletionShell',
array('in', '_stop', 'clear'),
array($out, $out, $in)
);
$this->Shell->Command = $this->getMock(
'CommandTask',
array('in', '_stop', 'clear'),
array($out, $out, $in)
);
}
/**
* tearDown
*
* @return void
*/
public function tearDown() {
parent::tearDown();
unset($this->Shell);
CakePlugin::unload();
}
/**
* test that the startup method supresses the shell header
*
* @return void
*/
public function testStartup() {
$this->Shell->runCommand('main', array());
$output = $this->Shell->stdout->output;
$needle = 'Welcome to CakePHP';
$this->assertTextNotContains($needle, $output);
}
/**
* test that main displays a warning
*
* @return void
*/
public function testMain() {
$this->Shell->runCommand('main', array());
$output = $this->Shell->stdout->output;
$expected = "/This command is not intended to be called manually/";
$this->assertRegExp($expected, $output);
}
/**
* test commands method that list all available commands
*
* @return void
*/
public function testCommands() {
$this->Shell->runCommand('commands', array());
$output = $this->Shell->stdout->output;
$expected = "TestPlugin.example TestPluginTwo.example TestPluginTwo.welcome acl api bake command_list completion console i18n schema server test testsuite upgrade sample\n";
$this->assertEquals($expected, $output);
}
/**
* test that options without argument returns the default options
*
* @return void
*/
public function testOptionsNoArguments() {
$this->Shell->runCommand('options', array());
$output = $this->Shell->stdout->output;
$expected = "--help -h --verbose -v --quiet -q\n";
$this->assertEquals($expected, $output);
}
/**
* test that options with a nonexisting command returns the default options
*
* @return void
*/
public function testOptionsNonExistingCommand() {
$this->Shell->runCommand('options', array('options', 'foo'));
$output = $this->Shell->stdout->output;
$expected = "--help -h --verbose -v --quiet -q\n";
$this->assertEquals($expected, $output);
}
/**
* test that options with a existing command returns the proper options
*
* @return void
*/
public function testOptions() {
$this->Shell->runCommand('options', array('options', 'bake'));
$output = $this->Shell->stdout->output;
$expected = "--help -h --verbose -v --quiet -q --connection -c --theme -t\n";
$this->assertEquals($expected, $output);
}
/**
* test that subCommands with a existing CORE command returns the proper sub commands
*
* @return void
*/
public function testSubCommandsCorePlugin() {
$this->Shell->runCommand('subCommands', array('subCommands', 'CORE.bake'));
$output = $this->Shell->stdout->output;
$expected = "controller db_config fixture model plugin project test view\n";
$this->assertEquals($expected, $output);
}
/**
* test that subCommands with a existing APP command returns the proper sub commands (in this case none)
*
* @return void
*/
public function testSubCommandsAppPlugin() {
$this->Shell->runCommand('subCommands', array('subCommands', 'app.sample'));
$output = $this->Shell->stdout->output;
$expected = '';
$this->assertEquals($expected, $output);
}
/**
* test that subCommands with a existing plugin command returns the proper sub commands
*
* @return void
*/
public function testSubCommandsPlugin() {
$this->Shell->runCommand('subCommands', array('subCommands', 'TestPluginTwo.welcome'));
$output = $this->Shell->stdout->output;
$expected = "say_hello\n";
$this->assertEquals($expected, $output);
}
/**
* test that subcommands without arguments returns nothing
*
* @return void
*/
public function testSubCommandsNoArguments() {
$this->Shell->runCommand('subCommands', array());
$output = $this->Shell->stdout->output;
$expected = '';
$this->assertEquals($expected, $output);
}
/**
* test that subcommands with a nonexisting command returns nothing
*
* @return void
*/
public function testSubCommandsNonExistingCommand() {
$this->Shell->runCommand('subCommands', array('subCommands', 'foo'));
$output = $this->Shell->stdout->output;
$expected = '';
$this->assertEquals($expected, $output);
}
/**
* test that subcommands returns the available subcommands for the given command
*
* @return void
*/
public function testSubCommands() {
$this->Shell->runCommand('subCommands', array('subCommands', 'bake'));
$output = $this->Shell->stdout->output;
$expected = "controller db_config fixture model plugin project test view\n";
$this->assertEquals($expected, $output);
}
/**
* test that fuzzy returns nothing
*
* @return void
*/
public function testFuzzy() {
$this->Shell->runCommand('fuzzy', array());
$output = $this->Shell->stdout->output;
$expected = '';
$this->assertEquals($expected, $output);
}
}

View file

@ -426,6 +426,29 @@ class SchemaShellTest extends CakeTestCase {
$this->assertContains('public $aros_acos = array(', $contents);
}
/**
* Test schema run create with --yes option
*
* @return void
*/
public function testCreateOptionYes() {
$this->Shell = $this->getMock(
'SchemaShell',
array('in', 'out', 'hr', 'createFile', 'error', 'err', '_stop', '_run'),
array(&$this->Dispatcher)
);
$this->Shell->params = array(
'connection' => 'test',
'yes' => true,
);
$this->Shell->args = array('i18n');
$this->Shell->expects($this->never())->method('in');
$this->Shell->expects($this->exactly(2))->method('_run');
$this->Shell->startup();
$this->Shell->create();
}
/**
* Test schema run create with no table args.
*
@ -536,6 +559,33 @@ class SchemaShellTest extends CakeTestCase {
$this->Shell->update();
}
/**
* test run update with --yes option
*
* @return void
*/
public function testUpdateWithOptionYes() {
$this->Shell = $this->getMock(
'SchemaShell',
array('in', 'out', 'hr', 'createFile', 'error', 'err', '_stop', '_run'),
array(&$this->Dispatcher)
);
$this->Shell->params = array(
'connection' => 'test',
'force' => true,
'yes' => true,
);
$this->Shell->args = array('SchemaShellTest', 'articles');
$this->Shell->startup();
$this->Shell->expects($this->never())->method('in');
$this->Shell->expects($this->once())
->method('_run')
->with($this->arrayHasKey('articles'), 'update', $this->isInstanceOf('CakeSchema'));
$this->Shell->update();
}
/**
* test that the plugin param creates the correct path in the schema object.
*

View file

@ -0,0 +1,240 @@
<?php
/**
* CakePHP : 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 Project
* @package Cake.Test.Case.Console.Command
* @since CakePHP v 2.5
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
App::uses('CommandTask', 'Console/Command/Task');
/**
* CommandTaskTest class
*
* @package Cake.Test.Case.Console.Command.Task
*/
class CommandTaskTest extends CakeTestCase {
/**
* setUp method
*
* @return void
*/
public function setUp() {
parent::setUp();
App::build(array(
'Plugin' => array(
CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS
),
'Console/Command' => array(
CAKE . 'Test' . DS . 'test_app' . DS . 'Console' . DS . 'Command' . DS
)
), App::RESET);
CakePlugin::load(array('TestPlugin', 'TestPluginTwo'));
$out = $this->getMock('ConsoleOutput', array(), array(), '', false);
$in = $this->getMock('ConsoleInput', array(), array(), '', false);
$this->CommandTask = $this->getMock(
'CommandTask',
array('in', '_stop', 'clear'),
array($out, $out, $in)
);
}
/**
* tearDown
*
* @return void
*/
public function tearDown() {
parent::tearDown();
unset($this->CommandTask);
CakePlugin::unload();
}
/**
* Test the resulting list of shells
*
* @return void
*/
public function testGetShellList() {
$result = $this->CommandTask->getShellList();
$expected = array(
'CORE' => array(
'acl',
'api',
'bake',
'command_list',
'completion',
'console',
'i18n',
'schema',
'server',
'test',
'testsuite',
'upgrade'
),
'TestPlugin' => array(
'example'
),
'TestPluginTwo' => array(
'example',
'welcome'
),
'app' => array(
'sample'
),
);
$this->assertEquals($expected, $result);
}
/**
* Test the resulting list of commands
*
* @return void
*/
public function testCommands() {
$result = $this->CommandTask->commands();
$expected = array(
'TestPlugin.example',
'TestPluginTwo.example',
'TestPluginTwo.welcome',
'acl',
'api',
'bake',
'command_list',
'completion',
'console',
'i18n',
'schema',
'server',
'test',
'testsuite',
'upgrade',
'sample'
);
$this->assertEquals($expected, $result);
}
/**
* Test the resulting list of subcommands for the given command
*
* @return void
*/
public function testSubCommands() {
$result = $this->CommandTask->subCommands('acl');
$expected = array(
'check',
'create',
'db_config',
'delete',
'deny',
'getPath',
'grant',
'inherit',
'initdb',
'nodeExists',
'parseIdentifier',
'setParent',
'view'
);
$this->assertEquals($expected, $result);
}
/**
* Test that unknown commands return an empty array
*
* @return void
*/
public function testSubCommandsUnknownCommand() {
$result = $this->CommandTask->subCommands('yoghurt');
$expected = array();
$this->assertEquals($expected, $result);
}
/**
* Test that getting a existing shell returns the shell instance
*
* @return void
*/
public function testGetShell() {
$result = $this->CommandTask->getShell('acl');
$this->assertInstanceOf('AclShell', $result);
}
/**
* Test that getting a non-existing shell returns false
*
* @return void
*/
public function testGetShellNonExisting() {
$result = $this->CommandTask->getShell('strawberry');
$this->assertFalse($result);
}
/**
* Test that getting a existing core shell with 'core.' prefix returns the correct shell instance
*
* @return void
*/
public function testGetShellCore() {
$result = $this->CommandTask->getShell('core.bake');
$this->assertInstanceOf('BakeShell', $result);
}
/**
* Test the options array for a known command
*
* @return void
*/
public function testOptions() {
$result = $this->CommandTask->options('bake');
$expected = array(
'--help',
'-h',
'--verbose',
'-v',
'--quiet',
'-q',
'--connection',
'-c',
'--theme',
'-t'
);
$this->assertEquals($expected, $result);
}
/**
* Test the options array for an unknown command
*
* @return void
*/
public function testOptionsUnknownCommand() {
$result = $this->CommandTask->options('pie');
$expected = array(
'--help',
'-h',
'--verbose',
'-v',
'--quiet',
'-q'
);
$this->assertEquals($expected, $result);
}
}

View file

@ -605,7 +605,6 @@ class ControllerTest extends CakeTestCase {
$Controller->set('title', 'someTitle');
$this->assertSame($Controller->viewVars['title'], 'someTitle');
$this->assertTrue(empty($Controller->pageTitle));
$Controller->viewVars = array();
$expected = array('ModelName' => 'name', 'ModelName2' => 'name2');

View file

@ -126,27 +126,20 @@ class CakeLogTest extends CakeTestCase {
}
/**
* Test that CakeLog autoconfigures itself to use a FileLogger with the LOGS dir.
* When no streams are there.
* Test that CakeLog does not auto create logs when no streams are there to listen.
*
* @return void
*/
public function testAutoConfig() {
public function testNoStreamListenting() {
if (file_exists(LOGS . 'error.log')) {
unlink(LOGS . 'error.log');
}
CakeLog::write(LOG_WARNING, 'Test warning');
$this->assertTrue(file_exists(LOGS . 'error.log'));
$res = CakeLog::write(LOG_WARNING, 'Test warning');
$this->assertFalse($res);
$this->assertFalse(file_exists(LOGS . 'error.log'));
$result = CakeLog::configured();
$this->assertEquals(array('default'), $result);
$testMessage = 'custom message';
CakeLog::write('custom', $testMessage);
$content = file_get_contents(LOGS . 'custom.log');
$this->assertContains($testMessage, $content);
unlink(LOGS . 'error.log');
unlink(LOGS . 'custom.log');
$this->assertEquals(array(), $result);
}
/**
@ -197,6 +190,10 @@ class CakeLogTest extends CakeTestCase {
* @return void
*/
public function testLogFileWriting() {
CakeLog::config('file', array(
'engine' => 'File',
'path' => LOGS
));
if (file_exists(LOGS . 'error.log')) {
unlink(LOGS . 'error.log');
}
@ -503,6 +500,11 @@ class CakeLogTest extends CakeTestCase {
$this->_resetLogConfig();
$this->_deleteLogs();
CakeLog::config('file', array(
'engine' => 'File',
'path' => LOGS
));
CakeLog::write('bogus', 'bogus message');
$this->assertTrue(file_exists(LOGS . 'bogus.log'));
$this->assertFalse(file_exists(LOGS . 'error.log'));

View file

@ -443,10 +443,10 @@ class CakeSessionTest extends CakeTestCase {
public function testKeyExploit() {
$key = "a'] = 1; phpinfo(); \$_SESSION['a";
$result = TestCakeSession::write($key, 'haxored');
$this->assertTrue($result);
$this->assertFalse($result);
$result = TestCakeSession::read($key);
$this->assertEquals('haxored', $result);
$this->assertNull($result);
}
/**

View file

@ -612,6 +612,34 @@ class ModelValidationTest extends BaseModelTest {
$this->assertEquals(0, $joinRecords, 'Records were saved on the join table. %s');
}
/**
* Test that if a behavior modifies the model's whitelist validation gets triggered
* properly for those fields.
*
* @return void
*/
public function testValidateWithFieldListAndBehavior() {
$TestModel = new ValidationTest1();
$TestModel->validate = array(
'title' => array(
'rule' => 'notEmpty',
),
'name' => array(
'rule' => 'notEmpty',
));
$TestModel->Behaviors->attach('ValidationRule', array('fields' => array('name')));
$data = array(
'title' => '',
'name' => '',
);
$result = $TestModel->save($data, array('fieldList' => array('title')));
$this->assertFalse($result);
$expected = array('title' => array('This field cannot be left blank'), 'name' => array('This field cannot be left blank'));
$this->assertEquals($expected, $TestModel->validationErrors);
}
/**
* test that saveAll and with models with validation interact well
*
@ -2380,3 +2408,21 @@ class ModelValidationTest extends BaseModelTest {
}
}
/**
* Behavior for testing validation rules.
*/
class ValidationRuleBehavior extends ModelBehavior {
public function setup(Model $Model, $config = array()) {
$this->settings[$Model->alias] = $config;
}
public function beforeValidate(Model $Model, $options = array()) {
$fields = $this->settings[$Model->alias]['fields'];
foreach ($fields as $field) {
$Model->whitelist[] = $field;
}
}
}

View file

@ -1046,6 +1046,13 @@ class CakeRequestTest extends CakeTestCase {
$request->return = false;
$this->assertFalse($request->isCallMe());
$request->addDetector('extension', array('param' => 'ext', 'options' => array('pdf', 'png', 'txt')));
$request->params['ext'] = 'pdf';
$this->assertTrue($request->is('extension'));
$request->params['ext'] = 'exe';
$this->assertFalse($request->isExtension());
}
/**

View file

@ -191,6 +191,28 @@ class RouterTest extends CakeTestCase {
$this->assertEquals($expected, $result);
}
/**
* testMapResources with custom connectOptions
*/
public function testMapResourcesConnectOptions() {
App::build(array(
'Plugin' => array(
CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS
)
));
CakePlugin::load('TestPlugin');
App::uses('TestRoute', 'TestPlugin.Routing/Route');
$resources = Router::mapResources('Posts', array(
'connectOptions' => array(
'routeClass' => 'TestPlugin.TestRoute',
'foo' => '^(bar)$',
),
));
$route = end(Router::$routes);
$this->assertInstanceOf('TestRoute', $route);
$this->assertEquals('^(bar)$', $route->options['foo']);
}
/**
* Test mapResources with a plugin and prefix.
*

View file

@ -346,11 +346,24 @@ class FolderTest extends CakeTestCase {
* @return void
*/
public function testAddPathElement() {
$expected = DS . 'some' . DS . 'dir' . DS . 'another_path';
$result = Folder::addPathElement(DS . 'some' . DS . 'dir', 'another_path');
$this->assertEquals(DS . 'some' . DS . 'dir' . DS . 'another_path', $result);
$this->assertEquals($expected, $result);
$result = Folder::addPathElement(DS . 'some' . DS . 'dir' . DS, 'another_path');
$this->assertEquals(DS . 'some' . DS . 'dir' . DS . 'another_path', $result);
$this->assertEquals($expected, $result);
$result = Folder::addPathElement(DS . 'some' . DS . 'dir', array('another_path'));
$this->assertEquals($expected, $result);
$result = Folder::addPathElement(DS . 'some' . DS . 'dir' . DS, array('another_path'));
$this->assertEquals($expected, $result);
$expected = DS . 'some' . DS . 'dir' . DS . 'another_path' . DS . 'and' . DS . 'another';
$result = Folder::addPathElement(DS . 'some' . DS . 'dir', array('another_path', 'and', 'another'));
$this->assertEquals($expected, $result);
}
/**

View file

@ -1303,6 +1303,23 @@ class HashTest extends CakeTestCase {
$result = Hash::insert($data, '{n}.Comment.{n}.insert', 'value');
$this->assertEquals('value', $result[0]['Comment'][0]['insert']);
$this->assertEquals('value', $result[0]['Comment'][1]['insert']);
$data = array(
0 => array('Item' => array('id' => 1, 'title' => 'first')),
1 => array('Item' => array('id' => 2, 'title' => 'second')),
2 => array('Item' => array('id' => 3, 'title' => 'third')),
3 => array('Item' => array('id' => 4, 'title' => 'fourth')),
4 => array('Item' => array('id' => 5, 'title' => 'fifth')),
);
$result = Hash::insert($data, '{n}.Item[id=/\b2|\b4/]', array('test' => 2));
$expected = array(
0 => array('Item' => array('id' => 1, 'title' => 'first')),
1 => array('Item' => array('id' => 2, 'title' => 'second', 'test' => 2)),
2 => array('Item' => array('id' => 3, 'title' => 'third')),
3 => array('Item' => array('id' => 4, 'title' => 'fourth', 'test' => 2)),
4 => array('Item' => array('id' => 5, 'title' => 'fifth')),
);
$this->assertEquals($expected, $result);
}
/**
@ -1366,6 +1383,23 @@ class HashTest extends CakeTestCase {
$result = Hash::remove($a, 'pages.2.vars');
$expected = $a;
$this->assertEquals($expected, $result);
$a = array(
0 => array(
'name' => 'pages'
),
1 => array(
'name' => 'files'
)
);
$result = Hash::remove($a, '{n}[name=files]');
$expected = array(
0 => array(
'name' => 'pages'
)
);
$this->assertEquals($expected, $result);
}
/**
@ -1385,6 +1419,22 @@ class HashTest extends CakeTestCase {
$this->assertFalse(isset($result[0]['Article']['user_id']));
$this->assertFalse(isset($result[0]['Article']['title']));
$this->assertFalse(isset($result[0]['Article']['body']));
$data = array(
0 => array('Item' => array('id' => 1, 'title' => 'first')),
1 => array('Item' => array('id' => 2, 'title' => 'second')),
2 => array('Item' => array('id' => 3, 'title' => 'third')),
3 => array('Item' => array('id' => 4, 'title' => 'fourth')),
4 => array('Item' => array('id' => 5, 'title' => 'fifth')),
);
$result = Hash::remove($data, '{n}.Item[id=/\b2|\b4/]');
$expected = array(
0 => array('Item' => array('id' => 1, 'title' => 'first')),
2 => array('Item' => array('id' => 3, 'title' => 'third')),
4 => array('Item' => array('id' => 5, 'title' => 'fifth')),
);
$this->assertEquals($result, $expected);
}
/**

View file

@ -302,4 +302,115 @@ class SecurityTest extends CakeTestCase {
Security::rijndael($txt, $key, 'encrypt');
}
/**
* Test encrypt/decrypt.
*
* @return void
*/
public function testEncryptDecrypt() {
$txt = 'The quick brown fox';
$key = 'This key is longer than 32 bytes long.';
$result = Security::encrypt($txt, $key);
$this->assertNotEquals($txt, $result, 'Should be encrypted.');
$this->assertNotEquals($result, Security::encrypt($txt, $key), 'Each result is unique.');
$this->assertEquals($txt, Security::decrypt($result, $key));
}
/**
* Test that changing the key causes decryption to fail.
*
* @return void
*/
public function testDecryptKeyFailure() {
$txt = 'The quick brown fox';
$key = 'This key is longer than 32 bytes long.';
$result = Security::encrypt($txt, $key);
$key = 'Not the same key. This one will fail';
$this->assertFalse(Security::decrypt($txt, $key), 'Modified key will fail.');
}
/**
* Test that decrypt fails when there is an hmac error.
*
* @return void
*/
public function testDecryptHmacFailure() {
$txt = 'The quick brown fox';
$key = 'This key is quite long and works well.';
$salt = 'this is a delicious salt!';
$result = Security::encrypt($txt, $key, $salt);
// Change one of the bytes in the hmac.
$result[10] = 'x';
$this->assertFalse(Security::decrypt($result, $key, $salt), 'Modified hmac causes failure.');
}
/**
* Test that changing the hmac salt will cause failures.
*
* @return void
*/
public function testDecryptHmacSaltFailure() {
$txt = 'The quick brown fox';
$key = 'This key is quite long and works well.';
$salt = 'this is a delicious salt!';
$result = Security::encrypt($txt, $key, $salt);
$salt = 'humpty dumpty had a great fall.';
$this->assertFalse(Security::decrypt($result, $key, $salt), 'Modified salt causes failure.');
}
/**
* Test that short keys cause errors
*
* @expectedException CakeException
* @expectedExceptionMessage Invalid key for encrypt(), key must be at least 256 bits (32 bytes) long.
* @return void
*/
public function testEncryptInvalidKey() {
$txt = 'The quick brown fox jumped over the lazy dog.';
$key = 'this is too short';
Security::encrypt($txt, $key);
}
/**
* Test that empty data cause errors
*
* @expectedException CakeException
* @expectedExceptionMessage The data to encrypt cannot be empty.
* @return void
*/
public function testEncryptInvalidData() {
$txt = '';
$key = 'This is a key that is long enough to be ok.';
Security::encrypt($txt, $key);
}
/**
* Test that short keys cause errors
*
* @expectedException CakeException
* @expectedExceptionMessage Invalid key for decrypt(), key must be at least 256 bits (32 bytes) long.
* @return void
*/
public function testDecryptInvalidKey() {
$txt = 'The quick brown fox jumped over the lazy dog.';
$key = 'this is too short';
Security::decrypt($txt, $key);
}
/**
* Test that empty data cause errors
*
* @expectedException CakeException
* @expectedExceptionMessage The data to decrypt cannot be empty.
* @return void
*/
public function testDecryptInvalidData() {
$txt = '';
$key = 'This is a key that is long enough to be ok.';
Security::decrypt($txt, $key);
}
}

View file

@ -1946,8 +1946,15 @@ class ValidationTest extends CakeTestCase {
$this->assertFalse(Validation::inList('three', array('one', 'two')));
$this->assertFalse(Validation::inList('1one', array(0, 1, 2, 3)));
$this->assertFalse(Validation::inList('one', array(0, 1, 2, 3)));
$this->assertFalse(Validation::inList('2', array(1, 2, 3)));
$this->assertTrue(Validation::inList('2', array(1, 2, 3), false));
$this->assertTrue(Validation::inList('2', array(1, 2, 3)));
$this->assertFalse(Validation::inList('2x', array(1, 2, 3)));
$this->assertFalse(Validation::inList(2, array('1', '2x', '3')));
$this->assertFalse(Validation::inList('One', array('one', 'two')));
// case insensitive
$this->assertTrue(Validation::inList('one', array('One', 'Two'), true));
$this->assertTrue(Validation::inList('Two', array('one', 'two'), true));
$this->assertFalse(Validation::inList('three', array('one', 'two'), true));
}
/**
@ -2066,14 +2073,24 @@ class ValidationTest extends CakeTestCase {
$this->assertFalse(Validation::multiple(array('foo', 'bar', 'baz', 'squirrel'), array('min' => 10)));
$this->assertTrue(Validation::multiple(array(0, 5, 9), array('in' => range(0, 10), 'max' => 5)));
$this->assertFalse(Validation::multiple(array('0', '5', '9'), array('in' => range(0, 10), 'max' => 5)));
$this->assertTrue(Validation::multiple(array('0', '5', '9'), array('in' => range(0, 10), 'max' => 5), false));
$this->assertTrue(Validation::multiple(array('0', '5', '9'), array('in' => range(0, 10), 'max' => 5)));
$this->assertFalse(Validation::multiple(array(0, 5, 9, 8, 6, 2, 1), array('in' => range(0, 10), 'max' => 5)));
$this->assertFalse(Validation::multiple(array(0, 5, 9, 8, 11), array('in' => range(0, 10), 'max' => 5)));
$this->assertFalse(Validation::multiple(array(0, 5, 9), array('in' => range(0, 10), 'max' => 5, 'min' => 3)));
$this->assertFalse(Validation::multiple(array(0, 5, 9, 8, 6, 2, 1), array('in' => range(0, 10), 'max' => 5, 'min' => 2)));
$this->assertFalse(Validation::multiple(array(0, 5, 9, 8, 11), array('in' => range(0, 10), 'max' => 5, 'min' => 2)));
$this->assertFalse(Validation::multiple(array('2x', '3x'), array('in' => array(1, 2, 3, 4, 5))));
$this->assertFalse(Validation::multiple(array(2, 3), array('in' => array('1x', '2x', '3x', '4x'))));
$this->assertFalse(Validation::multiple(array('one'), array('in' => array('One', 'Two'))));
$this->assertFalse(Validation::multiple(array('Two'), array('in' => array('one', 'two'))));
// case insensitive
$this->assertTrue(Validation::multiple(array('one'), array('in' => array('One', 'Two')), true));
$this->assertTrue(Validation::multiple(array('Two'), array('in' => array('one', 'two')), true));
$this->assertFalse(Validation::multiple(array('three'), array('in' => array('one', 'two')), true));
}
/**
@ -2334,6 +2351,7 @@ class ValidationTest extends CakeTestCase {
$this->assertTrue(Validation::mimeType($image, array('image/gif')));
$this->assertTrue(Validation::mimeType(array('tmp_name' => $image), array('image/gif')));
$this->assertFalse(Validation::mimeType($image, array('image/GIF')));
$this->assertFalse(Validation::mimeType($image, array('image/png')));
$this->assertFalse(Validation::mimeType(array('tmp_name' => $image), array('image/png')));
}

View file

@ -93,7 +93,7 @@ class RssHelperTest extends CakeTestCase {
*/
public function testChannel() {
$attrib = array('a' => '1', 'b' => '2');
$elements = array('title' => 'title');
$elements = array('title' => 'Title');
$content = 'content';
$result = $this->Rss->channel($attrib, $elements, $content);
@ -103,30 +103,7 @@ class RssHelperTest extends CakeTestCase {
'b' => '2'
),
'<title',
'title',
'/title',
'<link',
$this->Rss->url('/', true),
'/link',
'<description',
'content',
'/channel'
);
$this->assertTags($result, $expected);
$this->View->pageTitle = 'title';
$attrib = array('a' => '1', 'b' => '2');
$elements = array();
$content = 'content';
$result = $this->Rss->channel($attrib, $elements, $content);
$expected = array(
'channel' => array(
'a' => '1',
'b' => '2'
),
'<title',
'title',
'Title',
'/title',
'<link',
$this->Rss->url('/', true),

View file

@ -1610,19 +1610,6 @@ TEXT;
$this->assertEquals($expected, $result);
}
/**
* Test that setting arbitrary properties still works.
*
* @return void
*/
public function testPropertySetting() {
$this->assertFalse(isset($this->View->pageTitle));
$this->View->pageTitle = 'test';
$this->assertTrue(isset($this->View->pageTitle));
$this->assertTrue(!empty($this->View->pageTitle));
$this->assertEquals('test', $this->View->pageTitle);
}
/**
* Test that setting arbitrary properties still works.
*
@ -1660,7 +1647,7 @@ TEXT;
}
/**
* Tests that a vew block uses default value when not assigned and uses assigned value when it is
* Tests that a view block uses default value when not assigned and uses assigned value when it is
*
* @return void
*/
@ -1674,4 +1661,20 @@ TEXT;
$result = $this->View->fetch('title', $default);
$this->assertEquals($expected, $result);
}
/**
* Tests that a view variable uses default value when not assigned and uses assigned value when it is
*
* @return void
*/
public function testViewVarDefaultValue() {
$default = 'Default';
$result = $this->View->get('title', $default);
$this->assertEquals($default, $result);
$expected = 'Back to the Future';
$this->View->set('title', $expected);
$result = $this->View->get('title', $default);
$this->assertEquals($expected, $result);
}
}

View file

@ -96,7 +96,7 @@ abstract class BaseCoverageReport {
/**
* Gets the base path that the files we are interested in live in.
*
* @return void
* @return string Path
*/
public function getPathFilter() {
$path = ROOT . DS;

View file

@ -1,7 +1,5 @@
<?php
/**
* Generates code coverage reports in HTML from data obtained from PHPUnit
*
* PHP5
*
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
@ -28,9 +26,23 @@ App::uses('BaseCoverageReport', 'TestSuite/Coverage');
class HtmlCoverageReport extends BaseCoverageReport {
/**
* Generates report html to display.
* Holds the total number of processed rows.
*
* @return string compiled html report.
* @var integer
*/
protected $_total = 0;
/**
* Holds the total number of covered rows.
*
* @var integer
*/
protected $_covered = 0;
/**
* Generates report HTML to display.
*
* @return string Compiled HTML report.
*/
public function report() {
$pathFilter = $this->getPathFilter();
@ -48,6 +60,12 @@ HTML;
$fileData = file($file);
$output .= $this->generateDiff($file, $fileData, $coverageData);
}
$percentCovered = 100;
if ($this->_total > 0) {
$percentCovered = round(100 * $this->_covered / $this->_total, 2);
}
$output .= '<div class="total">Overall coverage: <span class="coverage">' . $percentCovered . '%</span></div>';
return $output;
}
@ -69,6 +87,8 @@ HTML;
$diff = array();
list($covered, $total) = $this->_calculateCoveredLines($fileLines, $coverageData);
$this->_covered += $covered;
$this->_total += $total;
//shift line numbers forward one;
array_unshift($fileLines, ' ');
@ -121,13 +141,13 @@ HTML;
}
/**
* Renders the html for a single line in the html diff.
* Renders the HTML for a single line in the HTML diff.
*
* @param string $line
* @param integer $linenumber
* @param string $class
* @param array $coveringTests
* @return void
* @return string
*/
protected function _paintLine($line, $linenumber, $class, $coveringTests) {
$coveredBy = '';
@ -150,7 +170,7 @@ HTML;
/**
* generate some javascript for the coverage report.
*
* @return void
* @return string
*/
public function coverageScript() {
return <<<HTML
@ -177,7 +197,7 @@ HTML;
*
* @param string $filename
* @param string $percent
* @return void
* @return string
*/
public function coverageHeader($filename, $percent) {
$filename = basename($filename);

View file

@ -321,12 +321,14 @@ class Folder {
* Returns $path with $element added, with correct slash in-between.
*
* @param string $path Path
* @param string $element Element to and at end of path
* @param string|array $element Element to add at end of path
* @return string Combined path
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/file-folder.html#Folder::addPathElement
*/
public static function addPathElement($path, $element) {
return rtrim($path, DS) . DS . $element;
$element = (array)$element;
array_unshift($element, rtrim($path, DS));
return implode(DS, $element);
}
/**

View file

@ -111,12 +111,7 @@ class Hash {
foreach ($tokens as $token) {
$next = array();
$conditions = false;
$position = strpos($token, '[');
if ($position !== false) {
$conditions = substr($token, $position);
$token = substr($token, 0, $position);
}
list($token, $conditions) = self::_splitConditions($token);
foreach ($context[$_key] as $item) {
foreach ((array)$item as $k => $v) {
@ -141,6 +136,22 @@ class Hash {
}
return $context[$_key];
}
/**
* Split token conditions
*
* @param string $token the token being splitted.
* @return array array(token, conditions) with token splitted
*/
protected static function _splitConditions($token) {
$conditions = false;
$position = strpos($token, '[');
if ($position !== false) {
$conditions = substr($token, $position);
$token = substr($token, 0, $position);
}
return array($token, $conditions);
}
/**
* Check a key against a token.
@ -225,16 +236,30 @@ class Hash {
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::insert
*/
public static function insert(array $data, $path, $values = null) {
$tokens = explode('.', $path);
if (strpos($path, '{') === false) {
if (strpos($path, '[') === false) {
$tokens = explode('.', $path);
} else {
$tokens = String::tokenize($path, '.', '[', ']');
}
if (strpos($path, '{') === false && strpos($path, '[') === false) {
return self::_simpleOp('insert', $data, $tokens, $values);
}
$token = array_shift($tokens);
$nextPath = implode('.', $tokens);
list($token, $conditions) = self::_splitConditions($token);
foreach ($data as $k => $v) {
if (self::_matchToken($k, $token)) {
$data[$k] = self::insert($v, $nextPath, $values);
if ($conditions && self::_matches($v, $conditions)) {
$data[$k] = array_merge($v, $values);
continue;
}
if (!$conditions) {
$data[$k] = self::insert($v, $nextPath, $values);
}
}
}
return $data;
@ -294,17 +319,32 @@ class Hash {
* @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::remove
*/
public static function remove(array $data, $path) {
$tokens = explode('.', $path);
if (strpos($path, '{') === false) {
if (strpos($path, '[') === false) {
$tokens = explode('.', $path);
} else {
$tokens = String::tokenize($path, '.', '[', ']');
}
if (strpos($path, '{') === false && strpos($path, '[') === false) {
return self::_simpleOp('remove', $data, $tokens);
}
$token = array_shift($tokens);
$nextPath = implode('.', $tokens);
list($token, $conditions) = self::_splitConditions($token);
foreach ($data as $k => $v) {
$match = self::_matchToken($k, $token);
if ($match && is_array($v)) {
if ($conditions && self::_matches($v, $conditions)) {
unset($data[$k]);
continue;
}
$data[$k] = self::remove($v, $nextPath);
if (empty($data[$k])) {
unset($data[$k]);
}
} elseif ($match) {
unset($data[$k]);
}

View file

@ -289,4 +289,94 @@ class Security {
return crypt($password, $salt);
}
/**
* Encrypt a value using AES-256.
*
* *Caveat* You cannot properly encrypt/decrypt data with trailing null bytes.
* Any trailing null bytes will be removed on decryption due to how PHP pads messages
* with nulls prior to encryption.
*
* @param string $plain The value to encrypt.
* @param string $key The 256 bit/32 byte key to use as a cipher key.
* @param string $hmacSalt The salt to use for the HMAC process. Leave null to use Security.salt.
* @return string Encrypted data.
* @throws CakeException On invalid data or key.
*/
public static function encrypt($plain, $key, $hmacSalt = null) {
self::_checkKey($key, 'encrypt()');
if (empty($plain)) {
throw new CakeException(__d('cake_dev', 'The data to encrypt cannot be empty.'));
}
if ($hmacSalt === null) {
$hmacSalt = Configure::read('Security.salt');
}
// Generate the encryption and hmac key.
$key = substr(hash('sha256', $key . $hmacSalt), 0, 32);
$algorithm = MCRYPT_RIJNDAEL_128;
$mode = MCRYPT_MODE_CBC;
$ivSize = mcrypt_get_iv_size($algorithm, $mode);
$iv = mcrypt_create_iv($ivSize, MCRYPT_DEV_URANDOM);
$ciphertext = $iv . mcrypt_encrypt($algorithm, $key, $plain, $mode, $iv);
$hmac = hash_hmac('sha256', $ciphertext, $key);
return $hmac . $ciphertext;
}
/**
* Check the encryption key for proper length.
*
* @param string $key
* @param string $method The method the key is being checked for.
* @return void
* @throws CakeException When key length is not 256 bit/32 bytes
*/
protected static function _checkKey($key, $method) {
if (strlen($key) < 32) {
throw new CakeException(__d('cake_dev', 'Invalid key for %s, key must be at least 256 bits (32 bytes) long.', $method));
}
}
/**
* Decrypt a value using AES-256.
*
* @param string $cipher The ciphertext to decrypt.
* @param string $key The 256 bit/32 byte key to use as a cipher key.
* @param string $hmacSalt The salt to use for the HMAC process. Leave null to use Security.salt.
* @return string Decrypted data. Any trailing null bytes will be removed.
* @throws CakeException On invalid data or key.
*/
public static function decrypt($cipher, $key, $hmacSalt = null) {
self::_checkKey($key, 'decrypt()');
if (empty($cipher)) {
throw new CakeException(__d('cake_dev', 'The data to decrypt cannot be empty.'));
}
if ($hmacSalt === null) {
$hmacSalt = Configure::read('Security.salt');
}
// Generate the encryption and hmac key.
$key = substr(hash('sha256', $key . $hmacSalt), 0, 32);
// Split out hmac for comparison
$macSize = 64;
$hmac = substr($cipher, 0, $macSize);
$cipher = substr($cipher, $macSize);
$compareHmac = hash_hmac('sha256', $cipher, $key);
if ($hmac !== $compareHmac) {
return false;
}
$algorithm = MCRYPT_RIJNDAEL_128;
$mode = MCRYPT_MODE_CBC;
$ivSize = mcrypt_get_iv_size($algorithm, $mode);
$iv = substr($cipher, 0, $ivSize);
$cipher = substr($cipher, $ivSize);
$plain = mcrypt_decrypt($algorithm, $key, $cipher, $mode, $iv);
return rtrim($plain, "\0");
}
}

View file

@ -540,7 +540,7 @@ class Validation {
}
/**
* Validate a multiple select.
* Validate a multiple select. Comparison is case sensitive by default.
*
* Valid Options
*
@ -550,12 +550,13 @@ class Validation {
*
* @param array $check Value to check
* @param array $options Options for the check.
* @param boolean $strict Defaults to true, set to false to disable strict type check
* @param boolean $caseInsensitive Set to true for case insensitive comparison.
* @return boolean Success
*/
public static function multiple($check, $options = array(), $strict = true) {
public static function multiple($check, $options = array(), $caseInsensitive = false) {
$defaults = array('in' => null, 'max' => null, 'min' => null);
$options = array_merge($defaults, $options);
$check = array_filter((array)$check);
if (empty($check)) {
return false;
@ -567,8 +568,15 @@ class Validation {
return false;
}
if ($options['in'] && is_array($options['in'])) {
if ($caseInsensitive) {
$options['in'] = array_map('mb_strtolower', $options['in']);
}
foreach ($check as $val) {
if (!in_array($val, $options['in'], $strict)) {
$strict = !is_numeric($val);
if ($caseInsensitive) {
$val = mb_strtolower($val);
}
if (!in_array((string)$val, $options['in'], $strict)) {
return false;
}
}
@ -766,15 +774,22 @@ class Validation {
}
/**
* Checks if a value is in a given list.
* Checks if a value is in a given list. Comparison is case sensitive by default.
*
* @param string $check Value to check
* @param array $list List to check against
* @param boolean $strict Defaults to true, set to false to disable strict type check
* @return boolean Success
* @param string $check Value to check.
* @param array $list List to check against.
* @param boolean $caseInsensitive Set to true for case insensitive comparison.
* @return boolean Success.
*/
public static function inList($check, $list, $strict = true) {
return in_array($check, $list, $strict);
public static function inList($check, $list, $caseInsensitive = false) {
$strict = !is_numeric($check);
if ($caseInsensitive) {
$list = array_map('mb_strtolower', $list);
$check = mb_strtolower($check);
}
return in_array((string)$check, $list, $strict);
}
/**
@ -896,7 +911,7 @@ class Validation {
}
/**
* Checks the mime type of a file
* Checks the mime type of a file. Comparison is case sensitive.
*
* @param string|array $check
* @param array $mimeTypes to check for

View file

@ -17,4 +17,4 @@
// @license http://www.opensource.org/licenses/mit-license.php MIT License
// +--------------------------------------------------------------------------------------------+ //
////////////////////////////////////////////////////////////////////////////////////////////////////
2.4.1
2.5.0-dev

View file

@ -123,12 +123,12 @@ class RssHelper extends AppHelper {
* @link http://book.cakephp.org/2.0/en/core-libraries/helpers/rss.html#RssHelper::channel
*/
public function channel($attrib = array(), $elements = array(), $content = null) {
if (!isset($elements['title']) && !empty($this->_View->pageTitle)) {
$elements['title'] = $this->_View->pageTitle;
}
if (!isset($elements['link'])) {
$elements['link'] = '/';
}
if (!isset($elements['title'])) {
$elements['title'] = '';
}
if (!isset($elements['description'])) {
$elements['description'] = '';
}

View file

@ -584,11 +584,12 @@ class View extends Object {
* Blocks are checked before view variables.
*
* @param string $var The view var you want the contents of.
* @return mixed The content of the named var if its set, otherwise null.
* @param mixed $default The default/fallback content of $var.
* @return mixed The content of the named var if its set, otherwise $default.
*/
public function get($var) {
public function get($var, $default = null) {
if (!isset($this->viewVars[$var])) {
return null;
return $default;
}
return $this->viewVars[$var];
}

View file

@ -65,7 +65,7 @@ if (!function_exists('debug')) {
*
* Only runs if debug level is greater than zero.
*
* @param boolean $var Variable to show debug information for.
* @param mixed $var Variable to show debug information for.
* @param boolean $showHtml If set to true, the method prints the debug data in a browser-friendly way.
* @param boolean $showFrom If set to true, the method prints from where the function was called.
* @return void