Merge pull request #7 from cakephp/2.x

Sync 2.x with upstream
This commit is contained in:
Val Bancer 2018-08-13 12:22:57 +02:00 committed by GitHub
commit 87677d7004
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 248 additions and 64 deletions

View file

@ -40,7 +40,7 @@ CREATE TABLE aros (
PRIMARY KEY (id)
);
/* this indexes will improve acl perfomance */
/* this indexes will improve acl performance */
CREATE INDEX idx_acos_lft_rght ON `acos` (`lft`, `rght`);
CREATE INDEX idx_acos_alias ON `acos` (`alias`);

View file

@ -18,8 +18,11 @@
"source": "https://github.com/cakephp/cakephp"
},
"require": {
"php": ">=5.3.0",
"ext-mcrypt": "*"
"php": ">=5.3.0"
},
"suggest": {
"ext-openssl": "You need to install ext-openssl or ext-mcrypt to use AES-256 encryption",
"ext-mcrypt": "You need to install ext-openssl or ext-mcrypt to use AES-256 encryption"
},
"require-dev": {
"phpunit/phpunit": "^3.7",

View file

@ -45,9 +45,9 @@ class SessionComponent extends Component {
*
* In your controller: $this->Session->write('Controller.sessKey', 'session value');
*
* @param string $name The name of the key your are setting in the session.
* @param string|array $name The name of the key your are setting in the session.
* This should be in a Controller.key format for better organizing
* @param string $value The value you want to store in a session.
* @param string|array $value The value you want to store in a session.
* @return bool Success
* @link https://book.cakephp.org/2.0/en/core-libraries/components/sessions.html#SessionComponent::write
*/

View file

@ -591,7 +591,7 @@ class App {
*
* @param string|array $type The type of Class if passed as a string, or all params can be passed as
* a single array to $type.
* @param string $name Name of the Class or a unique name for the file
* @param string|array $name Name of the Class or a unique name for the file
* @param bool|array $parent boolean true if Class Parent should be searched, accepts key => value
* array('parent' => $parent, 'file' => $file, 'search' => $search, 'ext' => '$ext');
* $ext allows setting the extension of the file name

View file

@ -137,6 +137,7 @@ class L10n {
/* Latvian */ 'lav' => 'lv',
/* Limburgish */ 'lim' => 'li',
/* Lithuanian */ 'lit' => 'lt',
/* Luxembourgish */ 'ltz' => 'lb',
/* Macedonian */ 'mkd' => 'mk',
/* Macedonian - bibliographic */ 'mac' => 'mk',
/* Malaysian */ 'msa' => 'ms',
@ -284,6 +285,7 @@ class L10n {
'ko-kp' => array('language' => 'Korea (North)', 'locale' => 'ko_kp', 'localeFallback' => 'kor', 'charset' => 'kr', 'direction' => 'ltr'),
'ko-kr' => array('language' => 'Korea (South)', 'locale' => 'ko_kr', 'localeFallback' => 'kor', 'charset' => 'kr', 'direction' => 'ltr'),
'koi8-r' => array('language' => 'Russian', 'locale' => 'koi8_r', 'localeFallback' => 'rus', 'charset' => 'koi8-r', 'direction' => 'ltr'),
'lb' => array('language' => 'Luxembourgish', 'locale' => 'ltz', 'localeFallback' => 'ltz', 'charset' => 'utf-8', 'direction' => 'ltr'),
'li' => array('language' => 'Limburgish', 'locale' => 'lim', 'localeFallback' => 'nld', 'charset' => 'utf-8', 'direction' => 'ltr'),
'lt' => array('language' => 'Lithuanian', 'locale' => 'lit', 'localeFallback' => 'lit', 'charset' => 'utf-8', 'direction' => 'ltr'),
'lv' => array('language' => 'Latvian', 'locale' => 'lav', 'localeFallback' => 'lav', 'charset' => 'utf-8', 'direction' => 'ltr'),

View file

@ -344,7 +344,7 @@ class TranslateBehavior extends ModelBehavior {
public function afterFind(Model $Model, $results, $primary = false) {
$Model->virtualFields = $this->runtime[$Model->alias]['virtualFields'];
$this->runtime[$Model->alias]['virtualFields'] = $this->runtime[$Model->alias]['fields'] = array();
$this->runtime[$Model->alias]['virtualFields'] = array();
if (!empty($this->runtime[$Model->alias]['restoreFields'])) {
$this->runtime[$Model->alias]['fields'] = $this->runtime[$Model->alias]['restoreFields'];
unset($this->runtime[$Model->alias]['restoreFields']);

View file

@ -432,7 +432,7 @@ class CakeSession {
* Writes value to given session variable name.
*
* @param string|array $name Name of variable
* @param string $value Value to write
* @param string|array $value Value to write
* @return bool True if the write was successful, false if the write failed
*/
public static function write($name, $value = null) {

View file

@ -109,7 +109,7 @@ class DboSource extends DataSource {
/**
* Result
*
* @var array
* @var array|PDOStatement
*/
protected $_result = null;
@ -1076,7 +1076,7 @@ class DboSource extends DataSource {
for ($i = 0; $i < $count; $i++) {
$schema = $Model->schema();
$valueInsert[] = $this->value($values[$i], $Model->getColumnType($fields[$i]), isset($schema[$fields[$i]]) ? $schema[$fields[$i]]['null'] : true);
$valueInsert[] = $this->value($values[$i], $Model->getColumnType($fields[$i]), isset($schema[$fields[$i]]['null']) ? $schema[$fields[$i]]['null'] : true);
$fieldInsert[] = $this->name($fields[$i]);
if ($fields[$i] === $Model->primaryKey) {
$id = $values[$i];
@ -1566,6 +1566,7 @@ class DboSource extends DataSource {
// Make one pass through children and collect by parent key
// Make second pass through parents and associate children
$mergedByFK = array();
if (is_array($assocResultSet)) {
foreach ($assocResultSet as $data) {
$fk = $data[$association][$foreignKey];
if (! array_key_exists($fk, $mergedByFK)) {
@ -1585,6 +1586,7 @@ class DboSource extends DataSource {
$mergedByFK[$fk][] = $data[$association];
}
}
}
foreach ($resultSet as &$result) {
if (!isset($result[$modelAlias])) {
@ -2175,7 +2177,7 @@ class DboSource extends DataSource {
$update = $quoted . ' = ';
if ($quoteValues) {
$update .= $this->value($value, $Model->getColumnType($field), isset($schema[$field]) ? $schema[$field]['null'] : true);
$update .= $this->value($value, $Model->getColumnType($field), isset($schema[$field]['null']) ? $schema[$field]['null'] : true);
} elseif ($Model->getColumnType($field) === 'boolean' && (is_int($value) || is_bool($value))) {
$update .= $this->boolean($value, true);
} elseif (!$alias) {
@ -2576,7 +2578,12 @@ class DboSource extends DataSource {
$virtual = array();
foreach ($fields as $field) {
$virtualField = $this->name($alias . $this->virtualFieldSeparator . $field);
$expression = $this->_quoteFields($Model->getVirtualField($field));
$virtualFieldExpression = $Model->getVirtualField($field);
if (is_object($virtualFieldExpression) && $virtualFieldExpression->type == 'expression') {
$expression = $virtualFieldExpression->value;
} else {
$expression = $this->_quoteFields($virtualFieldExpression);
}
$virtual[] = '(' . $expression . ") {$this->alias} {$virtualField}";
}
return $virtual;
@ -2885,7 +2892,12 @@ class DboSource extends DataSource {
if ($Model !== null) {
if ($Model->isVirtualField($key)) {
$key = $this->_quoteFields($Model->getVirtualField($key));
$virtualField = $Model->getVirtualField($key);
if (is_object($virtualField) && $virtualField->type == 'expression') {
$key = $virtualField->value;
} else {
$key = $this->_quoteFields($virtualField);
}
$virtual = true;
}
@ -3034,12 +3046,12 @@ class DboSource extends DataSource {
if (!is_array($keys)) {
$keys = array($keys);
}
$keys = array_filter($keys);
$result = array();
while (!empty($keys)) {
list($key, $dir) = each($keys);
$key = key($keys);
$dir = current($keys);
array_shift($keys);
if (is_numeric($key)) {

View file

@ -1648,7 +1648,7 @@ class Model extends CakeObject implements CakeEventListener {
*
* @param string $name The name of the field to get.
* @param array $conditions SQL conditions (defaults to NULL).
* @param string $order SQL ORDER BY fragment.
* @param string|array $order SQL ORDER BY fragment.
* @return string|false Field content, or false if not found.
* @link https://book.cakephp.org/2.0/en/models/retrieving-your-data.html#model-field
*/

View file

@ -594,7 +594,7 @@ class ModelValidator implements ArrayAccess, IteratorAggregate, Countable {
$this->_parseRules();
if ($rule === null) {
unset($this->_fields[$field]);
} else {
} elseif (array_key_exists($field, $this->_fields)) {
$this->_fields[$field]->removeRule($rule);
}
return $this;

View file

@ -589,7 +589,7 @@ class CakeEmail {
*/
protected function _setEmail($varName, $email, $name) {
if (!is_array($email)) {
$this->_validateEmail($email);
$this->_validateEmail($email, $varName);
if ($name === null) {
$name = $email;
}
@ -601,7 +601,7 @@ class CakeEmail {
if (is_int($key)) {
$key = $value;
}
$this->_validateEmail($key);
$this->_validateEmail($key, $varName);
$list[$key] = $value;
}
$this->{$varName} = $list;
@ -611,11 +611,12 @@ class CakeEmail {
/**
* Validate email address
*
* @param string $email Email
* @param string $email Email address to validate
* @param string $context Which property was set
* @return void
* @throws SocketException If email address does not validate
*/
protected function _validateEmail($email) {
protected function _validateEmail($email, $context) {
if ($this->_emailPattern === null) {
if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
return;
@ -623,7 +624,10 @@ class CakeEmail {
} elseif (preg_match($this->_emailPattern, $email)) {
return;
}
throw new SocketException(__d('cake_dev', 'Invalid email: "%s"', $email));
if ($email == '') {
throw new SocketException(__d('cake_dev', 'The email set for "%s" is empty.', $context));
}
throw new SocketException(__d('cake_dev', 'Invalid email set for "%s". You passed "%s".', $context, $email));
}
/**
@ -659,7 +663,7 @@ class CakeEmail {
*/
protected function _addEmail($varName, $email, $name) {
if (!is_array($email)) {
$this->_validateEmail($email);
$this->_validateEmail($email, $varName);
if ($name === null) {
$name = $email;
}
@ -671,7 +675,7 @@ class CakeEmail {
if (is_int($key)) {
$key = $value;
}
$this->_validateEmail($key);
$this->_validateEmail($key, $varName);
$list[$key] = $value;
}
$this->{$varName} = array_merge($this->{$varName}, $list);
@ -788,7 +792,7 @@ class CakeEmail {
}
if ($this->_messageId !== false) {
if ($this->_messageId === true) {
$headers['Message-ID'] = '<' . str_replace('-', '', CakeText::UUID()) . '@' . $this->_domain . '>';
$headers['Message-ID'] = '<' . str_replace('-', '', CakeText::uuid()) . '@' . $this->_domain . '>';
} else {
$headers['Message-ID'] = $this->_messageId;
}

View file

@ -1144,6 +1144,16 @@ class TranslateBehaviorTest extends CakeTestCase {
$TestModel->bindTranslation($translations);
$result = $TestModel->find('first');
$TestModel->find('first', array(
'fields' => array(
'TranslatedItem.title',
),
));
$TestModel->find('first', array(
'fields' => array(
'TranslatedItem.title',
),
));
$this->assertArrayHasKey('Title', $result);
$this->assertArrayHasKey('content', $result['Title'][0]);
$this->assertArrayNotHasKey('title', $result);

View file

@ -1650,6 +1650,42 @@ class DboSourceTest extends CakeTestCase {
$this->assertEquals($expected, $result[0]);
}
/**
* Test conditionKeysToString() with virtual field
*
* @return void
*/
public function testConditionKeysToStringVirtualFieldExpression() {
$Article = ClassRegistry::init('Article');
$Article->virtualFields = array(
'extra' => $Article->getDataSource()->expression('something virtual')
);
$conn = $this->getMock('MockPDO', array('quote'));
$db = new DboTestSource();
$db->setConnection($conn);
$conn->expects($this->at(0))
->method('quote')
->will($this->returnValue('just text'));
$conditions = array('Article.extra' => 'just text');
$result = $db->conditionKeysToString($conditions, true, $Article);
$expected = "(" . $Article->virtualFields['extra']->value . ") = just text";
$this->assertEquals($expected, $result[0]);
$conn->expects($this->at(0))
->method('quote')
->will($this->returnValue('just text'));
$conn->expects($this->at(1))
->method('quote')
->will($this->returnValue('other text'));
$conditions = array('Article.extra' => array('just text', 'other text'));
$result = $db->conditionKeysToString($conditions, true, $Article);
$expected = "(" . $Article->virtualFields['extra']->value . ") IN (just text, other text)";
$this->assertEquals($expected, $result[0]);
}
/**
* Test conditionKeysToString() with virtual field
*

View file

@ -2007,6 +2007,9 @@ class ModelValidationTest extends BaseModelTest {
$this->assertTrue(isset($Validator['other']));
$this->assertFalse(isset($Validator['other']['numeric']));
$this->assertTrue(isset($Validator['other']['between']));
$Validator->remove('other');
$Validator->remove('other', 'between');
}
/**

View file

@ -334,23 +334,34 @@ class CakeEmailTest extends CakeTestCase {
/**
* testBuildInvalidData
*
* @dataProvider invalidEmails
* @expectedException SocketException
* @expectedExceptionMessage The email set for "_to" is empty.
* @return void
*/
public function testInvalidEmail($value) {
$this->CakeEmail->to($value);
public function testInvalidEmail() {
$this->CakeEmail->to('');
}
/**
* testBuildInvalidData
*
* @dataProvider invalidEmails
* @expectedException SocketException
* @expectedExceptionMessage Invalid email set for "_from". You passed "cake.@"
* @return void
*/
public function testInvalidEmailAdd($value) {
$this->CakeEmail->addTo($value);
public function testInvalidFrom() {
$this->CakeEmail->from('cake.@');
}
/**
* testBuildInvalidData
*
* @expectedException SocketException
* @expectedExceptionMessage Invalid email set for "_to". You passed "1"
* @return void
*/
public function testInvalidEmailAdd() {
$this->CakeEmail->addTo('1');
}
/**
@ -423,7 +434,7 @@ class CakeEmailTest extends CakeTestCase {
* @return void
*
* @expectedException SocketException
* @expectedExceptionMessage Invalid email: "fail.@example.com"
* @expectedExceptionMessage Invalid email set for "_to". You passed "fail.@example.com"
*/
public function testUnsetEmailPattern() {
$email = new CakeEmail();

View file

@ -445,7 +445,8 @@ class CakeTestCaseTest extends CakeTestCase {
), App::RESET);
CakePlugin::load('TestPlugin');
ConnectionManager::create('test_secondary', array(
'datasource' => 'Database/TestLocalDriver'
'datasource' => 'Database/TestLocalDriver',
'prefix' => ''
));
$post = $this->getMockForModel('SecondaryPost', array('save'));
$this->assertEquals('test_secondary', $post->useDbConfig);

View file

@ -29,6 +29,26 @@ class SecurityTest extends CakeTestCase {
*/
public $sut = null;
/**
* setUp method
*
* @return void
*/
public function setUp() {
parent::setUp();
Configure::delete('Security.useOpenSsl');
}
/**
* tearDown method
*
* @return void
*/
public function tearDown() {
parent::tearDown();
Configure::delete('Security.useOpenSsl');
}
/**
* testInactiveMins method
*
@ -337,6 +357,54 @@ class SecurityTest extends CakeTestCase {
$this->assertEquals($txt, Security::decrypt($result, $key));
}
/**
* Tests that encrypted strings are compatible between the mcrypt and openssl engine.
*
* @dataProvider plainTextProvider
* @param string $txt Plain text to be encrypted.
* @return void
*/
public function testEncryptDecryptCompatibility($txt) {
$this->skipIf(!extension_loaded('mcrypt'), 'This test requires mcrypt to be installed');
$this->skipIf(!extension_loaded('openssl'), 'This test requires openssl to be installed');
$this->skipIf(version_compare(PHP_VERSION, '5.3.3', '<'), 'This test requires PHP 5.3.3 or greater');
$key = '12345678901234567890123456789012';
Configure::write('Security.useOpenSsl', false);
$mcrypt = Security::encrypt($txt, $key);
Configure::write('Security.useOpenSsl', true);
$openssl = Security::encrypt($txt, $key);
$this->assertEquals(strlen($mcrypt), strlen($openssl));
Configure::write('Security.useOpenSsl', false);
$this->assertEquals($txt, Security::decrypt($mcrypt, $key));
$this->assertEquals($txt, Security::decrypt($openssl, $key));
Configure::write('Security.useOpenSsl', true);
$this->assertEquals($txt, Security::decrypt($mcrypt, $key));
$this->assertEquals($txt, Security::decrypt($openssl, $key));
}
/**
* Data provider for testEncryptDecryptCompatibility
*
* @return array
*/
public function plainTextProvider() {
return array(
array(''),
array('abcdefg'),
array('1234567890123456'),
array('The quick brown fox'),
array('12345678901234567890123456789012'),
array('The quick brown fox jumped over the lazy dog.'),
array('何らかのマルチバイト文字列'),
);
}
/**
* Test that changing the key causes decryption to fail.
*

View file

@ -866,7 +866,8 @@ abstract class CakeTestCase extends PHPUnit_Framework_TestCase {
$availableDs = array_keys(ConnectionManager::enumConnectionObjects());
if ($mock->useDbConfig !== 'test' && in_array('test_' . $mock->useDbConfig, $availableDs)) {
$mock->setDataSource('test_' . $mock->useDbConfig);
$mock->useDbConfig = 'test_' . $mock->useDbConfig;
$mock->setDataSource($mock->useDbConfig);
} else {
$mock->useDbConfig = 'test';
$mock->setDataSource('test');

View file

@ -18,6 +18,14 @@
if (!class_exists('PHPUnit_TextUI_TestRunner')) {
require_once 'PHPUnit/TextUI/TestRunner.php';
}
if (class_exists('SebastianBergmann\CodeCoverage\CodeCoverage')) {
class_alias('SebastianBergmann\CodeCoverage\CodeCoverage', 'PHP_CodeCoverage');
class_alias('SebastianBergmann\CodeCoverage\Report\Text', 'PHP_CodeCoverage_Report_Text');
class_alias('SebastianBergmann\CodeCoverage\Report\PHP', 'PHP_CodeCoverage_Report_PHP');
class_alias('SebastianBergmann\CodeCoverage\Report\Clover', 'PHP_CodeCoverage_Report_Clover');
class_alias('SebastianBergmann\CodeCoverage\Report\Html\Facade', 'PHP_CodeCoverage_Report_HTML');
class_alias('SebastianBergmann\CodeCoverage\Exception', 'PHP_CodeCoverage_Exception');
}
App::uses('CakeFixtureManager', 'TestSuite/Fixture');

View file

@ -184,9 +184,12 @@ class Security {
if (function_exists('openssl_random_pseudo_bytes')) {
return openssl_random_pseudo_bytes($length);
}
if (function_exists('mcrypt_create_iv')) {
return mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
}
trigger_error(
'You do not have a safe source of random data available. ' .
'Install either the openssl extension, or paragonie/random_compat. ' .
'Install either the openssl extension, the mcrypt extension, or paragonie/random_compat. ' .
'Falling back to an insecure random source.',
E_USER_WARNING
);
@ -206,7 +209,7 @@ class Security {
* for sensitive data. Additionally this method does *not* work in environments
* where suhosin is enabled.
*
* Instead you should use Security::rijndael() when you need strong
* Instead you should use Security::encrypt() when you need strong
* encryption.
*
* @param string $text Encrypted string to decrypt, normal string to encrypt
@ -349,12 +352,24 @@ class Security {
// Generate the encryption and hmac key.
$key = substr(hash('sha256', $key . $hmacSalt), 0, 32);
if (Configure::read('Security.useOpenSsl')) {
$method = 'AES-256-CBC';
$ivSize = openssl_cipher_iv_length($method);
$iv = openssl_random_pseudo_bytes($ivSize);
$padLength = (int)ceil((strlen($plain) ?: 1) / $ivSize) * $ivSize;
$ciphertext = openssl_encrypt(str_pad($plain, $padLength, "\0"), $method, $key, true, $iv);
// Remove the PKCS#7 padding block for compatibility with mcrypt.
// Since we have padded the provided data with \0, the final block contains only padded bytes.
// So it can be removed safely.
$ciphertext = $iv . substr($ciphertext, 0, -$ivSize);
} else {
$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;
}
@ -404,13 +419,23 @@ class Security {
return false;
}
if (Configure::read('Security.useOpenSsl')) {
$method = 'AES-256-CBC';
$ivSize = openssl_cipher_iv_length($method);
$iv = substr($cipher, 0, $ivSize);
$cipher = substr($cipher, $ivSize);
// Regenerate PKCS#7 padding block
$padding = openssl_encrypt('', $method, $key, true, substr($cipher, -$ivSize));
$plain = openssl_decrypt($cipher . $padding, $method, $key, true, $iv);
} else {
$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

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