Merge pull request #4 from cakephp/2.x

2.x sync
This commit is contained in:
Val Bancer 2017-11-21 15:17:07 +01:00 committed by GitHub
commit 9a69363858
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 475 additions and 175 deletions

View file

@ -10,6 +10,7 @@ php:
- 5.6
- 7.0
- 7.1
- 7.2
env:
matrix:
@ -38,6 +39,15 @@ matrix:
- php: 7.1
env: DB=mysql PHPUNIT=5.7.19
- php: 7.2
env: DB=mysql PHPUNIT=5.7.19
allow_failures:
- php: 7.2
env: DB=mysql
- php: 7.2
env: DB=mysql PHPUNIT=5.7.19
before_script:
- composer require "phpunit/phpunit=$PHPUNIT"
@ -52,6 +62,7 @@ before_script:
- sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'CREATE SCHEMA test3;' -U postgres -d cakephp_test; fi"
- chmod -R 777 ./app/tmp
- if [[ ${TRAVIS_PHP_VERSION:0:3} == "5.3" ]] ; then pecl install timezonedb ; fi
- if [[ ${TRAVIS_PHP_VERSION:0:3} == "7.2" ]] ; then pecl install mcrypt ; fi
- sh -c "if [ '$PHPCS' = '1' ]; then composer require 'cakephp/cakephp-codesniffer:1.*'; fi"
- sh -c "if [ '$PHPCS' = '1' ]; then vendors/bin/phpcs --config-set installed_paths vendors/cakephp/cakephp-codesniffer; fi"
- echo "extension = memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini

View file

@ -415,7 +415,7 @@ class Shell extends CakeObject {
* @param string $command The command name to run on this shell. If this argument is empty,
* and the shell has a `main()` method, that will be called instead.
* @param array $argv Array of arguments to run the shell with. This array should be missing the shell name.
* @return void
* @return int|bool
* @link https://book.cakephp.org/2.0/en/console-and-shells.html#Shell::runCommand
*/
public function runCommand($command, $argv) {
@ -469,7 +469,7 @@ class Shell extends CakeObject {
* Display the help in the correct format
*
* @param string $command The command to get help for.
* @return void
* @return int|bool
*/
protected function _displayHelp($command) {
$format = 'text';
@ -571,7 +571,7 @@ class Shell extends CakeObject {
* @param string $prompt Prompt text.
* @param string|array $options Array or string of options.
* @param string $default Default input value.
* @return Either the default value, or the user-provided input.
* @return string|int the default value, or the user-provided input.
*/
protected function _getInput($prompt, $options, $default) {
if (!is_array($options)) {
@ -726,7 +726,7 @@ class Shell extends CakeObject {
*
* @param string $title Title of the error
* @param string $message An optional error message
* @return void
* @return int
* @link https://book.cakephp.org/2.0/en/console-and-shells.html#Shell::error
*/
public function error($title, $message = null) {

View file

@ -52,7 +52,7 @@ class DbAcl extends CakeObject implements AclInterface {
/**
* Initializes the containing component and sets the Aro/Aco objects to it.
*
* @param AclComponent $component The AclComponent instance.
* @param Component $component The AclComponent instance.
* @return void
*/
public function initialize(Component $component) {

View file

@ -7,8 +7,8 @@
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of the files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @license https://opensource.org/licenses/mit-license.php MIT License
*/

View file

@ -44,7 +44,7 @@ App::uses('BasicAuthenticate', 'Controller/Component/Auth');
* Due to the Digest authentication specification, digest auth requires a special password value. You
* can generate this password using `DigestAuthenticate::password()`
*
* `$digestPass = DigestAuthenticate::password($username, env('SERVER_NAME'), $password);`
* `$digestPass = DigestAuthenticate::password($username, $password, env('SERVER_NAME'));`
*
* Its recommended that you store this digest auth only password separate from password hashes used for other
* login methods. For example `User.digest_pass` could be used for a digest password, while `User.password` would
@ -136,7 +136,7 @@ class DigestAuthenticate extends BasicAuthenticate {
/**
* Gets the digest headers from the request/environment.
*
* @return array Array of digest information.
* @return array|bool|null Array of digest information.
*/
protected function _getDigest() {
$digest = env('PHP_AUTH_DIGEST');

View file

@ -761,7 +761,7 @@ class AuthComponent extends Component {
*
* @param CakeRequest $request The request that contains authentication data.
* @param CakeResponse $response The response
* @return array User record data, or false, if the user could not be identified.
* @return array|bool User record data, or false, if the user could not be identified.
*/
public function identify(CakeRequest $request, CakeResponse $response) {
if (empty($this->_authenticateObjects)) {

View file

@ -229,27 +229,14 @@ class CookieComponent extends Component {
}
foreach ($key as $name => $value) {
$names = array($name);
if (strpos($name, '.') !== false) {
$names = explode('.', $name, 2);
}
$firstName = $names[0];
$isMultiValue = (is_array($value) || count($names) > 1);
if (!isset($this->_values[$this->name][$firstName]) && $isMultiValue) {
$this->_values[$this->name][$firstName] = array();
}
if (count($names) > 1) {
$this->_values[$this->name][$firstName] = Hash::insert(
$this->_values[$this->name][$firstName],
$names[1],
$value
);
$this->_values[$this->name] = Hash::insert($this->_values[$this->name], $name, $value);
list($name) = explode('.', $name, 2);
$value = $this->_values[$this->name][$name];
} else {
$this->_values[$this->name][$firstName] = $value;
$this->_values[$this->name][$name] = $value;
}
$this->_write('[' . $firstName . ']', $this->_values[$this->name][$firstName]);
$this->_write('[' . $name . ']', $value);
}
$this->_encrypted = true;
}
@ -274,22 +261,7 @@ class CookieComponent extends Component {
if ($key === null) {
return $this->_values[$this->name];
}
if (strpos($key, '.') !== false) {
$names = explode('.', $key, 2);
$key = $names[0];
}
if (!isset($this->_values[$this->name][$key])) {
return null;
}
if (!empty($names[1])) {
if (is_array($this->_values[$this->name][$key])) {
return Hash::get($this->_values[$this->name][$key], $names[1]);
}
return null;
}
return $this->_values[$this->name][$key];
return Hash::get($this->_values[$this->name], $key);
}
/**
@ -329,20 +301,16 @@ class CookieComponent extends Component {
$this->read();
}
if (strpos($key, '.') === false) {
if (isset($this->_values[$this->name][$key]) && is_array($this->_values[$this->name][$key])) {
foreach ($this->_values[$this->name][$key] as $idx => $val) {
$this->_delete("[$key][$idx]");
}
}
$this->_delete("[$key]");
unset($this->_values[$this->name][$key]);
return;
$this->_delete('[' . $key . ']');
} else {
$this->_values[$this->name] = Hash::remove((array)$this->_values[$this->name], $key);
list($key) = explode('.', $key, 2);
if (isset($this->_values[$this->name][$key])) {
$value = $this->_values[$this->name][$key];
$this->_write('[' . $key . ']', $value);
}
}
$names = explode('.', $key, 2);
if (isset($this->_values[$this->name][$names[0]]) && is_array($this->_values[$this->name][$names[0]])) {
$this->_values[$this->name][$names[0]] = Hash::remove($this->_values[$this->name][$names[0]], $names[1]);
}
$this->_delete('[' . implode('][', $names) . ']');
}
/**
@ -360,14 +328,7 @@ class CookieComponent extends Component {
}
foreach ($this->_values[$this->name] as $name => $value) {
if (is_array($value)) {
foreach ($value as $key => $val) {
unset($this->_values[$this->name][$name][$key]);
$this->_delete("[$name][$key]");
}
}
unset($this->_values[$this->name][$name]);
$this->_delete("[$name]");
$this->delete($name);
}
}
@ -494,7 +455,7 @@ class CookieComponent extends Component {
* Decrypts $value using public $type method in Security class
*
* @param array $values Values to decrypt
* @return string decrypted string
* @return array decrypted string
*/
protected function _decrypt($values) {
$decrypted = array();
@ -516,7 +477,7 @@ class CookieComponent extends Component {
* Decodes and decrypts a single value.
*
* @param string $value The value to decode & decrypt.
* @return string Decoded value.
* @return string|array Decoded value.
*/
protected function _decode($value) {
$prefix = 'Q2FrZQ==.';
@ -552,7 +513,7 @@ class CookieComponent extends Component {
* Maintains reading backwards compatibility with 1.x CookieComponent::_implode().
*
* @param string $string A string containing JSON encoded data, or a bare string.
* @return array Map of key and values
* @return string|array Map of key and values
*/
protected function _explode($string) {
$first = substr($string, 0, 1);

View file

@ -282,7 +282,7 @@ class EmailComponent extends Component {
* If you are rendering a template this variable will be sent to the templates as `$content`
* @param string $template Template to use when sending email
* @param string $layout Layout to use to enclose email body
* @return bool Success
* @return array Success
*/
public function send($content = null, $template = null, $layout = null) {
$lib = new CakeEmail();

View file

@ -136,7 +136,13 @@ class SessionComponent extends Component {
* @deprecated 3.0.0 Since 2.7, use the FlashComponent instead.
*/
public function setFlash($message, $element = 'default', $params = array(), $key = 'flash') {
CakeSession::write('Message.' . $key, compact('message', 'element', 'params'));
$messages = (array)CakeSession::read('Message.' . $key);
$messages[] = array(
'message' => $message,
'element' => $element,
'params' => $params,
);
CakeSession::write('Message.' . $key, $messages);
}
/**
@ -147,7 +153,7 @@ class SessionComponent extends Component {
* @return void
*/
public function renew() {
return CakeSession::renew();
CakeSession::renew();
}
/**
@ -170,7 +176,7 @@ class SessionComponent extends Component {
* @link https://book.cakephp.org/2.0/en/core-libraries/components/sessions.html#SessionComponent::destroy
*/
public function destroy() {
return CakeSession::destroy();
CakeSession::destroy();
}
/**

View file

@ -111,7 +111,7 @@ class CakeEvent {
/**
* Stops the event from being used anymore
*
* @return void
* @return bool
*/
public function stopPropagation() {
return $this->_stopped = true;

View file

@ -432,7 +432,7 @@ class TranslateBehavior extends ModelBehavior {
* is disabled.
*
* @param Model $Model Model using this behavior.
* @return void
* @return bool true.
*/
protected function _setRuntimeData(Model $Model) {
$locale = $this->_getLocale($Model);
@ -465,7 +465,7 @@ class TranslateBehavior extends ModelBehavior {
* This solves issues with saveAssociated and validate = first.
*
* @param Model $Model Model using this behavior.
* @return void
* @return bool true.
*/
public function afterValidate(Model $Model) {
$Model->data[$Model->alias] = array_merge(
@ -481,7 +481,7 @@ class TranslateBehavior extends ModelBehavior {
* @param Model $Model Model the callback is called on
* @param bool $created Whether or not the save created a record.
* @param array $options Options passed from Model::save().
* @return void
* @return bool true.
*/
public function afterSave(Model $Model, $created, $options = array()) {
if (!isset($this->runtime[$Model->alias]['beforeValidate']) && !isset($this->runtime[$Model->alias]['beforeSave'])) {

View file

@ -73,7 +73,7 @@ class BehaviorCollection extends ObjectCollection implements CakeEventListener {
*
* @param string $behavior Behavior name.
* @param array $config Configuration options.
* @return void
* @return bool true.
* @deprecated 3.0.0 Will be removed in 3.0. Replaced with load().
*/
public function attach($behavior, $config = array()) {
@ -97,7 +97,7 @@ class BehaviorCollection extends ObjectCollection implements CakeEventListener {
*
* @param string $behavior CamelCased name of the behavior to load
* @param array $config Behavior configuration parameters
* @return bool True on success, false on failure
* @return bool True on success.
* @throws MissingBehaviorException when a behavior could not be found.
*/
public function load($behavior, $config = array()) {

View file

@ -134,6 +134,13 @@ class CakeSession {
*/
protected static $_cookieName = null;
/**
* Whether this session is running under a CLI environment
*
* @var bool
*/
protected static $_isCLI = false;
/**
* Pseudo constructor.
*
@ -155,6 +162,7 @@ class CakeSession {
}
static::$_initialized = true;
static::$_isCLI = (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg');
}
/**
@ -596,14 +604,18 @@ class CakeSession {
* @return bool
*/
protected static function _hasSession() {
return static::started() || isset($_COOKIE[static::_cookieName()]) || (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg');
return static::started()
|| !ini_get('session.use_cookies')
|| isset($_COOKIE[static::_cookieName()])
|| static::$_isCLI
|| (ini_get('session.use_trans_sid') && isset($_GET[session_name()]));
}
/**
* Find the handler class and make sure it implements the correct interface.
*
* @param string $handler Handler name.
* @return void
* @return CakeSessionHandlerInterface
* @throws CakeSessionException
*/
protected static function _getHandler($handler) {

View file

@ -212,7 +212,7 @@ class Sqlite extends DboSource {
* @param array $fields The fields to update.
* @param array $values The values to set columns to.
* @param mixed $conditions array of conditions to use.
* @return array
* @return bool
*/
public function update(Model $model, $fields = array(), $values = null, $conditions = null) {
if (empty($values) && !empty($fields)) {

View file

@ -72,7 +72,6 @@ class Sqlserver extends DboSource {
* @var array
*/
protected $_baseConfig = array(
'persistent' => true,
'host' => 'localhost\SQLEXPRESS',
'login' => '',
'password' => '',
@ -118,15 +117,24 @@ class Sqlserver extends DboSource {
/**
* Connects to the database using options in the given configuration array.
*
* Please note that the PDO::ATTR_PERSISTENT attribute is not supported by
* the SQL Server PHP PDO drivers. As a result you cannot use the
* persistent config option when connecting to a SQL Server (for more
* information see: https://github.com/Microsoft/msphpsql/issues/65).
*
* @return bool True if the database could be connected, else false
* @throws InvalidArgumentException if an unsupported setting is in the database config
* @throws MissingConnectionException
*/
public function connect() {
$config = $this->config;
$this->connected = false;
if (isset($config['persistent']) && $config['persistent']) {
throw new InvalidArgumentException('Config setting "persistent" cannot be set to true, as the Sqlserver PDO driver does not support PDO::ATTR_PERSISTENT');
}
$flags = $config['flags'] + array(
PDO::ATTR_PERSISTENT => $config['persistent'],
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
);
@ -403,7 +411,7 @@ class Sqlserver extends DboSource {
$rt = ' TOP';
}
$rt .= sprintf(' %u', $limit);
if (is_int($offset) && $offset > 0) {
if ((is_int($offset) || ctype_digit($offset)) && $offset > 0) {
$rt = sprintf(' OFFSET %u ROWS FETCH FIRST %u ROWS ONLY', $offset, $limit);
}
return $rt;

View file

@ -234,7 +234,7 @@ class ModelValidator implements ArrayAccess, IteratorAggregate, Countable {
* actually run validation rules over data, not just return the messages.
*
* @param string $options An optional array of custom options to be made available in the beforeValidate callback
* @return array Array of invalid fields
* @return array|bool Array of invalid fields
* @triggers Model.afterValidate $model
* @see ModelValidator::validates()
*/

View file

@ -70,7 +70,7 @@ class CakeEmail {
*
* @var string
*/
const EMAIL_PATTERN = '/^((?:[\p{L}0-9.!#$%&\'*+\/=?^_`{|}~-]+)*@[\p{L}0-9-.]+)$/ui';
const EMAIL_PATTERN = '/^((?:[\p{L}0-9.!#$%&\'*+\/=?^_`{|}~-]+)*@[\p{L}0-9-_.]+)$/ui';
/**
* Recipient of the email

View file

@ -760,7 +760,7 @@ class HttpSocket extends CakeSocket {
*
* @param string|array $uri URI to parse
* @param bool|array $base If true use default URI config, otherwise indexed array to set 'scheme', 'host', 'port', etc.
* @return array Parsed URI
* @return array|bool Parsed URI
*/
protected function _parseUri($uri = null, $base = array()) {
$uriBase = array(

View file

@ -259,7 +259,7 @@ class HttpSocketResponse implements ArrayAccess {
* Parses an array based header.
*
* @param array $header Header as an indexed array (field => value)
* @return array Parsed header
* @return array|bool Parsed header
*/
protected function _parseHeader($header) {
if (is_array($header)) {

View file

@ -429,6 +429,24 @@ class CookieComponentTest extends CakeTestCase {
$this->assertEquals($expected, $result);
}
/**
* Test that replacing scalar with array works.
*
* @return void
*/
public function testReplaceScalarWithArray() {
$this->Cookie->write('foo', 1);
$this->Cookie->write('foo.bar', 2);
$data = $this->Cookie->read();
$expected = array(
'foo' => array(
'bar' => 2
)
);
$this->assertEquals($expected, $data);
}
/**
* testReadingCookieValue
*
@ -696,6 +714,20 @@ class CookieComponentTest extends CakeTestCase {
$this->assertEquals(array(), $this->Cookie->read('Array'));
}
/**
* Test reading empty key
*
* @return void
*/
public function testReadEmptyKey() {
$_COOKIE['CakeTestCookie'] = array(
'0' => '{"name":"value"}',
'foo' => array('bar'),
);
$this->assertEquals('value', $this->Cookie->read('0.name'));
$this->assertEquals('bar', $this->Cookie->read('foo.0'));
}
/**
* test that no error is issued for non array data.
*
@ -789,6 +821,89 @@ class CookieComponentTest extends CakeTestCase {
$this->assertNull($this->Cookie->delete('Not.Found'));
}
/**
* Test deleting deep child elements sends correct cookies.
*
* @return void
*/
public function testDeleteDeepChildren() {
$_COOKIE = array(
'CakeTestCookie' => array(
'foo' => $this->_encrypt(array(
'bar' => array(
'baz' => 'value',
),
)),
),
);
$this->Cookie->delete('foo.bar.baz');
$cookies = $this->Controller->response->cookie();
$expected = array(
'CakeTestCookie[foo]' => array(
'name' => 'CakeTestCookie[foo]',
'value' => '{"bar":[]}',
'path' => '/',
'domain' => '',
'secure' => false,
'httpOnly' => false
),
);
$expires = Hash::combine($cookies, '{*}.name', '{*}.expire');
$cookies = Hash::remove($cookies, '{*}.expire');
$this->assertEquals($expected, $cookies);
$this->assertWithinMargin($expires['CakeTestCookie[foo]'], time() + 10, 2);
}
/**
* Test destroy works.
*
* @return void
*/
public function testDestroy() {
$_COOKIE = array(
'CakeTestCookie' => array(
'foo' => $this->_encrypt(array(
'bar' => array(
'baz' => 'value',
),
)),
'other' => 'value',
),
);
$this->Cookie->destroy();
$cookies = $this->Controller->response->cookie();
$expected = array(
'CakeTestCookie[foo]' => array(
'name' => 'CakeTestCookie[foo]',
'value' => '',
'path' => '/',
'domain' => '',
'secure' => false,
'httpOnly' => false
),
'CakeTestCookie[other]' => array(
'name' => 'CakeTestCookie[other]',
'value' => '',
'path' => '/',
'domain' => '',
'secure' => false,
'httpOnly' => false
),
);
$expires = Hash::combine($cookies, '{*}.name', '{*}.expire');
$cookies = Hash::remove($cookies, '{*}.expire');
$this->assertEquals($expected, $cookies);
$this->assertWithinMargin($expires['CakeTestCookie[foo]'], time() - 42000, 2);
$this->assertWithinMargin($expires['CakeTestCookie[other]'], time() - 42000, 2);
}
/**
* Helper method for generating old style encoded cookie values.
*

View file

@ -247,16 +247,13 @@ class SessionComponentTest extends CakeTestCase {
$this->assertNull($Session->read('Message.flash'));
$Session->setFlash('This is a test message');
$this->assertEquals(array('message' => 'This is a test message', 'element' => 'default', 'params' => array()), $Session->read('Message.flash'));
$this->assertEquals(array('message' => 'This is a test message', 'element' => 'default', 'params' => array()), $Session->read('Message.flash.0'));
$Session->setFlash('This is a test message', 'test', array('name' => 'Joel Moss'));
$this->assertEquals(array('message' => 'This is a test message', 'element' => 'test', 'params' => array('name' => 'Joel Moss')), $Session->read('Message.flash'));
$this->assertEquals(array('message' => 'This is a test message', 'element' => 'test', 'params' => array('name' => 'Joel Moss')), $Session->read('Message.flash.1'));
$Session->setFlash('This is a test message', 'default', array(), 'myFlash');
$this->assertEquals(array('message' => 'This is a test message', 'element' => 'default', 'params' => array()), $Session->read('Message.myFlash'));
$Session->setFlash('This is a test message', 'non_existing_layout');
$this->assertEquals(array('message' => 'This is a test message', 'element' => 'default', 'params' => array()), $Session->read('Message.myFlash'));
$this->assertEquals(array('message' => 'This is a test message', 'element' => 'default', 'params' => array()), $Session->read('Message.myFlash.0'));
$Session->delete('Message');
}

View file

@ -397,6 +397,13 @@ class SqlserverTest extends CakeTestCase {
));
$result = $this->db->getLastQuery();
$this->assertRegExp('/^SELECT DISTINCT TOP 5/', $result);
$this->db->read($this->model, array(
'fields' => array('DISTINCT SqlserverTestModel.city', 'SqlserverTestModel.country'),
'limit' => '5'
));
$result = $this->db->getLastQuery();
$this->assertRegExp('/^SELECT DISTINCT TOP 5/', $result);
}
/**

View file

@ -275,6 +275,10 @@ class CakeEmailTest extends CakeTestCase {
$expected = array('cake@cakephp.org' => 'CakePHP');
$this->assertSame($expected, $this->CakeEmail->to());
$this->CakeEmail->to('cake@cake_php.org', 'CakePHPUnderscore');
$expected = array('cake@cake_php.org' => 'CakePHPUnderscore');
$this->assertSame($expected, $this->CakeEmail->to());
$list = array(
'root@localhost' => 'root',
'bjørn@hammeröath.com' => 'Bjorn',

View file

@ -1356,7 +1356,7 @@ class HashTest extends CakeTestCase {
);
$this->assertEquals($expected, $result);
$result = Hash::sort($items, '{n}.Item.image', 'asc', array('type' => 'natural', 'ignoreCase' => true));
$result = Hash::sort($items, '{n}.Item.image', 'asc', array('type' => 'NATURAL', 'ignoreCase' => true));
$expected = array(
array('Item' => array('image' => 'img1.jpg')),
array('Item' => array('image' => 'img2.jpg')),
@ -1529,6 +1529,58 @@ class HashTest extends CakeTestCase {
$this->assertEquals($expected, $sorted);
}
/**
* Test sorting on a nested key that is sometimes undefined.
*
* @return void
*/
public function testSortSparse() {
$data = array(
array(
'id' => 1,
'title' => 'element 1',
'extra' => 1,
),
array(
'id' => 2,
'title' => 'element 2',
'extra' => 2,
),
array(
'id' => 3,
'title' => 'element 3',
),
array(
'id' => 4,
'title' => 'element 4',
'extra' => 4,
)
);
$result = Hash::sort($data, '{n}.extra', 'desc', 'natural');
$expected = array(
array(
'id' => 4,
'title' => 'element 4',
'extra' => 4,
),
array(
'id' => 2,
'title' => 'element 2',
'extra' => 2,
),
array(
'id' => 1,
'title' => 'element 1',
'extra' => 1,
),
array(
'id' => 3,
'title' => 'element 3',
),
);
$this->assertSame($expected, $result);
}
/**
* Test insert()
*
@ -1594,6 +1646,17 @@ class HashTest extends CakeTestCase {
4 => array('Item' => array('id' => 5, 'title' => 'fifth')),
);
$this->assertEquals($expected, $result);
$data[3]['testable'] = true;
$result = Hash::insert($data, '{n}[testable].Item[id=/\b2|\b4/].test', 2);
$expected = 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', 'test' => 2), 'testable' => true),
4 => array('Item' => array('id' => 5, 'title' => 'fifth')),
);
$this->assertEquals($expected, $result);
}
/**
@ -1686,6 +1749,43 @@ class HashTest extends CakeTestCase {
$this->assertEquals($expected, $result);
$result = Hash::remove($array, '{n}.{n}.part');
$this->assertEquals($expected, $result);
$array = array(
'foo' => 'string',
);
$expected = $array;
$result = Hash::remove($array, 'foo.bar');
$this->assertEquals($expected, $result);
$array = array(
'foo' => 'string',
'bar' => array(
0 => 'a',
1 => 'b',
),
);
$expected = array(
'foo' => 'string',
'bar' => array(
1 => 'b',
),
);
$result = Hash::remove($array, '{s}.0');
$this->assertEquals($expected, $result);
$array = array(
'foo' => array(
0 => 'a',
1 => 'b',
),
);
$expected = array(
'foo' => array(
1 => 'b',
),
);
$result = Hash::remove($array, 'foo[1=b].0');
$this->assertEquals($expected, $result);
}
/**
@ -1721,6 +1821,17 @@ class HashTest extends CakeTestCase {
4 => array('Item' => array('id' => 5, 'title' => 'fifth')),
);
$this->assertEquals($expected, $result);
$data[3]['testable'] = true;
$result = Hash::remove($data, '{n}[testable].Item[id=/\b2|\b4/].title');
$expected = 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), 'testable' => true),
4 => array('Item' => array('id' => 5, 'title' => 'fifth')),
);
$this->assertEquals($expected, $result);
}
/**

View file

@ -329,14 +329,14 @@ class ValidationTest extends CakeTestCase {
$this->assertTrue(Validation::cc('214981579370225', array('enroute')));
$this->assertTrue(Validation::cc('201447595859877', array('enroute')));
//JCB 15 digit
$this->assertTrue(Validation::cc('210034762247893', array('jcb')));
$this->assertTrue(Validation::cc('213134762247898', array('jcb')));
$this->assertTrue(Validation::cc('180078671678892', array('jcb')));
$this->assertTrue(Validation::cc('180010559353736', array('jcb')));
$this->assertTrue(Validation::cc('210095474464258', array('jcb')));
$this->assertTrue(Validation::cc('210006675562188', array('jcb')));
$this->assertTrue(Validation::cc('210063299662662', array('jcb')));
$this->assertTrue(Validation::cc('213195474464253', array('jcb')));
$this->assertTrue(Validation::cc('213106675562183', array('jcb')));
$this->assertTrue(Validation::cc('213163299662667', array('jcb')));
$this->assertTrue(Validation::cc('180032506857825', array('jcb')));
$this->assertTrue(Validation::cc('210057919192738', array('jcb')));
$this->assertTrue(Validation::cc('213157919192733', array('jcb')));
$this->assertTrue(Validation::cc('180031358949367', array('jcb')));
$this->assertTrue(Validation::cc('180033802147846', array('jcb')));
//JCB 16 digit
@ -706,7 +706,7 @@ class ValidationTest extends CakeTestCase {
//enRoute
$this->assertTrue(Validation::luhn('201496944158937', true));
//JCB 15 digit
$this->assertTrue(Validation::luhn('210034762247893', true));
$this->assertTrue(Validation::luhn('213134762247898', true));
//JCB 16 digit
$this->assertTrue(Validation::luhn('3096806857839939', true));
//Maestro (debit card)
@ -811,7 +811,7 @@ class ValidationTest extends CakeTestCase {
//enRoute
$this->assertTrue(Validation::cc('201496944158937', 'all'));
//JCB 15 digit
$this->assertTrue(Validation::cc('210034762247893', 'all'));
$this->assertTrue(Validation::cc('213134762247898', 'all'));
//JCB 16 digit
$this->assertTrue(Validation::cc('3096806857839939', 'all'));
//Maestro (debit card)
@ -861,7 +861,7 @@ class ValidationTest extends CakeTestCase {
//enRoute
$this->assertTrue(Validation::cc('201496944158937', 'all', true));
//JCB 15 digit
$this->assertTrue(Validation::cc('210034762247893', 'all', true));
$this->assertTrue(Validation::cc('213134762247898', 'all', true));
//JCB 16 digit
$this->assertTrue(Validation::cc('3096806857839939', 'all', true));
//Maestro (debit card)

View file

@ -4944,6 +4944,30 @@ class FormHelperTest extends CakeTestCase {
$this->assertTags($result, $expected);
}
/**
* test setting a hiddenField value
*
* @return void
*/
public function testRadioHiddenFieldValue() {
$result = $this->Form->input('Model.1.field', array(
'type' => 'radio',
'options' => array('option A'),
'hiddenField' => 'N'
)
);
$expected = array(
'div' => array('class' => 'input radio'),
array('input' => array('type' => 'hidden', 'name' => 'data[Model][1][field]', 'value' => 'N', 'id' => 'Model1Field_')),
array('input' => array('type' => 'radio', 'name' => 'data[Model][1][field]', 'value' => '0', 'id' => 'Model1Field0')),
'label' => array('for' => 'Model1Field0'),
'option A',
'/label',
'/div'
);
$this->assertTags($result, $expected);
}
/**
* test adding an empty option for radio buttons
*

View file

@ -47,24 +47,32 @@ class SessionHelperTest extends CakeTestCase {
'test' => 'info',
'Message' => array(
'flash' => array(
'element' => 'default',
'params' => array(),
'message' => 'This is a calling'
array(
'element' => 'default',
'params' => array(),
'message' => 'This is a calling'
),
),
'notification' => array(
'element' => 'session_helper',
'params' => array('title' => 'Notice!', 'name' => 'Alert!'),
'message' => 'This is a test of the emergency broadcasting system',
array(
'element' => 'session_helper',
'params' => array('title' => 'Notice!', 'name' => 'Alert!'),
'message' => 'This is a test of the emergency broadcasting system',
),
),
'classy' => array(
'element' => 'default',
'params' => array('class' => 'positive'),
'message' => 'Recorded'
array(
'element' => 'default',
'params' => array('class' => 'positive'),
'message' => 'Recorded'
),
),
'bare' => array(
'element' => null,
'message' => 'Bare message',
'params' => array(),
array(
'element' => null,
'message' => 'Bare message',
'params' => array(),
),
),
),
'Deeply' => array('nested' => array('key' => 'value')),
@ -104,7 +112,7 @@ class SessionHelperTest extends CakeTestCase {
public function testCheck() {
$this->assertTrue($this->Session->check('test'));
$this->assertTrue($this->Session->check('Message.flash.element'));
$this->assertTrue($this->Session->check('Message.flash.0.element'));
$this->assertFalse($this->Session->check('Does.not.exist'));

View file

@ -175,7 +175,7 @@ class CakeFixtureManager {
}
/**
* Runs the drop and create commands on the fixtures if necessary.
* Runs the drop, create and truncate commands on the fixtures if necessary.
*
* @param CakeTestFixture $fixture the fixture object to create
* @param DataSource $db the datasource instance to use
@ -191,6 +191,7 @@ class CakeFixtureManager {
}
}
if (!empty($fixture->created) && in_array($db->configKeyName, $fixture->created)) {
$fixture->truncate($db);
return;
}
@ -205,6 +206,7 @@ class CakeFixtureManager {
$fixture->create($db);
} else {
$fixture->created[] = $db->configKeyName;
$fixture->truncate($db);
}
}
@ -229,7 +231,6 @@ class CakeFixtureManager {
$db = ConnectionManager::getDataSource($fixture->useDbConfig);
$db->begin();
$this->_setupTable($fixture, $db, $test->dropTables);
$fixture->truncate($db);
$fixture->insert($db);
$db->commit();
}
@ -274,7 +275,6 @@ class CakeFixtureManager {
$db = ConnectionManager::getDataSource($fixture->useDbConfig);
}
$this->_setupTable($fixture, $db, $dropTables);
$fixture->truncate($db);
$fixture->insert($db);
} else {
throw new UnexpectedValueException(__d('cake_dev', 'Referenced fixture class %s not found', $name));

View file

@ -265,7 +265,16 @@ class CakeHtmlReporter extends CakeBaseReporter {
echo "<div class='msg'><pre>" . $this->_htmlEntities($message->toString());
if ((is_string($actualMsg) && is_string($expectedMsg)) || (is_array($actualMsg) && is_array($expectedMsg))) {
echo "<br />" . $this->_htmlEntities(PHPUnit_Util_Diff::diff($expectedMsg, $actualMsg));
$diffs = "";
if (class_exists('PHPUnit_Util_Diff')) {
$diffs = PHPUnit_Util_Diff::diff($expectedMsg, $actualMsg);
} elseif (class_exists('SebastianBergmann\Diff\Differ')) {
$differ = new SebastianBergmann\Diff\Differ();
$diffs = $differ->diff($expectedMsg, $actualMsg);
}
echo "<br />" . $this->_htmlEntities($diffs);
}
echo "</pre></div>\n";

View file

@ -323,7 +323,7 @@ class Folder {
* Returns true if given $path is a registered stream wrapper.
*
* @param string $path Path to check
* @return boo true If path is registered stream wrapper.
* @return bool true If path is registered stream wrapper.
*/
public static function isRegisteredStreamWrapper($path) {
if (preg_match('/^[A-Z]+(?=:\/\/)/i', $path, $matches) &&

View file

@ -91,8 +91,8 @@ class Hash {
*
* - `1.User.name` Get the name of the user at index 1.
* - `{n}.User.name` Get the name of every user in the set of users.
* - `{n}.User[id]` Get the name of every user with an id key.
* - `{n}.User[id>=2]` Get the name of every user with an id key greater than or equal to 2.
* - `{n}.User[id].name` Get the name of every user with an id key.
* - `{n}.User[id>=2].name` Get the name of every user with an id key greater than or equal to 2.
* - `{n}.User[username=/^paul/]` Get User elements with username matching `^paul`.
*
* @param array $data The data to extract from.
@ -149,6 +149,7 @@ class Hash {
}
return $context[$_key];
}
/**
* Split token conditions
*
@ -274,12 +275,10 @@ class Hash {
foreach ($data as $k => $v) {
if (static::_matchToken($k, $token)) {
if ($conditions && static::_matches($v, $conditions)) {
$data[$k] = array_merge($v, $values);
continue;
}
if (!$conditions) {
$data[$k] = static::insert($v, $nextPath, $values);
if (!$conditions || static::_matches($v, $conditions)) {
$data[$k] = $nextPath
? static::insert($v, $nextPath, $values)
: array_merge($v, (array)$values);
}
}
}
@ -301,9 +300,6 @@ class Hash {
$count = count($path);
$last = $count - 1;
foreach ($path as $i => $key) {
if ((is_numeric($key) && intval($key) > 0 || $key === '0') && strpos($key, '0') !== 0) {
$key = (int)$key;
}
if ($op === 'insert') {
if ($i === $last) {
$_list[$key] = $values;
@ -318,7 +314,9 @@ class Hash {
}
} elseif ($op === 'remove') {
if ($i === $last) {
unset($_list[$key]);
if (is_array($_list)) {
unset($_list[$key]);
}
return $data;
}
if (!isset($_list[$key])) {
@ -358,15 +356,21 @@ class Hash {
foreach ($data as $k => $v) {
$match = static::_matchToken($k, $token);
if ($match && is_array($v)) {
if ($conditions && static::_matches($v, $conditions)) {
unset($data[$k]);
continue;
if ($conditions) {
if (static::_matches($v, $conditions)) {
if ($nextPath !== '') {
$data[$k] = static::remove($v, $nextPath);
} else {
unset($data[$k]);
}
}
} else {
$data[$k] = static::remove($v, $nextPath);
}
$data[$k] = static::remove($v, $nextPath);
if (empty($data[$k])) {
unset($data[$k]);
}
} elseif ($match && empty($nextPath)) {
} elseif ($match && $nextPath === '') {
unset($data[$k]);
}
}
@ -454,7 +458,7 @@ class Hash {
* The `$format` string can use any format options that `vsprintf()` and `sprintf()` do.
*
* @param array $data Source array from which to extract the data
* @param string $paths An array containing one or more Hash::extract()-style key paths
* @param array $paths An array containing one or more Hash::extract()-style key paths
* @param string $format Format string into which values will be inserted, see sprintf()
* @return array An array of strings extracted from `$path` and formatted with `$format`
* @link https://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::format
@ -729,7 +733,7 @@ class Hash {
* Counts the dimensions of an array.
* Only considers the dimension of the first element in the array.
*
* If you have an un-even or heterogenous array, consider using Hash::maxDimensions()
* If you have an un-even or heterogeneous array, consider using Hash::maxDimensions()
* to get the dimensions of the array.
*
* @param array $data Array to count dimensions on
@ -809,11 +813,15 @@ class Hash {
* You can easily count the results of an extract using apply().
* For example to count the comments on an Article:
*
* `$count = Hash::apply($data, 'Article.Comment.{n}', 'count');`
* ```
* $count = Hash::apply($data, 'Article.Comment.{n}', 'count');
* ```
*
* You could also use a function like `array_sum` to sum the results.
*
* `$total = Hash::apply($data, '{n}.Item.price', 'array_sum');`
* ```
* $total = Hash::apply($data, '{n}.Item.price', 'array_sum');
* ```
*
* @param array $data The data to reduce.
* @param string $path The path to extract from $data.
@ -833,7 +841,7 @@ class Hash {
* - `asc` Sort ascending.
* - `desc` Sort descending.
*
* ## Sort types
* ### Sort types
*
* - `regular` For regular sorting (don't change types)
* - `numeric` Compare values numerically
@ -868,12 +876,18 @@ class Hash {
$data = array_values($data);
}
$sortValues = static::extract($data, $path);
$sortCount = count($sortValues);
$dataCount = count($data);
// Make sortValues match the data length, as some keys could be missing
// the sorted value path.
if ($sortCount < $dataCount) {
$missingData = count($sortValues) < $dataCount;
if ($missingData && $numeric) {
// Get the path without the leading '{n}.'
$itemPath = substr($path, 4);
foreach ($data as $key => $value) {
$sortValues[$key] = static::get($value, $itemPath);
}
} elseif ($missingData) {
$sortValues = array_pad($sortValues, $dataCount, null);
}
$result = static::_squash($sortValues);
@ -888,9 +902,8 @@ class Hash {
$type += array('ignoreCase' => false, 'type' => 'regular');
$ignoreCase = $type['ignoreCase'];
$type = $type['type'];
} else {
$type = strtolower($type);
}
$type = strtolower($type);
if ($type === 'natural' && version_compare(PHP_VERSION, '5.4.0', '<')) {
$type = 'regular';

View file

@ -34,9 +34,9 @@ class Sanitize {
/**
* Removes any non-alphanumeric characters.
*
* @param string $string String to sanitize
* @param string|array $string String to sanitize
* @param array $allowed An array of additional characters that are not to be removed.
* @return string Sanitized string
* @return string|array Sanitized string
*/
public static function paranoid($string, $allowed = array()) {
$allow = null;

View file

@ -175,7 +175,7 @@ class Validation {
'disc' => '/^(?:6011|650\\d)\\d{12}$/',
'electron' => '/^(?:417500|4917\\d{2}|4913\\d{2})\\d{10}$/',
'enroute' => '/^2(?:014|149)\\d{11}$/',
'jcb' => '/^(3\\d{4}|2100|1800)\\d{11}$/',
'jcb' => '/^(3\\d{4}|2131|1800)\\d{11}$/',
'maestro' => '/^(?:5020|6\\d{3})\\d{12}$/',
'mc' => '/^(5[1-5]\\d{14})|(2(?:22[1-9]|2[3-9][0-9]|[3-6][0-9]{2}|7[0-1][0-9]|720)\\d{12})$/',
'solo' => '/^(6334[5-9][0-9]|6767[0-9]{2})\\d{10}(\\d{2,3})?$/',
@ -478,7 +478,7 @@ class Validation {
if (function_exists('checkdnsrr') && checkdnsrr($regs[1], 'MX')) {
return true;
}
return is_array(gethostbynamel($regs[1]));
return is_array(gethostbynamel($regs[1] . '.'));
}
return false;
}

View file

@ -17,4 +17,4 @@
// @license https://opensource.org/licenses/mit-license.php MIT License
// +--------------------------------------------------------------------------------------------+ //
////////////////////////////////////////////////////////////////////////////////////////////////////
2.10.0
2.10.5

View file

@ -1674,7 +1674,7 @@ class FormHelper extends AppHelper {
$hidden = $this->hidden($fieldName, array(
'form' => isset($attributes['form']) ? $attributes['form'] : null,
'id' => $attributes['id'] . '_',
'value' => '',
'value' => $hiddenField === true ? '' : $hiddenField,
'name' => $attributes['name']
));
}

View file

@ -134,30 +134,14 @@ class SessionHelper extends AppHelper {
if (CakeSession::check('Message.' . $key)) {
$flash = CakeSession::read('Message.' . $key);
CakeSession::delete('Message.' . $key);
$message = $flash['message'];
unset($flash['message']);
if (!empty($attrs)) {
$flash = array_merge($flash, $attrs);
}
if ($flash['element'] === 'default') {
$class = 'message';
if (!empty($flash['params']['class'])) {
$class = $flash['params']['class'];
$out = '';
foreach ($flash as $flashArray) {
if (!empty($attrs)) {
$flashArray = array_merge($flashArray, $attrs);
}
$out = '<div id="' . $key . 'Message" class="' . $class . '">' . $message . '</div>';
} elseif (!$flash['element']) {
$out = $message;
} else {
$options = array();
if (isset($flash['params']['plugin'])) {
$options['plugin'] = $flash['params']['plugin'];
}
$tmpVars = $flash['params'];
$tmpVars['message'] = $message;
$tmpVars['key'] = $key;
$out = $this->_View->element($flash['element'], $tmpVars, $options);
$flashArray['key'] = $key;
$out .= $this->_render($flashArray);
}
}
return $out;
@ -173,4 +157,34 @@ class SessionHelper extends AppHelper {
return CakeSession::valid();
}
/**
* Renders a flash message
*
* @param array $flash Flash message array
* @return string
*/
protected function _render($flash) {
$message = $flash['message'];
unset($flash['message']);
if ($flash['element'] === 'default') {
$class = 'message';
if (!empty($flash['params']['class'])) {
$class = $flash['params']['class'];
}
$out = '<div id="' . $flash['key'] . 'Message" class="' . $class . '">' . $message . '</div>';
} elseif (!$flash['element']) {
$out = $message;
} else {
$options = array();
if (isset($flash['params']['plugin'])) {
$options['plugin'] = $flash['params']['plugin'];
}
$tmpVars = $flash['params'];
$tmpVars['message'] = $message;
$tmpVars['key'] = $flash['key'];
$out = $this->_View->element($flash['element'], $tmpVars, $options);
}
return $out;
}
}

View file

@ -196,7 +196,7 @@ if (!function_exists('h')) {
* implement a `__toString` method. Otherwise the class name will be used.
* @param bool $double Encode existing html entities
* @param string $charset Character set to use when escaping. Defaults to config value in 'App.encoding' or 'UTF-8'
* @return string Wrapped text
* @return string|array|object Wrapped text, Wrapped Array or Wrapped Object
* @link https://book.cakephp.org/2.0/en/core-libraries/global-constants-and-functions.html#h
*/
function h($text, $double = true, $charset = null) {