Merge branch '2.x' into 2.next

This commit is contained in:
mark_story 2016-05-02 21:58:41 -04:00
commit 12c6fd4e22
21 changed files with 1580 additions and 1266 deletions

View file

@ -1,5 +1,8 @@
<IfModule mod_rewrite.c>
RewriteEngine on
# Uncomment if you have a .well-known directory in the root folder, e.g. for the Let's Encrypt challenge
# https://tools.ietf.org/html/rfc5785
#RewriteRule ^(\.well-known/.*)$ $1 [L]
RewriteRule ^$ app/webroot/ [L]
RewriteRule (.*) app/webroot/$1 [L]
</IfModule>

View file

@ -1,5 +1,8 @@
<IfModule mod_rewrite.c>
RewriteEngine on
# Uncomment if you have a .well-known directory in the app folder, e.g. for the Let's Encrypt challenge
# https://tools.ietf.org/html/rfc5785
#RewriteRule ^(\.well-known/.*)$ $1 [L]
RewriteRule ^$ webroot/ [L]
RewriteRule (.*) webroot/$1 [L]
</IfModule>

View file

@ -132,7 +132,7 @@ class FileEngine extends CacheEngine {
}
$expires = time() + $duration;
$contents = $expires . $lineBreak . $data . $lineBreak;
$contents = implode(array($expires, $lineBreak, $data, $lineBreak));
if ($this->settings['lock']) {
$this->_File->flock(LOCK_EX);

File diff suppressed because it is too large Load diff

View file

@ -125,7 +125,8 @@ class FixtureTask extends BakeTask {
return $this->all();
}
$model = $this->_modelName($this->args[0]);
$this->bake($model);
$importOptions = $this->importOptions($model);
$this->bake($model, false, $importOptions);
}
}
@ -177,24 +178,29 @@ class FixtureTask extends BakeTask {
*/
public function importOptions($modelName) {
$options = array();
$plugin = '';
if (isset($this->params['plugin'])) {
$plugin = $this->params['plugin'] . '.';
}
if (!empty($this->params['schema'])) {
$options['schema'] = $modelName;
} else {
$options['schema'] = $plugin . $modelName;
} elseif ($this->interactive) {
$doSchema = $this->in(__d('cake_console', 'Would you like to import schema for this fixture?'), array('y', 'n'), 'n');
if ($doSchema === 'y') {
$options['schema'] = $modelName;
}
}
if (!empty($this->params['records'])) {
$doRecords = 'y';
} else {
$options['fromTable'] = true;
} elseif ($this->interactive) {
$doRecords = $this->in(__d('cake_console', 'Would you like to use record importing for this fixture?'), array('y', 'n'), 'n');
if ($doRecords === 'y') {
$options['records'] = true;
}
}
if ($doRecords === 'y') {
$options['records'] = true;
}
if ($doRecords === 'n') {
if (!isset($options['records']) && $this->interactive) {
$prompt = __d('cake_console', "Would you like to build this fixture with data from %s's table?", $modelName);
$fromTable = $this->in($prompt, array('y', 'n'), 'n');
if (strtolower($fromTable) === 'y') {

View file

@ -53,14 +53,15 @@ class CakeBaseException extends RuntimeException {
}
if (!class_exists('HttpException', false)) {
/**
* Parent class for all of the HTTP related exceptions in CakePHP.
*
* All HTTP status/error related exceptions should extend this class so
* catch blocks can be specifically typed.
*
* @package Cake.Error
*/
if (!class_exists('HttpException', false)) {
class HttpException extends CakeBaseException {
}
}

View file

@ -209,6 +209,7 @@ class L10n {
'ca' => array('language' => 'Catalan', 'locale' => 'cat', 'localeFallback' => 'cat', 'charset' => 'utf-8', 'direction' => 'ltr'),
'cs' => array('language' => 'Czech', 'locale' => 'ces', 'localeFallback' => 'ces', 'charset' => 'utf-8', 'direction' => 'ltr'),
'da' => array('language' => 'Danish', 'locale' => 'dan', 'localeFallback' => 'dan', 'charset' => 'utf-8', 'direction' => 'ltr'),
'da-dk' => array('language' => 'Danish (Denmark)', 'locale' => 'da_dk', 'localeFallback' => 'dan', 'charset' => 'utf-8', 'direction' => 'ltr'),
'de' => array('language' => 'German (Standard)', 'locale' => 'deu', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'),
'de-at' => array('language' => 'German (Austria)', 'locale' => 'de_at', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'),
'de-ch' => array('language' => 'German (Swiss)', 'locale' => 'de_ch', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'),
@ -248,9 +249,11 @@ class L10n {
'es-uy' => array('language' => 'Spanish (Uruguay)', 'locale' => 'es_uy', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'),
'es-ve' => array('language' => 'Spanish (Venezuela)', 'locale' => 'es_ve', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'),
'et' => array('language' => 'Estonian', 'locale' => 'est', 'localeFallback' => 'est', 'charset' => 'utf-8', 'direction' => 'ltr'),
'et-ee' => array('language' => 'Estonian (Estonia)', 'locale' => 'et_ee', 'localeFallback' => 'est', 'charset' => 'utf-8', 'direction' => 'ltr'),
'eu' => array('language' => 'Basque', 'locale' => 'eus', 'localeFallback' => 'eus', 'charset' => 'utf-8', 'direction' => 'ltr'),
'fa' => array('language' => 'Farsi', 'locale' => 'fas', 'localeFallback' => 'fas', 'charset' => 'utf-8', 'direction' => 'rtl'),
'fi' => array('language' => 'Finnish', 'locale' => 'fin', 'localeFallback' => 'fin', 'charset' => 'utf-8', 'direction' => 'ltr'),
'fi-fi' => array('language' => 'Finnish (Finland)', 'locale' => 'fi_fi', 'localeFallback' => 'fin', 'charset' => 'utf-8', 'direction' => 'ltr'),
'fo' => array('language' => 'Faeroese', 'locale' => 'fao', 'localeFallback' => 'fao', 'charset' => 'utf-8', 'direction' => 'ltr'),
'fr' => array('language' => 'French (Standard)', 'locale' => 'fra', 'localeFallback' => 'fra', 'charset' => 'utf-8', 'direction' => 'ltr'),
'fr-be' => array('language' => 'French (Belgium)', 'locale' => 'fr_be', 'localeFallback' => 'fra', 'charset' => 'utf-8', 'direction' => 'ltr'),
@ -274,6 +277,7 @@ class L10n {
'ja' => array('language' => 'Japanese', 'locale' => 'jpn', 'localeFallback' => 'jpn', 'charset' => 'utf-8', 'direction' => 'ltr'),
'kk' => array('language' => 'Kazakh', 'locale' => 'kaz', 'localeFallback' => 'kaz', 'charset' => 'utf-8', 'direction' => 'ltr'),
'kl' => array('language' => 'Kalaallisut (Greenlandic)', 'locale' => 'kal', 'localeFallback' => 'kal', 'charset' => 'kl', 'direction' => 'ltr'),
'kl-gl' => array('language' => 'Kalaallisut (Greenland)', 'locale' => 'kl_gl', 'localeFallback' => 'kal', 'charset' => 'kl', 'direction' => 'ltr'),
'ko' => array('language' => 'Korean', 'locale' => 'kor', 'localeFallback' => 'kor', 'charset' => 'kr', 'direction' => 'ltr'),
'ko-kp' => array('language' => 'Korea (North)', 'locale' => 'ko_kp', 'localeFallback' => 'kor', 'charset' => 'kr', 'direction' => 'ltr'),
'ko-kr' => array('language' => 'Korea (South)', 'locale' => 'ko_kr', 'localeFallback' => 'kor', 'charset' => 'kr', 'direction' => 'ltr'),
@ -286,9 +290,12 @@ class L10n {
'ms' => array('language' => 'Malaysian', 'locale' => 'msa', 'localeFallback' => 'msa', 'charset' => 'utf-8', 'direction' => 'ltr'),
'mt' => array('language' => 'Maltese', 'locale' => 'mlt', 'localeFallback' => 'mlt', 'charset' => 'utf-8', 'direction' => 'ltr'),
'nb' => array('language' => 'Norwegian Bokmal', 'locale' => 'nob', 'localeFallback' => 'nor', 'charset' => 'utf-8', 'direction' => 'ltr'),
'nb-no' => array('language' => 'Norwegian Bokmål (Norway)', 'locale' => 'nb_no', 'localeFallback' => 'nor', 'charset' => 'utf-8', 'direction' => 'ltr'),
'nl' => array('language' => 'Dutch (Standard)', 'locale' => 'nld', 'localeFallback' => 'nld', 'charset' => 'utf-8', 'direction' => 'ltr'),
'nl-be' => array('language' => 'Dutch (Belgium)', 'locale' => 'nl_be', 'localeFallback' => 'nld', 'charset' => 'utf-8', 'direction' => 'ltr'),
'nl-nl' => array('language' => 'Dutch (Netherlands)', 'locale' => 'nl_nl', 'localeFallback' => 'nld', 'charset' => 'utf-8', 'direction' => 'ltr'),
'nn' => array('language' => 'Norwegian Nynorsk', 'locale' => 'nno', 'localeFallback' => 'nor', 'charset' => 'utf-8', 'direction' => 'ltr'),
'nn-no' => array('language' => 'Norwegian Nynorsk (Norway)', 'locale' => 'nn_no', 'localeFallback' => 'nor', 'charset' => 'utf-8', 'direction' => 'ltr'),
'no' => array('language' => 'Norwegian', 'locale' => 'nor', 'localeFallback' => 'nor', 'charset' => 'utf-8', 'direction' => 'ltr'),
'pl' => array('language' => 'Polish', 'locale' => 'pol', 'localeFallback' => 'pol', 'charset' => 'utf-8', 'direction' => 'ltr'),
'pt' => array('language' => 'Portuguese (Portugal)', 'locale' => 'por', 'localeFallback' => 'por', 'charset' => 'utf-8', 'direction' => 'ltr'),
@ -296,6 +303,7 @@ class L10n {
'rm' => array('language' => 'Rhaeto-Romanic', 'locale' => 'roh', 'localeFallback' => 'roh', 'charset' => 'utf-8', 'direction' => 'ltr'),
'ro' => array('language' => 'Romanian', 'locale' => 'ron', 'localeFallback' => 'ron', 'charset' => 'utf-8', 'direction' => 'ltr'),
'ro-mo' => array('language' => 'Romanian (Moldavia)', 'locale' => 'ro_mo', 'localeFallback' => 'ron', 'charset' => 'utf-8', 'direction' => 'ltr'),
'ro-ro' => array('language' => 'Romanian (Romania)', 'locale' => 'ro_ro', 'localeFallback' => 'ron', 'charset' => 'utf-8', 'direction' => 'ltr'),
'ru' => array('language' => 'Russian', 'locale' => 'rus', 'localeFallback' => 'rus', 'charset' => 'utf-8', 'direction' => 'ltr'),
'ru-mo' => array('language' => 'Russian (Moldavia)', 'locale' => 'ru_mo', 'localeFallback' => 'rus', 'charset' => 'utf-8', 'direction' => 'ltr'),
'sb' => array('language' => 'Sorbian', 'locale' => 'wen', 'localeFallback' => 'wen', 'charset' => 'utf-8', 'direction' => 'ltr'),
@ -304,6 +312,7 @@ class L10n {
'sq' => array('language' => 'Albanian', 'locale' => 'sqi', 'localeFallback' => 'sqi', 'charset' => 'utf-8', 'direction' => 'ltr'),
'sr' => array('language' => 'Serbian', 'locale' => 'srp', 'localeFallback' => 'srp', 'charset' => 'utf-8', 'direction' => 'ltr'),
'sv' => array('language' => 'Swedish', 'locale' => 'swe', 'localeFallback' => 'swe', 'charset' => 'utf-8', 'direction' => 'ltr'),
'sv-se' => array('language' => 'Swedish (Sweden)', 'locale' => 'sv_se', 'localeFallback' => 'swe', 'charset' => 'utf-8', 'direction' => 'ltr'),
'sv-fi' => array('language' => 'Swedish (Finland)', 'locale' => 'sv_fi', 'localeFallback' => 'swe', 'charset' => 'utf-8', 'direction' => 'ltr'),
'se' => array('language' => 'Sami', 'locale' => 'sme', 'localeFallback' => 'sme', 'charset' => 'utf-8', 'direction' => 'ltr'),
'th' => array('language' => 'Thai', 'locale' => 'tha', 'localeFallback' => 'tha', 'charset' => 'utf-8', 'direction' => 'ltr'),

View file

@ -21,10 +21,10 @@
App::uses('LogEngineCollection', 'Log');
/**
* Logs messages to configured Log adapters. One or more adapters
* can be configured using CakeLogs's methods. If you don't
* configure any adapters, and write to the logs a default
* FileLog will be autoconfigured for you.
* Logs messages to configured Log adapters.
*
* One or more adapters
* can be configured using CakeLogs's methods.
*
* ### Configuring Log adapters
*
@ -84,6 +84,9 @@ class CakeLog {
/**
* Default log levels as detailed in RFC 5424
* http://tools.ietf.org/html/rfc5424
*
* Windows has fewer levels, thus notice, info and debug are the same.
* https://bugs.php.net/bug.php?id=18090
*
* @var array
*/

View file

@ -1490,34 +1490,38 @@ class DboSource extends DataSource {
$primaryKey = $Model->primaryKey;
$foreignKey = $Model->hasMany[$association]['foreignKey'];
// Make one pass through children and collect by parent key
// Make second pass through parents and associate children
$mergedByFK = array();
foreach ($assocResultSet as $data) {
$fk = $data[$association][$foreignKey];
if (! array_key_exists($fk, $mergedByFK)) {
$mergedByFK[$fk] = array();
}
if (count($data) > 1) {
$data = array_merge($data[$association], $data);
unset($data[$association]);
foreach ($data as $key => $name) {
if (is_numeric($key)) {
$data[$association][] = $name;
unset($data[$key]);
}
}
$mergedByFK[$fk][] = $data;
} else {
$mergedByFK[$fk][] = $data[$association];
}
}
foreach ($resultSet as &$result) {
if (!isset($result[$modelAlias])) {
continue;
}
$resultPrimaryKey = $result[$modelAlias][$primaryKey];
$merged = array();
foreach ($assocResultSet as $data) {
if ($resultPrimaryKey !== $data[$association][$foreignKey]) {
continue;
}
if (count($data) > 1) {
$data = array_merge($data[$association], $data);
unset($data[$association]);
foreach ($data as $key => $name) {
if (is_numeric($key)) {
$data[$association][] = $name;
unset($data[$key]);
}
}
$merged[] = $data;
} else {
$merged[] = $data[$association];
}
$pk = $result[$modelAlias][$primaryKey];
if (isset($mergedByFK[$pk])) {
$merged = $mergedByFK[$pk];
}
$result = Hash::mergeDiff($result, array($association => $merged));
}
}

View file

@ -1201,9 +1201,6 @@ class CakeResponse {
* If the method is called with an array as argument, it will set the cookie
* configuration to the cookie container.
*
* @param array $options Either null to get all cookies, string for a specific cookie
* or array to set cookie.
*
* ### Options (when setting a configuration)
* - name: The Cookie name
* - value: Value of the cookie
@ -1227,6 +1224,8 @@ class CakeResponse {
*
* `$this->cookie((array) $options)`
*
* @param array $options Either null to get all cookies, string for a specific cookie
* or array to set cookie.
* @return mixed
*/
public function cookie($options = null) {
@ -1417,11 +1416,16 @@ class CakeResponse {
* @return void
*/
protected function _fileRange($file, $httpRange) {
list(, $range) = explode('=', $httpRange);
list($start, $end) = explode('-', $range);
$fileSize = $file->size();
$lastByte = $fileSize - 1;
$start = 0;
$end = $lastByte;
preg_match('/^bytes\s*=\s*(\d+)?\s*-\s*(\d+)?$/', $httpRange, $matches);
if ($matches) {
$start = $matches[1];
$end = isset($matches[2]) ? $matches[2] : '';
}
if ($start === '') {
$start = $fileSize - $end;

View file

@ -98,6 +98,7 @@ class FixtureTaskTest extends CakeTestCase {
* @return void
*/
public function testImportOptionsSchemaRecords() {
$this->Task->interactive = true;
$this->Task->expects($this->at(0))->method('in')->will($this->returnValue('y'));
$this->Task->expects($this->at(1))->method('in')->will($this->returnValue('y'));
@ -112,6 +113,7 @@ class FixtureTaskTest extends CakeTestCase {
* @return void
*/
public function testImportOptionsNothing() {
$this->Task->interactive = true;
$this->Task->expects($this->at(0))->method('in')->will($this->returnValue('n'));
$this->Task->expects($this->at(1))->method('in')->will($this->returnValue('n'));
$this->Task->expects($this->at(2))->method('in')->will($this->returnValue('n'));
@ -130,7 +132,20 @@ class FixtureTaskTest extends CakeTestCase {
$this->Task->params = array('schema' => true, 'records' => true);
$result = $this->Task->importOptions('Article');
$expected = array('schema' => 'Article', 'records' => true);
$expected = array('schema' => 'Article', 'fromTable' => true);
$this->assertEquals($expected, $result);
}
/**
* test importOptions with overwriting CLI options
*
* @return void
*/
public function testImportOptionsWithCommandLineOptionsPlugin() {
$this->Task->params = array('schema' => true, 'records' => true, 'plugin' => 'TestPlugin');
$result = $this->Task->importOptions('Article');
$expected = array('schema' => 'TestPlugin.Article', 'fromTable' => true);
$this->assertEquals($expected, $result);
}
@ -140,6 +155,7 @@ class FixtureTaskTest extends CakeTestCase {
* @return void
*/
public function testImportOptionsWithSchema() {
$this->Task->interactive = true;
$this->Task->params = array('schema' => true);
$this->Task->expects($this->at(0))->method('in')->will($this->returnValue('n'));
$this->Task->expects($this->at(1))->method('in')->will($this->returnValue('n'));
@ -155,11 +171,12 @@ class FixtureTaskTest extends CakeTestCase {
* @return void
*/
public function testImportOptionsWithRecords() {
$this->Task->interactive = true;
$this->Task->params = array('records' => true);
$this->Task->expects($this->at(0))->method('in')->will($this->returnValue('n'));
$result = $this->Task->importOptions('Article');
$expected = array('records' => true);
$expected = array('fromTable' => true);
$this->assertEquals($expected, $result);
}
@ -169,6 +186,7 @@ class FixtureTaskTest extends CakeTestCase {
* @return void
*/
public function testImportOptionsTable() {
$this->Task->interactive = true;
$this->Task->expects($this->at(0))->method('in')->will($this->returnValue('n'));
$this->Task->expects($this->at(1))->method('in')->will($this->returnValue('n'));
$this->Task->expects($this->at(2))->method('in')->will($this->returnValue('y'));
@ -244,6 +262,62 @@ class FixtureTaskTest extends CakeTestCase {
$this->assertContains("'body' => 'Body \"value\"'", $result, 'Data has bad escaping');
}
/**
* test that execute includes import options
*
* @return void
*/
public function testExecuteWithImportSchema() {
$this->Task->connection = 'test';
$this->Task->path = '/my/path/';
$this->Task->args = array('article');
$this->Task->params = array(
'schema' => true,
'records' => false,
);
$filename = '/my/path/ArticleFixture.php';
$this->Task->expects($this->never())
->method('in');
$this->Task->expects($this->at(0))
->method('createFile')
->with($filename, $this->logicalAnd(
$this->stringContains('class ArticleFixture'),
$this->stringContains("\$import = array('model' => 'Article'")
));
$this->Task->execute();
}
/**
* test that execute includes import options
*
* @return void
*/
public function testExecuteWithImportRecords() {
$this->Task->connection = 'test';
$this->Task->path = '/my/path/';
$this->Task->args = array('article');
$this->Task->params = array(
'schema' => true,
'records' => true,
);
$filename = '/my/path/ArticleFixture.php';
$this->Task->expects($this->never())
->method('in');
$this->Task->expects($this->at(0))
->method('createFile')
->with($filename, $this->logicalAnd(
$this->stringContains('class ArticleFixture'),
$this->stringContains("\$import = array('model' => 'Article', 'connection' => 'test')")
));
$this->Task->execute();
}
/**
* test that execute passes runs bake depending with named model.
*

View file

@ -1705,48 +1705,81 @@ class CakeResponseTest extends CakeTestCase {
$this->assertNotSame(false, $result);
}
/**
* Provider for invalid range header values.
*
* @return array
*/
public function invalidFileRangeProvider() {
return array(
// malformed range
array(
'bytes=0,38'
),
// malformed punctuation
array(
'bytes: 0 - 32'
),
array(
'garbage: poo - poo'
),
);
}
/**
* Test invalid file ranges.
*
* @dataProvider invalidFileRangeProvider
* @return void
*/
public function testFileRangeInvalid() {
$_SERVER['HTTP_RANGE'] = 'bytes=30-2';
public function testFileRangeInvalid($range) {
$_SERVER['HTTP_RANGE'] = $range;
$response = $this->getMock('CakeResponse', array(
'header',
'type',
'_sendHeader',
'_setContentType',
'_isActive',
'_clearBuffer',
'_flushBuffer'
));
$response->expects($this->at(1))
->method('header')
->with('Content-Disposition', 'attachment; filename="test_asset.css"');
$response->expects($this->at(2))
->method('header')
->with('Content-Transfer-Encoding', 'binary');
$response->expects($this->at(3))
->method('header')
->with('Accept-Ranges', 'bytes');
$response->expects($this->at(4))
->method('header')
->with(array(
'Content-Range' => 'bytes 0-37/38',
));
$response->file(
CAKE . 'Test' . DS . 'test_app' . DS . 'Vendor' . DS . 'css' . DS . 'test_asset.css',
array('download' => true)
);
$expected = array(
'Content-Disposition' => 'attachment; filename="test_asset.css"',
'Content-Transfer-Encoding' => 'binary',
'Accept-Ranges' => 'bytes',
'Content-Range' => 'bytes 0-37/38',
'Content-Length' => 38,
);
$this->assertEquals($expected, $response->header());
}
/**
* Test backwards file range
*
* @return void
*/
public function testFileRangeReversed() {
$_SERVER['HTTP_RANGE'] = 'bytes=30-5';
$response = $this->getMock('CakeResponse', array(
'_sendHeader',
'_isActive',
));
$response->file(
CAKE . 'Test' . DS . 'test_app' . DS . 'Vendor' . DS . 'css' . DS . 'test_asset.css',
array('download' => true)
);
$expected = array(
'Content-Disposition' => 'attachment; filename="test_asset.css"',
'Content-Transfer-Encoding' => 'binary',
'Accept-Ranges' => 'bytes',
'Content-Range' => 'bytes 0-37/38',
);
$this->assertEquals($expected, $response->header());
$this->assertEquals(416, $response->statusCode());
$response->send();
}
/**

View file

@ -1843,7 +1843,6 @@ class HttpSocketTest extends CakeTestCase {
} catch (SocketException $e) {
$message = $e->getMessage();
$this->skipIf(strpos($message, 'Invalid HTTP') !== false, 'Invalid HTTP Response received, skipping.');
$this->assertContains('Peer certificate CN', $message);
$this->assertContains('Failed to enable crypto', $message);
}
}

View file

@ -151,16 +151,36 @@ class SecurityTest extends CakeTestCase {
Security::setHash($_hashType);
}
/**
* Test that blowfish doesn't return '' when the salt is ''
*
* @return void
*/
public function testHashBlowfishEmptySalt() {
$test = Security::hash('password', 'blowfish');
$this->skipIf(strpos($test, '$2a$') === false, 'Blowfish hashes are incorrect.');
$stored = '';
$hash = Security::hash('anything', 'blowfish', $stored);
$this->assertNotEquals($stored, $hash);
$hash = Security::hash('anything', 'blowfish', false);
$this->assertNotEquals($stored, $hash);
$hash = Security::hash('anything', 'blowfish', null);
$this->assertNotEquals($stored, $hash);
}
/**
* Test that hash() works with blowfish.
*
* @return void
*/
public function testHashBlowfish() {
Security::setCost(10);
$test = Security::hash('password', 'blowfish');
$this->skipIf(strpos($test, '$2a$') === false, 'Blowfish hashes are incorrect.');
Security::setCost(10);
$_hashType = Security::$hashType;
$key = 'someKey';

View file

@ -88,6 +88,25 @@ class TestDeValidation {
}
/**
* ValidationStub
*
* @package Cake.Test.Case.Utility
*/
class ValidationStub extends Validation {
/**
* Stub out is_uploaded_file check
*
* @param string $path
* @return void
*/
protected static function _isUploadedFile($path) {
return file_exists($path);
}
}
/**
* Test Case for Validation Class
*
@ -384,6 +403,12 @@ class ValidationTest extends CakeTestCase {
$this->assertTrue(Validation::cc('5467639122779531', array('mc')));
$this->assertTrue(Validation::cc('5297350261550024', array('mc')));
$this->assertTrue(Validation::cc('5162739131368058', array('mc')));
//Mastercard (additional 2016 BIN)
$this->assertTrue(Validation::cc('2221000000000009', array('mc')));
$this->assertTrue(Validation::cc('2720999999999996', array('mc')));
$this->assertTrue(Validation::cc('2223000010005798', array('mc')));
$this->assertTrue(Validation::cc('2623430710235708', array('mc')));
$this->assertTrue(Validation::cc('2420452519835723', array('mc')));
//Solo 16
$this->assertTrue(Validation::cc('6767432107064987', array('solo')));
$this->assertTrue(Validation::cc('6334667758225411', array('solo')));
@ -2142,9 +2167,6 @@ class ValidationTest extends CakeTestCase {
$this->assertFalse(Validation::phone('1-(511)-999-9999'));
$this->assertFalse(Validation::phone('1-(555)-999-9999'));
// invalid exhange
$this->assertFalse(Validation::phone('1-(222)-511-9999'));
// invalid phone number
$this->assertFalse(Validation::phone('1-(222)-555-0199'));
$this->assertFalse(Validation::phone('1-(222)-555-0122'));
@ -2167,6 +2189,7 @@ class ValidationTest extends CakeTestCase {
$this->assertTrue(Validation::phone('1.(333).333-4444'));
$this->assertTrue(Validation::phone('1.(333).333.4444'));
$this->assertTrue(Validation::phone('1-333-333-4444'));
$this->assertTrue(Validation::phone('1-800-211-4511'));
}
/**
@ -2405,11 +2428,11 @@ class ValidationTest extends CakeTestCase {
* @return void
*/
public function testUploadedFileErrorCode() {
$this->assertFalse(Validation::uploadedFile('derp'));
$this->assertFalse(ValidationStub::uploadedFile('derp'));
$invalid = array(
'name' => 'testing'
);
$this->assertFalse(Validation::uploadedFile($invalid));
$this->assertFalse(ValidationStub::uploadedFile($invalid));
$file = array(
'name' => 'cake.power.gif',
'tmp_name' => CORE_PATH . 'Cake' . DS . 'Test' . DS . 'test_app' . DS . 'webroot/img/cake.power.gif',
@ -2417,9 +2440,9 @@ class ValidationTest extends CakeTestCase {
'type' => 'image/gif',
'size' => 201
);
$this->assertTrue(Validation::uploadedFile($file));
$this->assertTrue(ValidationStub::uploadedFile($file));
$file['error'] = UPLOAD_ERR_NO_FILE;
$this->assertFalse(Validation::uploadedFile($file), 'Error upload should fail.');
$this->assertFalse(ValidationStub::uploadedFile($file), 'Error upload should fail.');
}
/**
@ -2438,11 +2461,11 @@ class ValidationTest extends CakeTestCase {
$options = array(
'types' => array('text/plain')
);
$this->assertFalse(Validation::uploadedFile($file, $options), 'Incorrect mimetype.');
$this->assertFalse(ValidationStub::uploadedFile($file, $options), 'Incorrect mimetype.');
$options = array(
'types' => array('image/gif', 'image/png')
);
$this->assertTrue(Validation::uploadedFile($file, $options));
$this->assertTrue(ValidationStub::uploadedFile($file, $options));
}
/**
@ -2461,24 +2484,24 @@ class ValidationTest extends CakeTestCase {
$options = array(
'minSize' => 500
);
$this->assertFalse(Validation::uploadedFile($file, $options), 'Too small');
$this->assertFalse(ValidationStub::uploadedFile($file, $options), 'Too small');
$options = array(
'maxSize' => 100
);
$this->assertFalse(Validation::uploadedFile($file, $options), 'Too big');
$this->assertFalse(ValidationStub::uploadedFile($file, $options), 'Too big');
$options = array(
'minSize' => 100,
);
$this->assertTrue(Validation::uploadedFile($file, $options));
$this->assertTrue(ValidationStub::uploadedFile($file, $options));
$options = array(
'maxSize' => 500,
);
$this->assertTrue(Validation::uploadedFile($file, $options));
$this->assertTrue(ValidationStub::uploadedFile($file, $options));
$options = array(
'minSize' => 100,
'maxSize' => 500
);
$this->assertTrue(Validation::uploadedFile($file, $options));
$this->assertTrue(ValidationStub::uploadedFile($file, $options));
}
/**
@ -2519,6 +2542,6 @@ class ValidationTest extends CakeTestCase {
'size' => 201
);
$options = array();
$this->assertTrue(Validation::uploadedFile($file, $options), 'Wrong order');
$this->assertTrue(ValidationStub::uploadedFile($file, $options), 'Wrong order');
}
}

View file

@ -4870,6 +4870,50 @@ class FormHelperTest extends CakeTestCase {
$this->assertTags($result, $expected);
}
/**
* testSelect boolean method
*
* @return void
*/
public function testSelectBoolean() {
$result = $this->Form->select(
'Model.field',
array(0 => 'No', 1 => 'Yes'),
array('value' => false, 'empty' => false)
);
$expected = array(
'select' => array('name' => 'data[Model][field]', 'id' => 'ModelField'),
array('option' => array('value' => '0', 'selected' => 'selected')),
'No',
'/option',
array('option' => array('value' => '1')),
'Yes',
'/option',
'/select'
);
$this->assertTags($result, $expected);
$result = $this->Form->select(
'Model.field',
array(0 => 'No', 1 => 'Yes', 2 => 'Yes again'),
array('value' => array(false, 2), 'empty' => false)
);
$expected = array(
'select' => array('name' => 'data[Model][field]', 'id' => 'ModelField'),
array('option' => array('value' => '0', 'selected' => 'selected')),
'No',
'/option',
array('option' => array('value' => '1')),
'Yes',
'/option',
array('option' => array('value' => '2', 'selected' => 'selected')),
'Yes again',
'/option',
'/select'
);
$this->assertTags($result, $expected);
}
/**
* test that select() with optiongroups listens to the escape param.
*
@ -8120,6 +8164,34 @@ class FormHelperTest extends CakeTestCase {
));
}
/**
* Test that postLink doesn't modify the fields in the containing form.
*
* postLink() calls inside open forms should not modify the field list
* for the form.
*
* @return void
*/
public function testPostLinkSecurityHashInline() {
$hash = Security::hash(
'/posts/delete/1' .
serialize(array()) .
'' .
Configure::read('Security.salt')
);
$hash .= '%3A';
$this->Form->request->params['_Token']['key'] = 'test';
$this->Form->create('Post', array('url' => array('action' => 'add')));
$this->Form->input('title');
$this->Form->postLink('Delete', '/posts/delete/1', array('inline' => false));
$result = $this->View->fetch('postLink');
$this->assertEquals(array('Post.title'), $this->Form->fields);
$this->assertContains($hash, $result, 'Should contain the correct hash.');
$this->assertAttributeEquals('/posts/add', '_lastAction', $this->Form, 'lastAction was should be restored.');
}
/**
* Test using postLink with N dimensional data.
*
@ -8674,6 +8746,36 @@ class FormHelperTest extends CakeTestCase {
$this->assertTags($result, $expected);
}
/**
* Test that the action key still uses the model as the implicit controller
* when the url option is undefined. While the action parameter is deprecated
* we need it to continue working for the duration of 2.x
*
* @return void
*/
public function testCreateUrlImpliedController() {
$restore = error_reporting(E_ALL ^ E_USER_DEPRECATED);
$this->Form->request['controller'] = 'posts';
$result = $this->Form->create('Comment', array(
'action' => 'addComment',
'id' => 'addCommentForm',
'type' => 'POST'
));
$expected = array(
'form' => array(
'action' => '/comments/addComment',
'id' => 'addCommentForm',
'method' => 'post',
'accept-charset' => strtolower(Configure::read('App.encoding'))
),
'div' => array('style' => 'display:none;'),
'input' => array('type' => 'hidden', 'name' => '_method', 'value' => 'POST'),
'/div'
);
$this->assertTags($result, $expected);
error_reporting($restore);
}
/**
* Test the onsubmit option for create()
*

View file

@ -178,6 +178,13 @@ abstract class ControllerTestCase extends CakeTestCase {
*/
protected $_dirtyController = false;
/**
* The class name to use for mocking the response object.
*
* @var string
*/
protected $_responseClass = 'CakeResponse';
/**
* Used to enable calling ControllerTestCase::testAction() without the testing
* framework thinking that it's a test case
@ -276,8 +283,14 @@ abstract class ControllerTestCase extends CakeTestCase {
$params['requested'] = 1;
}
$Dispatch->testController = $this->controller;
$Dispatch->response = $this->getMock('CakeResponse', array('send', '_clearBuffer'));
$Dispatch->response = $this->getMock($this->_responseClass, array('send', '_clearBuffer'));
$this->result = $Dispatch->dispatch($request, $Dispatch->response, $params);
// Clear out any stored requests.
while (Router::getRequest()) {
Router::popRequest();
}
$this->controller = $Dispatch->testController;
$this->vars = $this->controller->viewVars;
$this->contents = $this->controller->response->body();
@ -339,7 +352,7 @@ abstract class ControllerTestCase extends CakeTestCase {
$controllerObj = $this->getMock($name . 'Controller', $mocks['methods'], array(), '', false);
$controllerObj->name = $name;
$request = $this->getMock('CakeRequest');
$response = $this->getMock('CakeResponse', array('_sendHeader'));
$response = $this->getMock($this->_responseClass, array('_sendHeader'));
$controllerObj->__construct($request, $response);
$controllerObj->Components->setController($controllerObj);

View file

@ -303,7 +303,7 @@ class Security {
* @return string The hashed string or an empty string on error.
*/
protected static function _crypt($password, $salt = false) {
if ($salt === false) {
if ($salt === false || $salt === null || $salt === '') {
$salt = static::_salt(22);
$salt = vsprintf('$2a$%02d$%s', array(static::$hashCost, $salt));
}

View file

@ -182,7 +182,7 @@ class Validation {
'enroute' => '/^2(?:014|149)\\d{11}$/',
'jcb' => '/^(3\\d{4}|2100|1800)\\d{11}$/',
'maestro' => '/^(?:5020|6\\d{3})\\d{12}$/',
'mc' => '/^5[1-5]\\d{14}$/',
'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})?$/',
'switch' =>
'/^(?:49(03(0[2-9]|3[5-9])|11(0[1-2]|7[4-9]|8[1-2])|36[0-9]{2})\\d{10}(\\d{2,3})?)|(?:564182\\d{10}(\\d{2,3})?)|(6(3(33[0-4][0-9])|759[0-9]{2})\\d{10}(\\d{2,3})?)$/',
@ -672,7 +672,7 @@ class Validation {
// Exchange and 555-XXXX numbers
$regex .= '(?!(555(?:\s*(?:[.\-\s]\s*))(01([0-9][0-9])|1212)))';
$regex .= '(?!(555(01([0-9][0-9])|1212)))';
$regex .= '([2-9]1[02-9]|[2-9]1[02-9]|[2-9][02-9]1|[2-9][02-9]{2})\s*(?:[.-]\s*)';
$regex .= '([2-9]1[02-9]|[2-9][02-9]1|[2-9][0-9]{2})\s*(?:[.-]\s*)';
// Local number and extension
$regex .= '?([0-9]{4})';
@ -1036,7 +1036,17 @@ class Validation {
if (isset($options['types']) && !static::mimeType($file, $options['types'])) {
return false;
}
return true;
return static::_isUploadedFile($file['tmp_name']);
}
/**
* Helper method that can be stubbed in testing.
*
* @param string $path The path to check.
* @return bool Whether or not the file is an uploaded file.
*/
protected static function _isUploadedFile($path) {
return is_uploaded_file($path);
}
/**

View file

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

View file

@ -382,6 +382,7 @@ class FormHelper extends AppHelper {
if (isset($options['action'])) {
trigger_error('Using key `action` is deprecated, use `url` directly instead.', E_USER_DEPRECATED);
}
if (is_array($options['url']) && isset($options['url']['action'])) {
$options['action'] = $options['url']['action'];
}
@ -393,7 +394,7 @@ class FormHelper extends AppHelper {
if ($options['action'] === null && $options['url'] === null) {
$options['action'] = $this->request->here(false);
} elseif (is_array($options['url'])) {
} elseif (empty($options['url']) || is_array($options['url'])) {
if (empty($options['url']['controller'])) {
if (!empty($model)) {
$options['url']['controller'] = Inflector::underscore(Inflector::pluralize($model));
@ -611,11 +612,13 @@ class FormHelper extends AppHelper {
$tokenFields = array_merge($secureAttributes, array(
'value' => urlencode($fields . ':' . $locked),
'id' => 'TokenFields' . mt_rand(),
'secure' => static::SECURE_SKIP,
));
$out = $this->hidden('_Token.fields', $tokenFields);
$tokenUnlocked = array_merge($secureAttributes, array(
'value' => urlencode($unlocked),
'id' => 'TokenUnlocked' . mt_rand(),
'secure' => static::SECURE_SKIP,
));
$out .= $this->hidden('_Token.unlocked', $tokenUnlocked);
return $this->Html->useTag('hiddenblock', $out);
@ -1870,6 +1873,7 @@ class FormHelper extends AppHelper {
unset($options['target']);
}
$previousLastAction = $this->_lastAction;
$this->_lastAction($url);
$out = $this->Html->useTag('form', $formUrl, $formOptions);
@ -1882,7 +1886,7 @@ class FormHelper extends AppHelper {
if (isset($options['data']) && is_array($options['data'])) {
foreach (Hash::flatten($options['data']) as $key => $value) {
$fields[$key] = $value;
$out .= $this->hidden($key, array('value' => $value, 'id' => false));
$out .= $this->hidden($key, array('value' => $value, 'id' => false, 'secure' => static::SECURE_SKIP));
}
unset($options['data']);
}
@ -1892,6 +1896,8 @@ class FormHelper extends AppHelper {
if ($options['block']) {
$this->_View->append($options['block'], $out);
$out = '';
// Reset security-relevant fields for outer form
$this->_lastAction = $previousLastAction;
}
unset($options['block']);
@ -2783,6 +2789,11 @@ class FormHelper extends AppHelper {
$selectedIsEmpty = ($attributes['value'] === '' || $attributes['value'] === null);
$selectedIsArray = is_array($attributes['value']);
// Cast boolean false into an integer so string comparisons can work.
if ($attributes['value'] === false) {
$attributes['value'] = 0;
}
$this->_domIdSuffixes = array();
foreach ($elements as $name => $title) {
$htmlOptions = array();