diff --git a/cake/dispatcher.php b/cake/dispatcher.php index abfd56d5d..ef63a3243 100644 --- a/cake/dispatcher.php +++ b/cake/dispatcher.php @@ -582,8 +582,12 @@ class Dispatcher extends Object { App::import('View', 'View', false); } $controller = null; - $view =& new View($controller, false); - return $view->renderCache($filename, getMicrotime()); + $view =& new View($controller); + $return = $view->renderCache($filename, getMicrotime()); + if (!$return) { + ClassRegistry::removeObject('view'); + } + return $return; } } diff --git a/cake/libs/controller/components/security.php b/cake/libs/controller/components/security.php index 20de444b9..868804cff 100644 --- a/cake/libs/controller/components/security.php +++ b/cake/libs/controller/components/security.php @@ -652,6 +652,10 @@ class SecurityComponent extends Object { */ function _generateToken(&$controller) { if (isset($controller->params['requested']) && $controller->params['requested'] === 1) { + if ($this->Session->check('_Token')) { + $tokenData = unserialize($this->Session->read('_Token')); + $controller->params['_Token'] = $tokenData; + } return false; } $authKey = Security::generateAuthKey(); @@ -682,7 +686,6 @@ class SecurityComponent extends Object { } $controller->params['_Token'] = $token; $this->Session->write('_Token', serialize($token)); - return true; } diff --git a/cake/libs/l10n.php b/cake/libs/l10n.php index 9d84da4c9..20064c83f 100644 --- a/cake/libs/l10n.php +++ b/cake/libs/l10n.php @@ -398,7 +398,7 @@ class L10n extends Object { * @access private */ function __autoLanguage() { - $_detectableLanguages = split('[,;]', env('HTTP_ACCEPT_LANGUAGE')); + $_detectableLanguages = preg_split('/[,;]/', env('HTTP_ACCEPT_LANGUAGE')); foreach ($_detectableLanguages as $key => $langKey) { $langKey = strtolower($langKey); if (strpos($langKey, '_') !== false) { diff --git a/cake/libs/model/datasources/dbo/dbo_mysql.php b/cake/libs/model/datasources/dbo/dbo_mysql.php index 814ffcb31..d980cc428 100644 --- a/cake/libs/model/datasources/dbo/dbo_mysql.php +++ b/cake/libs/model/datasources/dbo/dbo_mysql.php @@ -535,20 +535,20 @@ class DboMysql extends DboMysqlBase { */ function connect() { $config = $this->config; - $connect = $config['connect']; $this->connected = false; if (!$config['persistent']) { $this->connection = mysql_connect($config['host'] . ':' . $config['port'], $config['login'], $config['password'], true); + $config['connect'] = 'mysql_connect'; } else { - $this->connection = $connect($config['host'] . ':' . $config['port'], $config['login'], $config['password']); + $this->connection = mysql_pconnect($config['host'] . ':' . $config['port'], $config['login'], $config['password']); } if (mysql_select_db($config['database'], $this->connection)) { $this->connected = true; } - if (isset($config['encoding']) && !empty($config['encoding'])) { + if (!empty($config['encoding'])) { $this->setEncoding($config['encoding']); } diff --git a/cake/libs/model/datasources/dbo_source.php b/cake/libs/model/datasources/dbo_source.php index dcaf89980..af2607fa2 100644 --- a/cake/libs/model/datasources/dbo_source.php +++ b/cake/libs/model/datasources/dbo_source.php @@ -1760,7 +1760,9 @@ class DboSource extends DataSource { if ($count >= 1 && !in_array($fields[0], array('*', 'COUNT(*)'))) { for ($i = 0; $i < $count; $i++) { - if (preg_match('/^\(.*\)\s' . $this->alias . '.*/i', $fields[$i])){ + if (is_object($fields[$i]) && isset($fields[$i]->type) && $fields[$i]->type === 'expression') { + $fields[$i] = $fields[$i]->value; + } elseif (preg_match('/^\(.*\)\s' . $this->alias . '.*/i', $fields[$i])){ continue; } elseif (!preg_match('/^.+\\(.*\\)/', $fields[$i])) { $prepend = ''; diff --git a/cake/libs/model/model.php b/cake/libs/model/model.php index 726f628db..c35418fe7 100644 --- a/cake/libs/model/model.php +++ b/cake/libs/model/model.php @@ -2417,7 +2417,8 @@ class Model extends Overloadable { } /** - * Returns true if all fields pass validation. + * Returns true if all fields pass validation. Will validate hasAndBelongsToMany associations + * that use the 'with' key as well. Since __saveMulti is incapable of exiting a save operation. * * Will validate the currently set data. Use Model::set() or Model::create() to set the active data. * @@ -2428,6 +2429,9 @@ class Model extends Overloadable { */ function validates($options = array()) { $errors = $this->invalidFields($options); + if (empty($errors) && $errors !== false) { + $errors = $this->__validateWithModels($options); + } if (is_array($errors)) { return count($errors) === 0; } @@ -2435,7 +2439,7 @@ class Model extends Overloadable { } /** - * Returns an array of fields that have failed validation. + * Returns an array of fields that have failed validation. On the current model. * * @param string $options An optional array of custom options to be made available in the beforeValidate callback * @return array Array of invalid fields @@ -2593,6 +2597,43 @@ class Model extends Overloadable { return $this->validationErrors; } +/** + * Runs validation for hasAndBelongsToMany associations that have 'with' keys + * set. And data in the set() data set. + * + * @param array $options Array of options to use on Valdation of with models + * @return boolean Failure of validation on with models. + * @access private + * @see Model::validates() + */ + function __validateWithModels($options) { + $valid = true; + foreach ($this->hasAndBelongsToMany as $assoc => $association) { + if (empty($association['with']) || !isset($this->data[$assoc])) { + continue; + } + list($join) = $this->joinModel($this->hasAndBelongsToMany[$assoc]['with']); + $data = $this->data[$assoc]; + + $newData = array(); + foreach ((array)$data as $row) { + if (isset($row[$this->hasAndBelongsToMany[$assoc]['associationForeignKey']])) { + $newData[] = $row; + } elseif (isset($row[$join]) && isset($row[$join][$this->hasAndBelongsToMany[$assoc]['associationForeignKey']])) { + $newData[] = $row[$join]; + } + } + if (empty($newData)) { + continue; + } + foreach ($newData as $data) { + $data[$this->hasAndBelongsToMany[$assoc]['foreignKey']] = $this->id; + $this->{$join}->create($data); + $valid = ($valid && $this->{$join}->validates($options)); + } + } + return $valid; + } /** * Marks a field as invalid, optionally setting the name of validation * rule (in case of multiple validation for field) that was broken. diff --git a/cake/libs/set.php b/cake/libs/set.php index 2320f7949..40929479a 100644 --- a/cake/libs/set.php +++ b/cake/libs/set.php @@ -459,7 +459,7 @@ class Set { $item = $items[$token]; $matches[] = array( 'trace' => array_merge($context['trace'], $ctext), - 'key' => $key, + 'key' => $token, 'item' => $item, ); break; diff --git a/cake/tests/cases/dispatcher.test.php b/cake/tests/cases/dispatcher.test.php index 47aa91d3f..2938d9630 100644 --- a/cake/tests/cases/dispatcher.test.php +++ b/cake/tests/cases/dispatcher.test.php @@ -490,6 +490,15 @@ class TestCachedPagesController extends AppController { function view($id = null) { $this->render('index'); } +/** + * test cached forms / tests view object being registered + * + * @return void + */ + function cache_form() { + $this->cacheAction = 10; + $this->helpers[] = 'Form'; + } } /** @@ -2088,6 +2097,48 @@ class DispatcherTest extends CakeTestCase { $filename = $this->__cachePath($dispatcher->here); $this->assertTrue(file_exists($filename)); unlink($filename); + + $url = 'TestCachedPages/test_nocache_tags'; + } +/** + * test that cached() registers a view and un-registers it. Tests + * that helpers using ClassRegistry::getObject('view'); don't fail + * + * @return void + */ + function testCachedRegisteringViewObject() { + Configure::write('Cache.disable', false); + Configure::write('Cache.check', true); + Configure::write('debug', 2); + + $_POST = array(); + $_SERVER['PHP_SELF'] = '/'; + + Router::reload(); + Configure::write('viewPaths', array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views'. DS)); + + $dispatcher =& new Dispatcher(); + $dispatcher->base = false; + + $url = 'test_cached_pages/cache_form'; + ob_start(); + $dispatcher->dispatch($url); + $out = ob_get_clean(); + + ClassRegistry::flush(); + + ob_start(); + $dispatcher->cached($url); + $cached = ob_get_clean(); + + $result = str_replace(array("\t", "\r\n", "\n"), "", $out); + $cached = preg_replace('//', '', $cached); + $expected = str_replace(array("\t", "\r\n", "\n"), "", $cached); + + $this->assertEqual($result, $expected); + $filename = $this->__cachePath($dispatcher->here); + unlink($filename); + ClassRegistry::flush(); } /** diff --git a/cake/tests/cases/libs/controller/components/security.test.php b/cake/tests/cases/libs/controller/components/security.test.php index f9024902d..26774e200 100644 --- a/cake/tests/cases/libs/controller/components/security.test.php +++ b/cake/tests/cases/libs/controller/components/security.test.php @@ -1177,5 +1177,23 @@ DIGEST; $this->assertEqual(count($this->Controller->testHeaders), 1); $this->assertEqual(current($this->Controller->testHeaders), $expected); } + +/** + * test that a requestAction's controller will have the _Token appended to + * the params. + * + * @return void + * @see http://cakephp.lighthouseapp.com/projects/42648/tickets/68 + */ + function testSettingTokenForRequestAction() { + $this->Controller->Security->startup($this->Controller); + $key = $this->Controller->params['_Token']['key']; + + $this->Controller->params['requested'] = 1; + unset($this->Controller->params['_Token']); + + $this->Controller->Security->startup($this->Controller); + $this->assertEqual($this->Controller->params['_Token']['key'], $key); + } } ?> \ No newline at end of file diff --git a/cake/tests/cases/libs/model/datasources/dbo_source.test.php b/cake/tests/cases/libs/model/datasources/dbo_source.test.php index 865000320..b304c5038 100644 --- a/cake/tests/cases/libs/model/datasources/dbo_source.test.php +++ b/cake/tests/cases/libs/model/datasources/dbo_source.test.php @@ -2928,6 +2928,20 @@ class DboSourceTest extends CakeTestCase { $this->assertEqual($result, $expected); } +/** + * test that fields() will accept objects made from DboSource::expression + * + * @return void + */ + function testFieldsWithExpression() { + $expression =& $this->testDb->expression("CASE Sample.id WHEN 1 THEN 'Id One' ELSE 'Other Id' END AS case_col"); + $result = $this->testDb->fields($this->Model, null, array("id", $expression)); + $expected = array( + '`TestModel`.`id`', + "CASE Sample.id WHEN 1 THEN 'Id One' ELSE 'Other Id' END AS case_col" + ); + $this->assertEqual($result, $expected); + } /** * testMergeAssociations method * diff --git a/cake/tests/cases/libs/model/model_validation.test.php b/cake/tests/cases/libs/model/model_validation.test.php index 640b3b9a5..c70f6a1bf 100644 --- a/cake/tests/cases/libs/model/model_validation.test.php +++ b/cake/tests/cases/libs/model/model_validation.test.php @@ -119,6 +119,103 @@ class ModelValidationTest extends BaseModelTest { $this->assertEqual($TestModel->validate, $validate); } +/** + * test that validates() checks all the 'with' associations as well for validation + * as this can cause partial/wrong data insertion. + * + * @return void + */ + function testValidatesWithAssociations() { + $data = array( + 'Something' => array( + 'id' => 5, + 'title' => 'Extra Fields', + 'body' => 'Extra Fields Body', + 'published' => '1' + ), + 'SomethingElse' => array( + array('something_else_id' => 1, 'doomed' => '') + ) + ); + + $Something =& new Something(); + $JoinThing =& $Something->JoinThing; + + $JoinThing->validate = array('doomed' => array('rule' => 'notEmpty')); + + $expectedError = array('doomed' => 'This field cannot be left blank'); + + $Something->create(); + $result = $Something->save($data); + $this->assertFalse($result, 'Save occured even when with models failed. %s'); + $this->assertEqual($JoinThing->validationErrors, $expectedError); + $count = $Something->find('count', array('conditions' => array('Something.id' => $data['Something']['id']))); + $this->assertIdentical($count, 0); + + $data = array( + 'Something' => array( + 'id' => 5, + 'title' => 'Extra Fields', + 'body' => 'Extra Fields Body', + 'published' => '1' + ), + 'SomethingElse' => array( + array('something_else_id' => 1, 'doomed' => 1), + array('something_else_id' => 1, 'doomed' => '') + ) + ); + $Something->create(); + $result = $Something->save($data); + $this->assertFalse($result, 'Save occured even when with models failed. %s'); + + $joinRecords = $JoinThing->find('count', array( + 'conditions' => array('JoinThing.something_id' => $data['Something']['id']) + )); + $this->assertEqual($joinRecords, 0, 'Records were saved on the join table. %s'); + } + +/** + * test that saveAll and with models with validation interact well + * + * @return void + */ + function testValidatesWithModelsAndSaveAll() { + $data = array( + 'Something' => array( + 'id' => 5, + 'title' => 'Extra Fields', + 'body' => 'Extra Fields Body', + 'published' => '1' + ), + 'SomethingElse' => array( + array('something_else_id' => 1, 'doomed' => '') + ) + ); + $Something =& new Something(); + $JoinThing =& $Something->JoinThing; + + $JoinThing->validate = array('doomed' => array('rule' => 'notEmpty')); + $expectedError = array('doomed' => 'This field cannot be left blank'); + + $Something->create(); + $result = $Something->saveAll($data, array('validate' => 'only')); + $this->assertFalse($result); + $this->assertEqual($JoinThing->validationErrors, $expectedError); + + $Something->create(); + $result = $Something->saveAll($data, array('validate' => 'first')); + $this->assertFalse($result); + $this->assertEqual($JoinThing->validationErrors, $expectedError); + + $count = $Something->find('count', array('conditions' => array('Something.id' => $data['Something']['id']))); + $this->assertIdentical($count, 0); + + $joinRecords = $JoinThing->find('count', array( + 'conditions' => array('JoinThing.something_id' => $data['Something']['id']) + )); + $this->assertEqual($joinRecords, 0, 'Records were saved on the join table. %s'); + } + /** * Test that missing validation methods trigger errors in development mode. * Helps to make developement easier. diff --git a/cake/tests/cases/libs/set.test.php b/cake/tests/cases/libs/set.test.php index 3050c599f..c0f55d2a2 100644 --- a/cake/tests/cases/libs/set.test.php +++ b/cake/tests/cases/libs/set.test.php @@ -1030,6 +1030,22 @@ class SetTest extends CakeTestCase { $this->assertEqual($result, $expected); } +/** + * testExtractWithArrays method + * + * @access public + * @return void + */ + function testExtractWithArrays() { + $data = array( + 'Level1' => array( + 'Level2' => array('test1', 'test2'), + 'Level2bis' => array('test3', 'test4') + ) + ); + $this->assertEqual(Set::extract('/Level1/Level2', $data), array(array('Level2' => array('test1', 'test2')))); + $this->assertEqual(Set::extract('/Level1/Level2bis', $data), array(array('Level2bis' => array('test3', 'test4')))); + } /** * testMatches method * diff --git a/cake/tests/cases/libs/xml.test.php b/cake/tests/cases/libs/xml.test.php index c4fb1b465..a5f2d8a9e 100644 --- a/cake/tests/cases/libs/xml.test.php +++ b/cake/tests/cases/libs/xml.test.php @@ -187,6 +187,22 @@ class XmlTest extends CakeTestCase { $result = $xml->toString(false); $expected = '00'; $this->assertEqual($expected, $result); + + $data = array( + 'Client' => array( + 'id' => 3, + 'object_id' => 9, + 'key' => 'alt', + 'name' => 'Client Two', + 'created_by' => 4, + 'status' => '0', + 'num_projects' => 0 + ) + ); + $xml = new Xml($data, array('format' => 'tags')); + $result = $xml->toString(array('format' => 'tags', 'header' => false)); + $this->assertPattern('/0<\/status>/', $result); + $this->assertPattern('/0<\/num_projects>/', $result); } /** * testHeader method diff --git a/cake/tests/test_app/views/posts/cache_form.ctp b/cake/tests/test_app/views/posts/cache_form.ctp new file mode 100644 index 000000000..87ec9cf5b --- /dev/null +++ b/cake/tests/test_app/views/posts/cache_form.ctp @@ -0,0 +1,14 @@ +
+ + create('User');?> +
+ + input('username'); + echo $form->input('email'); + echo $form->input('password'); + ?> +
+ end('Submit');?> +
+
\ No newline at end of file