diff --git a/lib/Cake/Controller/Component/PaginatorComponent.php b/lib/Cake/Controller/Component/PaginatorComponent.php index ecc1f2ec4e4..cc82ab259b2 100644 --- a/lib/Cake/Controller/Component/PaginatorComponent.php +++ b/lib/Cake/Controller/Component/PaginatorComponent.php @@ -333,7 +333,7 @@ public function validateSort($object, $options, $whitelist = array()) { $options['order'] = array($options['sort'] => $direction); } - if (!empty($whitelist)) { + if (!empty($whitelist) && isset($options['order']) && is_array($options['order'])) { $field = key($options['order']); if (!in_array($field, $whitelist)) { $options['order'] = null; diff --git a/lib/Cake/Core/Configure.php b/lib/Cake/Core/Configure.php index 2772eb0842e..67cabfc50ac 100644 --- a/lib/Cake/Core/Configure.php +++ b/lib/Cake/Core/Configure.php @@ -312,7 +312,7 @@ public static function load($key, $config = 'default', $merge = true) { $keys = array_keys($values); foreach ($keys as $key) { if (($c = self::read($key)) && is_array($values[$key]) && is_array($c)) { - $values[$key] = array_merge_recursive($c, $values[$key]); + $values[$key] = Set::merge($c, $values[$key]); } } } diff --git a/lib/Cake/Model/Behavior/TranslateBehavior.php b/lib/Cake/Model/Behavior/TranslateBehavior.php index bc72c8e8edd..52ab421d93f 100644 --- a/lib/Cake/Model/Behavior/TranslateBehavior.php +++ b/lib/Cake/Model/Behavior/TranslateBehavior.php @@ -34,6 +34,20 @@ class TranslateBehavior extends ModelBehavior { */ public $runtime = array(); +/** + * Stores the joinTable object for generating joins. + * + * @var object + */ + var $_joinTable; + +/** + * Stores the runtime model for generating joins. + * + * @var Model + */ + var $_runtimeModel; + /** * Callback * @@ -94,6 +108,7 @@ public function beforeFind($model, $query) { } $db = $model->getDataSource(); $RuntimeModel = $this->translateModel($model); + if (!empty($RuntimeModel->tablePrefix)) { $tablePrefix = $RuntimeModel->tablePrefix; } else { @@ -104,8 +119,11 @@ public function beforeFind($model, $query) { $joinTable->table = $RuntimeModel->table; $joinTable->schemaName = $RuntimeModel->getDataSource()->getSchemaName(); + $this->_joinTable = $joinTable; + $this->_runtimeModel = $RuntimeModel; + if (is_string($query['fields']) && 'COUNT(*) AS ' . $db->name('count') == $query['fields']) { - $query['fields'] = 'COUNT(DISTINCT(' . $db->name($model->alias . '.' . $model->primaryKey) . ')) ' . $db->alias . 'count'; + $query['fields'] = 'COUNT(DISTINCT('.$db->name($model->alias . '.' . $model->primaryKey) . ')) ' . $db->alias . 'count'; $query['joins'][] = array( 'type' => 'INNER', 'alias' => $RuntimeModel->alias, @@ -116,6 +134,11 @@ public function beforeFind($model, $query) { $RuntimeModel->alias.'.locale' => $locale ) ); + $conditionFields = $this->_checkConditions($model, $query); + foreach ($conditionFields as $field) { + $query = $this->_addJoin($model, $query, $field, $field, $locale); + } + unset($this->_joinTable, $this->_runtimeModel); return $query; } @@ -145,45 +168,93 @@ public function beforeFind($model, $query) { unset($query['fields'][$key]); } } + $query = $this->_addJoin($model, $query, $field, $aliasField, $locale); + } + } + $this->runtime[$model->alias]['beforeFind'] = $addFields; + unset($this->_joinTable, $this->_runtimeModel); + return $query; + } - if (is_array($locale)) { - foreach ($locale as $_locale) { - $model->virtualFields['i18n_' . $field . '_' . $_locale] = 'I18n__' . $field . '__' . $_locale . '.content'; - if (!empty($query['fields'])) { - $query['fields'][] = 'i18n_' . $field . '_' . $_locale; - } - $query['joins'][] = array( - 'type' => 'LEFT', - 'alias' => 'I18n__' . $field . '__' . $_locale, - 'table' => $joinTable, - 'conditions' => array( - $model->alias . '.' . $model->primaryKey => $db->identifier("I18n__{$field}__{$_locale}.foreign_key"), - 'I18n__' . $field . '__' . $_locale . '.model' => $model->name, - 'I18n__' . $field . '__' . $_locale . '.' . $RuntimeModel->displayField => $aliasField, - 'I18n__' . $field . '__' . $_locale . '.locale' => $_locale - ) - ); - } - } else { - $model->virtualFields['i18n_' . $field] = 'I18n__' . $field . '.content'; - if (!empty($query['fields'])) { - $query['fields'][] = 'i18n_' . $field; - } - $query['joins'][] = array( - 'type' => 'INNER', - 'alias' => 'I18n__' . $field, - 'table' => $joinTable, - 'conditions' => array( - $model->alias . '.' . $model->primaryKey => $db->identifier("I18n__{$field}.foreign_key"), - 'I18n__' . $field . '.model' => $model->name, - 'I18n__' . $field . '.' . $RuntimeModel->displayField => $aliasField, - 'I18n__' . $field . '.locale' => $locale - ) - ); +/** + * Check a query's conditions for translated fields. + * Return an array of translated fields found in the conditions. + * + * @param Model $model The model being read. + * @param array $query The query array. + * @return array The list of translated fields that are in the conditions. + */ + protected function _checkConditions(Model $model, $query) { + $conditionFields = array(); + if (empty($query['conditions']) || (!empty($query['conditions']) && !is_array($query['conditions'])) ) { + return $conditionFields; + } + foreach ($query['conditions'] as $col => $val) { + foreach ($this->settings[$model->alias] as $field => $assoc) { + if (is_numeric($field)) { + $field = $assoc; + } + if (strpos($col, $field) !== false) { + $conditionFields[] = $field; } } } - $this->runtime[$model->alias]['beforeFind'] = $addFields; + return $conditionFields; + } + +/** + * Appends a join for translated fields and possibly a field. + * + * @param Model $model The model being worked on. + * @param object $joinTable The jointable object. + * @param array $query The query array to append a join to. + * @param string $field The field name being joined. + * @param string $aliasField The aliased field name being joined. + * @param mixed $locale The locale(s) having joins added. + * @param boolean $addField Whether or not to add a field. + * @return array The modfied query + */ + protected function _addJoin(Model $model, $query, $field, $aliasField, $locale, $addField = false) { + $db = ConnectionManager::getDataSource($model->useDbConfig); + + $RuntimeModel = $this->_runtimeModel; + $joinTable = $this->_joinTable; + + if (is_array($locale)) { + foreach ($locale as $_locale) { + $model->virtualFields['i18n_' . $field . '_' . $_locale] = 'I18n__' . $field . '__' . $_locale . '.content'; + if (!empty($query['fields']) && is_array($query['fields'])) { + $query['fields'][] = 'i18n_'.$field.'_'.$_locale; + } + $query['joins'][] = array( + 'type' => 'LEFT', + 'alias' => 'I18n__'.$field.'__'.$_locale, + 'table' => $joinTable, + 'conditions' => array( + $model->alias . '.' . $model->primaryKey => $db->identifier("I18n__{$field}__{$_locale}.foreign_key"), + 'I18n__'.$field.'__'.$_locale.'.model' => $model->name, + 'I18n__'.$field.'__'.$_locale.'.'.$RuntimeModel->displayField => $aliasField, + 'I18n__'.$field.'__'.$_locale.'.locale' => $_locale + ) + ); + } + } else { + $model->virtualFields['i18n_' . $field] = 'I18n__' . $field . '.content'; + if (!empty($query['fields']) && is_array($query['fields'])) { + $query['fields'][] = 'i18n_'.$field; + } + $query['joins'][] = array( + 'type' => 'INNER', + 'alias' => 'I18n__'.$field, + 'table' => $joinTable, + 'conditions' => array( + $model->alias . '.' . $model->primaryKey => $db->identifier("I18n__{$field}.foreign_key"), + 'I18n__'.$field.'.model' => $model->name, + 'I18n__'.$field.'.'.$RuntimeModel->displayField => $aliasField, + 'I18n__'.$field.'.locale' => $locale + ) + ); + } return $query; } diff --git a/lib/Cake/Model/BehaviorCollection.php b/lib/Cake/Model/BehaviorCollection.php index 3b8bcbd6ef7..a9edf032415 100644 --- a/lib/Cake/Model/BehaviorCollection.php +++ b/lib/Cake/Model/BehaviorCollection.php @@ -107,6 +107,9 @@ public function load($behavior, $config = array()) { $alias = $behavior; $behavior = $config['className']; } + $configDisabled = isset($config['enabled']) && $config['enabled'] === false; + unset($config['enabled'], $config['className']); + list($plugin, $name) = pluginSplit($behavior, true); if (!isset($alias)) { $alias = $name; @@ -166,8 +169,7 @@ public function load($behavior, $config = array()) { } } - $enable = isset($config['enabled']) ? $config['enabled'] : true; - if ($enable) { + if (!in_array($alias, $this->_enabled) && !$configDisabled) { $this->enable($alias); } else { $this->disable($alias); diff --git a/lib/Cake/Model/Datasource/Database/Sqlserver.php b/lib/Cake/Model/Datasource/Database/Sqlserver.php index c912bc5fe49..2b9bc7d7d31 100644 --- a/lib/Cake/Model/Datasource/Database/Sqlserver.php +++ b/lib/Cake/Model/Datasource/Database/Sqlserver.php @@ -182,7 +182,7 @@ public function listSources($data = null) { } else { $tables = array(); - while ($line = $result->fetch()) { + while ($line = $result->fetch(PDO::FETCH_NUM)) { $tables[] = $line[0]; } @@ -222,7 +222,7 @@ public function describe($model) { throw new CakeException(__d('cake_dev', 'Could not describe table for %s', $table)); } - foreach ($cols as $column) { + while ($column = $cols->fetch(PDO::FETCH_OBJ)) { $field = $column->Field; $fields[$field] = array( 'type' => $this->column($column), @@ -645,14 +645,7 @@ public function insertMulti($table, $fields, $values) { $this->_execute('SET IDENTITY_INSERT ' . $this->fullTableName($table) . ' ON'); } - $table = $this->fullTableName($table); - $fields = implode(', ', array_map(array(&$this, 'name'), $fields)); - $this->begin(); - foreach ($values as $value) { - $holder = implode(', ', array_map(array(&$this, 'value'), $value)); - $this->_execute("INSERT INTO {$table} ({$fields}) VALUES ({$holder})"); - } - $this->commit(); + parent::insertMulti($table, $fields, $values); if ($hasPrimaryKey) { $this->_execute('SET IDENTITY_INSERT ' . $this->fullTableName($table) . ' OFF'); @@ -717,9 +710,6 @@ public function buildIndex($indexes, $table = null) { * @return string */ protected function _getPrimaryKey($model) { - if (!is_object($model)) { - $model = new Model(false, $model); - } $schema = $this->describe($model); foreach ($schema as $field => $props) { if (isset($props['key']) && $props['key'] == 'primary') { diff --git a/lib/Cake/Test/Case/Controller/Component/PaginatorComponentTest.php b/lib/Cake/Test/Case/Controller/Component/PaginatorComponentTest.php index fe6c961a31e..814fa6b7d12 100644 --- a/lib/Cake/Test/Case/Controller/Component/PaginatorComponentTest.php +++ b/lib/Cake/Test/Case/Controller/Component/PaginatorComponentTest.php @@ -776,6 +776,26 @@ public function testValidateSortMultiple() { $this->assertEquals($expected, $result['order']); } +/** + * Test that no sort doesn't trigger an error. + * + * @return void + */ + public function testValidateSortNoSort() { + $model = $this->getMock('Model'); + $model->alias = 'model'; + $model->expects($this->any())->method('hasField')->will($this->returnValue(true)); + + $options = array('direction' => 'asc'); + $result = $this->Paginator->validateSort($model, $options, array('title', 'id')); + $this->assertFalse(isset($result['order'])); + + $options = array('order' => 'invalid desc'); + $result = $this->Paginator->validateSort($model, $options, array('title', 'id')); + + $this->assertEquals($options['order'], $result['order']); + } + /** * test that maxLimit is respected * diff --git a/lib/Cake/Test/Case/Core/ConfigureTest.php b/lib/Cake/Test/Case/Core/ConfigureTest.php index 746412fdfed..5e94b574943 100644 --- a/lib/Cake/Test/Case/Core/ConfigureTest.php +++ b/lib/Cake/Test/Case/Core/ConfigureTest.php @@ -224,6 +224,8 @@ public function testLoadWithMerge() { $this->assertEquals('value2', Configure::read('Read')); $this->assertEquals('buried2', Configure::read('Deep.Second.SecondDeepest')); $this->assertEquals('buried', Configure::read('Deep.Deeper.Deepest')); + $this->assertEquals('Overwrite', Configure::read('TestAcl.classname')); + $this->assertEquals('one', Configure::read('TestAcl.custom')); } /** diff --git a/lib/Cake/Test/Case/Model/Behavior/TranslateBehaviorTest.php b/lib/Cake/Test/Case/Model/Behavior/TranslateBehaviorTest.php index 89fa6a864f9..538eb7623be 100644 --- a/lib/Cake/Test/Case/Model/Behavior/TranslateBehaviorTest.php +++ b/lib/Cake/Test/Case/Model/Behavior/TranslateBehaviorTest.php @@ -58,6 +58,24 @@ public function tearDown() { ClassRegistry::flush(); } +/** + * Test that count queries with conditions get the correct joins + * + * @return void + */ + function testCountWithConditions() { + $this->loadFixtures('Translate', 'TranslatedItem'); + + $Model =& new TranslatedItem(); + $Model->locale = 'eng'; + $result = $Model->find('count', array( + 'conditions' => array( + 'I18n__content.locale' => 'eng' + ) + )); + $this->assertEqual(3, $result); + } + /** * testTranslateModel method * diff --git a/lib/Cake/Test/Case/Model/BehaviorCollectionTest.php b/lib/Cake/Test/Case/Model/BehaviorCollectionTest.php index 6c23daf4c8e..f29bc88fe0e 100644 --- a/lib/Cake/Test/Case/Model/BehaviorCollectionTest.php +++ b/lib/Cake/Test/Case/Model/BehaviorCollectionTest.php @@ -419,9 +419,22 @@ class BehaviorCollectionTest extends CakeTestCase { */ public $fixtures = array( 'core.apple', 'core.sample', 'core.article', 'core.user', 'core.comment', - 'core.attachment', 'core.tag', 'core.articles_tag' + 'core.attachment', 'core.tag', 'core.articles_tag', 'core.translate' ); +/** + * Test load() with enabled => false + * + */ + public function testLoadDisabled() { + $Apple = new Apple(); + $this->assertSame($Apple->Behaviors->attached(), array()); + + $Apple->Behaviors->load('Translate', array('enabled' => false)); + $this->assertTrue($Apple->Behaviors->attached('Translate')); + $this->assertFalse($Apple->Behaviors->enabled('Translate')); + } + /** * Tests loading aliased behaviors */ diff --git a/lib/Cake/Test/Case/Model/Datasource/Database/SqlserverTest.php b/lib/Cake/Test/Case/Model/Datasource/Database/SqlserverTest.php index 05890dac11b..95394bf62dd 100644 --- a/lib/Cake/Test/Case/Model/Datasource/Database/SqlserverTest.php +++ b/lib/Cake/Test/Case/Model/Datasource/Database/SqlserverTest.php @@ -111,7 +111,7 @@ public function describe($model) { * * @package Cake.Test.Case.Model.Datasource.Database */ -class SqlserverTestModel extends Model { +class SqlserverTestModel extends CakeTestModel { /** * name property @@ -183,7 +183,7 @@ public function find($conditions = null, $fields = null, $order = null, $recursi * * @package Cake.Test.Case.Model.Datasource.Database */ -class SqlserverClientTestModel extends Model { +class SqlserverClientTestModel extends CakeTestModel { /** * name property * @@ -224,6 +224,20 @@ class SqlserverTestResultIterator extends ArrayIterator { * @return void */ public function closeCursor() {} + +/** + * fetch method + * + * @return void + */ + public function fetch() { + if (!$this->valid()) { + return null; + } + $current = $this->current(); + $this->next(); + return $current; + } } /** @@ -283,7 +297,7 @@ public function tearDown() { * @return void */ public function testQuoting() { - $expected = "1.200000"; + $expected = "1.2"; $result = $this->db->value(1.2, 'float'); $this->assertSame($expected, $result); @@ -586,43 +600,6 @@ public function testGetPrimaryKey() { $this->assertNull($result); } -/** - * testInsertMulti - * - * @return void - */ - public function testInsertMulti() { - $this->db->describe = $this->model->schema(); - - $fields = array('id', 'name', 'login'); - $values = array( - array(1, 'Larry', 'PhpNut'), - array(2, 'Renan', 'renan.saddam')); - $this->db->simulated = array(); - $this->db->insertMulti($this->model, $fields, $values); - $result = $this->db->simulated; - $expected = array( - 'SET IDENTITY_INSERT [sqlserver_test_models] ON', - "INSERT INTO [sqlserver_test_models] ([id], [name], [login]) VALUES (1, N'Larry', N'PhpNut')", - "INSERT INTO [sqlserver_test_models] ([id], [name], [login]) VALUES (2, N'Renan', N'renan.saddam')", - 'SET IDENTITY_INSERT [sqlserver_test_models] OFF' - ); - $this->assertEquals($expected, $result); - - $fields = array('name', 'login'); - $values = array( - array('Larry', 'PhpNut'), - array('Renan', 'renan.saddam')); - $this->db->simulated = array(); - $this->db->insertMulti($this->model, $fields, $values); - $result = $this->db->simulated; - $expected = array( - "INSERT INTO [sqlserver_test_models] ([name], [login]) VALUES (N'Larry', N'PhpNut')", - "INSERT INTO [sqlserver_test_models] ([name], [login]) VALUES (N'Renan', N'renan.saddam')", - ); - $this->assertEquals($expected, $result); - } - /** * SQL server < 11 doesn't have proper limit/offset support, test that our hack works. * diff --git a/lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php b/lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php index a1755bebc42..4031a49d65f 100644 --- a/lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php +++ b/lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php @@ -493,6 +493,9 @@ public function testDirectCallThrowsException() { * @return void */ public function testValue() { + if ($this->db instanceof Sqlserver) { + $this->markTestSkipped('Cannot run this test with SqlServer'); + } $result = $this->db->value('{$__cakeForeignKey__$}'); $this->assertEquals($result, '{$__cakeForeignKey__$}'); diff --git a/lib/Cake/Test/Case/Model/ModelIntegrationTest.php b/lib/Cake/Test/Case/Model/ModelIntegrationTest.php index 0209b0833a9..329a4f08565 100644 --- a/lib/Cake/Test/Case/Model/ModelIntegrationTest.php +++ b/lib/Cake/Test/Case/Model/ModelIntegrationTest.php @@ -208,11 +208,11 @@ public function testPkInHabtmLinkModel() { public function testDynamicBehaviorAttachment() { $this->loadFixtures('Apple', 'Sample', 'Author'); $TestModel = new Apple(); - $this->assertEquals($TestModel->Behaviors->attached(), array()); + $this->assertEquals(array(), $TestModel->Behaviors->attached()); $TestModel->Behaviors->attach('Tree', array('left' => 'left_field', 'right' => 'right_field')); $this->assertTrue(is_object($TestModel->Behaviors->Tree)); - $this->assertEquals($TestModel->Behaviors->attached(), array('Tree')); + $this->assertEquals(array('Tree'), $TestModel->Behaviors->attached()); $expected = array( 'parent' => 'parent_id', @@ -223,16 +223,14 @@ public function testDynamicBehaviorAttachment() { '__parentChange' => false, 'recursive' => -1 ); + $this->assertEquals($expected, $TestModel->Behaviors->Tree->settings['Apple']); - $this->assertEquals($TestModel->Behaviors->Tree->settings['Apple'], $expected); - - $expected['enabled'] = false; $TestModel->Behaviors->attach('Tree', array('enabled' => false)); - $this->assertEquals($TestModel->Behaviors->Tree->settings['Apple'], $expected); - $this->assertEquals($TestModel->Behaviors->attached(), array('Tree')); + $this->assertEquals($expected, $TestModel->Behaviors->Tree->settings['Apple']); + $this->assertEquals(array('Tree'), $TestModel->Behaviors->attached()); $TestModel->Behaviors->detach('Tree'); - $this->assertEquals($TestModel->Behaviors->attached(), array()); + $this->assertEquals(array(), $TestModel->Behaviors->attached()); $this->assertFalse(isset($TestModel->Behaviors->Tree)); } diff --git a/lib/Cake/Test/test_app/Config/var_test.php b/lib/Cake/Test/test_app/Config/var_test.php index e0f3ae2a199..44c345563d1 100644 --- a/lib/Cake/Test/test_app/Config/var_test.php +++ b/lib/Cake/Test/test_app/Config/var_test.php @@ -5,5 +5,8 @@ 'Deeper' => array( 'Deepest' => 'buried' ) + ), + 'TestAcl' => array( + 'classname' => 'Original' ) -); \ No newline at end of file +); diff --git a/lib/Cake/Test/test_app/Config/var_test2.php b/lib/Cake/Test/test_app/Config/var_test2.php index 33345b4ec8d..745b75dbc7d 100644 --- a/lib/Cake/Test/test_app/Config/var_test2.php +++ b/lib/Cake/Test/test_app/Config/var_test2.php @@ -5,5 +5,9 @@ 'Second' => array( 'SecondDeepest' => 'buried2' ) + ), + 'TestAcl' => array( + 'classname' => 'Overwrite', + 'custom' => 'one' ) -); \ No newline at end of file +);