diff --git a/app/config/core.php b/app/config/core.php index 1de429cc5..56e3b716a 100644 --- a/app/config/core.php +++ b/app/config/core.php @@ -166,7 +166,7 @@ Configure::write('Session.cookie', 'CAKEPHP'); /** - * Session time out time (in seconds). + * Session time out time (in minutes). * Actual value depends on 'Security.level' setting. */ Configure::write('Session.timeout', '120'); @@ -187,9 +187,9 @@ * in 'Session.timeout' is multiplied according to the settings here. * Valid values: * - * 'high' Session timeout in 'Session.timeout' x 10 - * 'medium' Session timeout in 'Session.timeout' x 5040 - * 'low' Session timeout in 'Session.timeout' x 2628000 + * 'high' Session timeout in 'Session.timeout' x 10 + * 'medium' Session timeout in 'Session.timeout' x 100 + * 'low' Session timeout in 'Session.timeout' x 300 * * CakePHP session IDs are also regenerated between requests if * 'Security.level' is set to 'high'. diff --git a/cake/console/templates/skel/config/core.php b/cake/console/templates/skel/config/core.php index 5a2ddc4d3..68c238f17 100644 --- a/cake/console/templates/skel/config/core.php +++ b/cake/console/templates/skel/config/core.php @@ -29,7 +29,6 @@ * Development Mode: * 1: Errors and warnings shown, model caches refreshed, flash messages halted. * 2: As in 1, but also with full debug messages and SQL output. - * 3: As in 2, but also with full controller dump. * * In production mode, flash messages redirect after a time interval. * In development mode, you need to click the flash message to continue. @@ -72,6 +71,9 @@ /** * Uncomment the define below to use CakePHP prefix routes. * + * The value of the define determines the names of the routes + * and their associated controller actions: + * * Set to an array of prefixes you want to use in your application. Use for * admin or other prefixed routes. * @@ -80,6 +82,8 @@ * Enables: * `admin_index()` and `/admin/controller/index` * `manager_index()` and `/manager/controller/index` + * + * [Note Routing.admin is deprecated in 1.3. Use Routing.prefixes instead] */ //Configure::write('Routing.prefixes', array('admin')); @@ -154,11 +158,16 @@ /** * The name of CakePHP's session cookie. + * + * Note the guidelines for Session names states: "The session name references + * the session id in cookies and URLs. It should contain only alphanumeric + * characters." + * @link http://php.net/session_name */ Configure::write('Session.cookie', 'CAKEPHP'); /** - * Session time out time (in seconds). + * Session time out time (in minutes). * Actual value depends on 'Security.level' setting. */ Configure::write('Session.timeout', '120'); @@ -179,9 +188,9 @@ * in 'Session.timeout' is multiplied according to the settings here. * Valid values: * - * 'high' Session timeout in 'Session.timeout' x 10 - * 'medium' Session timeout in 'Session.timeout' x 100 - * 'low' Session timeout in 'Session.timeout' x 300 + * 'high' Session timeout in 'Session.timeout' x 10 + * 'medium' Session timeout in 'Session.timeout' x 100 + * 'low' Session timeout in 'Session.timeout' x 300 * * CakePHP session IDs are also regenerated between requests if * 'Security.level' is set to 'high'. @@ -198,6 +207,15 @@ */ Configure::write('Security.cipherSeed', '76859309657453542496749683645'); +/** + * Apply timestamps with the last modified time to static assets (js, css, images). + * Will append a querystring parameter containing the time the file was modified. This is + * useful for invalidating browser caches. + * + * Set to `true` to apply timestamps, when debug = 0, or set to 'force' to always enable + * timestamping. + */ + //Configure::write('Asset.timestamp', true); /** * Compress CSS output by removing comments, whitespace, repeating tags, etc. * This requires a/var/cache directory to be writable by the web server for caching. @@ -228,11 +246,6 @@ */ //date_default_timezone_set('UTC'); -/** - * If you are on PHP 5.3 uncomment this line and correct your server timezone - * to fix the date & time related errors. - */ - //date_default_timezone_set('UTC'); /** * * Cache Engine Configuration diff --git a/cake/dispatcher.php b/cake/dispatcher.php index 44a3bf4e7..98d0665ff 100644 --- a/cake/dispatcher.php +++ b/cake/dispatcher.php @@ -345,20 +345,15 @@ class Dispatcher { if ($parts[0] === 'theme') { $themeName = $parts[1]; unset($parts[0], $parts[1]); - $fileFragment = implode('/', $parts); - - $viewPaths = App::path('views'); - foreach ($viewPaths as $viewPath) { - $path = $viewPath . 'themed' . DS . $themeName . DS . 'webroot' . DS; - if (file_exists($path . $fileFragment)) { - $assetFile = $path . $fileFragment; - break; - } + $fileFragment = implode(DS, $parts); + $path = App::themePath($themeName) . 'webroot' . DS; + if (file_exists($path . $fileFragment)) { + $assetFile = $path . $fileFragment; } } else { $plugin = $parts[0]; unset($parts[0]); - $fileFragment = implode('/', $parts); + $fileFragment = implode(DS, $parts); $pluginWebroot = App::pluginPath($plugin) . 'webroot' . DS; if (file_exists($pluginWebroot . $fileFragment)) { $assetFile = $pluginWebroot . $fileFragment; diff --git a/cake/libs/cake_session.php b/cake/libs/cake_session.php index 7bac0a34f..e0d824988 100644 --- a/cake/libs/cake_session.php +++ b/cake/libs/cake_session.php @@ -98,6 +98,14 @@ class CakeSession extends Object { */ public $sessionTime = false; +/** + * The number of seconds to set for session.cookie_lifetime. 0 means + * at browser close. + * + * @var integer + */ + var $cookieLifeTime = false; + /** * Keeps track of keys to watch for writes on * @@ -125,7 +133,7 @@ class CakeSession extends Object { /** * Session timeout multiplier factor * - * @var ineteger + * @var integer * @access public */ public $timeout = null; @@ -453,8 +461,13 @@ class CakeSession extends Object { } if ($iniSet && ($this->security === 'high' || $this->security === 'medium')) { ini_set('session.referer_check', $this->host); + } + + if ($this->security == 'high') { + $this->cookieLifeTime = 0; + } else { + $this->cookieLifeTime = Configure::read('Session.timeout') * (Security::inactiveMins() * 60); } - $this->cookieLifeTime = Configure::read('Session.timeout') * Security::inactiveMins(); switch (Configure::read('Session.save')) { case 'cake': @@ -582,7 +595,7 @@ class CakeSession extends Object { if (time() > ($time - (Security::inactiveMins() * Configure::read('Session.timeout')) + 2) || $check < 1) { $this->renew(); - $this->write('Config.timeout', Security::inactiveMins()); + $this->write('Config.timeout', 10); } } $this->valid = true; @@ -594,7 +607,7 @@ class CakeSession extends Object { } else { $this->write('Config.userAgent', $this->_userAgent); $this->write('Config.time', $this->sessionTime); - $this->write('Config.timeout', Security::inactiveMins()); + $this->write('Config.timeout', 10); $this->valid = true; $this->__setError(1, 'Session is valid'); } diff --git a/cake/libs/configure.php b/cake/libs/configure.php index af789ea40..cdd1d1497 100644 --- a/cake/libs/configure.php +++ b/cake/libs/configure.php @@ -668,7 +668,7 @@ class App extends Object { /** * Get the path that a plugin is on. Searches through the defined plugin paths. * - * @param string $plugin CamelCased plugin name to find the path of. + * @param string $plugin CamelCased/lower_cased plugin name to find the path of. * @return string full path to the plugin. */ public static function pluginPath($plugin) { @@ -682,6 +682,23 @@ class App extends Object { return $_this->plugins[0] . $pluginDir . DS; } +/** + * Find the path that a theme is on. Search through the defined theme paths. + * + * @param string $theme lower_cased theme name to find the path of. + * @return string full path to the theme. + */ + public static function themePath($theme) { + $_this = App::getInstance(); + $themeDir = 'themed' . DS . Inflector::underscore($theme); + for ($i = 0, $length = count($_this->views); $i < $length; $i++) { + if (is_dir($_this->views[$i] . $themeDir)) { + return $_this->views[$i] . $themeDir . DS ; + } + } + return $_this->views[0] . $themeDir . DS; + } + /** * Returns a key/value list of all paths where core libs are found. * Passing $type only returns the values for a given value of $key. @@ -723,7 +740,11 @@ class App extends Object { } /** - * Returns an index of objects of the given type, with the physical path to each object. + * Returns an array of objects of the given type. + * + * Example usage: + * + * `App::objects('plugin');` returns `array('DebugKit', 'Blog', 'User');` * * @param string $type Type of object, i.e. 'model', 'controller', 'helper', or 'plugin' * @param mixed $path Optional Scan only the path given. If null, paths for the chosen diff --git a/cake/libs/controller/components/request_handler.php b/cake/libs/controller/components/request_handler.php index 524c5ad5d..0231922bb 100644 --- a/cake/libs/controller/components/request_handler.php +++ b/cake/libs/controller/components/request_handler.php @@ -609,7 +609,6 @@ class RequestHandlerComponent extends Object { * like 'application/x-shockwave'. * @param array $options If $type is a friendly type name that is associated with * more than one type of content, $index is used to select which content-type to use. - * * @return boolean Returns false if the friendly type name given in $type does * not exist in the type map, or if the Content-type header has * already been set by this method. @@ -618,9 +617,6 @@ class RequestHandlerComponent extends Object { */ function respondAs($type, $options = array()) { $this->__initializeTypes(); - if ($this->__responseTypeSet != null) { - return false; - } if (!array_key_exists($type, $this->__requestContent) && strpos($type, '/') === false) { return false; } @@ -657,10 +653,10 @@ class RequestHandlerComponent extends Object { $header .= '; charset=' . $options['charset']; } if (!empty($options['attachment'])) { - header("Content-Disposition: attachment; filename=\"{$options['attachment']}\""); + $this->_header("Content-Disposition: attachment; filename=\"{$options['attachment']}\""); } if (Configure::read() < 2 && !defined('CAKEPHP_SHELL')) { - @header($header); + $this->_header($header); } $this->__responseTypeSet = $cType; return true; @@ -668,6 +664,16 @@ class RequestHandlerComponent extends Object { return false; } +/** + * Wrapper for header() so calls can be easily tested. + * + * @param string $header The header to be sent. + * @return void + */ + protected function _header($header) { + header($header); + } + /** * Returns the current response type (Content-type header), or null if none has been set * diff --git a/cake/libs/model/datasources/dbo_source.php b/cake/libs/model/datasources/dbo_source.php index cacb4e119..0067435e5 100755 --- a/cake/libs/model/datasources/dbo_source.php +++ b/cake/libs/model/datasources/dbo_source.php @@ -53,7 +53,10 @@ class DboSource extends DataSource { public $alias = 'AS '; /** - * Caches result from query parsing operations + * Caches result from query parsing operations. Cached results for both DboSource::name() and + * DboSource::conditions() will be stored here. Method caching uses `crc32()` which is + * fast but can collisions more easily than other hashing algorithms. If you have problems + * with collisions, set DboSource::$cacheMethods to false. * * @var array * @access public @@ -497,7 +500,12 @@ class DboSource extends DataSource { * Returns a quoted name of $data for use in an SQL statement. * Strips fields out of SQL functions before quoting. * - * @param string $data + * Results of this method are stored in a memory cache. This improves performance, but + * because the method uses a simple hashing algorithm it can infrequently have collisions. + * Setting DboSource::$cacheMethods to false will disable the memory cache. + * + * @param mixed $data Either a string with a column to quote. An array of columns to quote or an + * object from DboSource::expression() or DboSource::identifier() * @return string SQL field */ public function name($data) { @@ -2008,6 +2016,10 @@ class DboSource extends DataSource { * conditions are provided those conditions will be parsed and quoted. If a boolean * is given it will be integer cast as condition. Null will return 1 = 1. * + * Results of this method are stored in a memory cache. This improves performance, but + * because the method uses a simple hashing algorithm it can infrequently have collisions. + * Setting DboSource::$cacheMethods to false will disable the memory cache. + * * @param mixed $conditions Array or string of conditions, or any value. * @param boolean $quoteValues If true, values should be quoted * @param boolean $where If true, "WHERE " will be prepended to the return value diff --git a/cake/libs/view/helper.php b/cake/libs/view/helper.php index 4078eee92..4753f952a 100644 --- a/cake/libs/view/helper.php +++ b/cake/libs/view/helper.php @@ -241,7 +241,22 @@ class Helper extends Object { ); if (strpos($path, '?') === false && $timestampEnabled) { $filepath = preg_replace('/^' . preg_quote($this->request->webroot, '/') . '/', '', $path); - $path .= '?' . @filemtime(WWW_ROOT . str_replace('/', DS, $filepath)); + $webrootPath = WWW_ROOT . str_replace('/', DS, $filepath); + if (file_exists($webrootPath)) { + return $path . '?' . @filemtime($webrootPath); + } + $segments = explode('/', ltrim($filepath, '/')); + if ($segments[0] === 'theme') { + $theme = $segments[1]; + unset($segments[0], $segments[1]); + $themePath = App::themePath($theme) . 'webroot' . DS . implode(DS, $segments); + return $path . '?' . @filemtime($themePath); + } else { + $plugin = $segments[0]; + unset($segments[0]); + $pluginPath = App::pluginPath($plugin) . 'webroot' . DS . implode(DS, $segments); + return $path . '?' . @filemtime($pluginPath); + } } return $path; } diff --git a/cake/libs/view/helpers/form.php b/cake/libs/view/helpers/form.php index 82cbfc529..c1d0f8247 100755 --- a/cake/libs/view/helpers/form.php +++ b/cake/libs/view/helpers/form.php @@ -584,6 +584,13 @@ class FormHelper extends AppHelper { * )); * }}} * + * In addition to fields control, inputs() allows you to use a few additional options. + * + * - `fieldset` Set to false to disable the fieldset. If a string is supplied it will be used as + * the classname for the fieldset element. + * - `legend` Set to false to disable the legend for the generated input set. Or supply a string + * to customize the legend text. + * * @param mixed $fields An array of fields to generate inputs for, or null. * @param array $blacklist a simple array of fields to not create inputs for. * @return string Completed form inputs. diff --git a/cake/libs/view/helpers/html.php b/cake/libs/view/helpers/html.php index a86c07fbf..f82b9359b 100644 --- a/cake/libs/view/helpers/html.php +++ b/cake/libs/view/helpers/html.php @@ -593,6 +593,7 @@ class HtmlHelper extends AppHelper { if (!empty($this->_crumbs)) { $result = ''; $crumbCount = count($this->_crumbs); + $ulOptions = $options; foreach ($this->_crumbs as $which => $crumb) { $options = array(); if (empty($crumb[1])) { @@ -607,7 +608,7 @@ class HtmlHelper extends AppHelper { } $result .= $this->tag('li', $elementContent, $options); } - return $this->tag('ul', $result, $options); + return $this->tag('ul', $result, $ulOptions); } else { return null; } diff --git a/cake/libs/view/view.php b/cake/libs/view/view.php index a4b44219b..fb83fc8fd 100644 --- a/cake/libs/view/view.php +++ b/cake/libs/view/view.php @@ -620,9 +620,9 @@ class View extends Object { ($count == 1 && !empty($this->association)) || ($count == 1 && $this->model != $this->entityPath) || ($count == 2 && !empty($this->fieldSuffix)) || - is_numeric($path[0]) + is_numeric($path[0]) && !empty($assoc) ) { - array_unshift($path,$assoc); + array_unshift($path, $assoc); } return Set::filter($path); } diff --git a/cake/tests/cases/libs/configure.test.php b/cake/tests/cases/libs/configure.test.php index 92307fb1a..aacd0729b 100644 --- a/cake/tests/cases/libs/configure.test.php +++ b/cake/tests/cases/libs/configure.test.php @@ -460,6 +460,26 @@ class AppImportTest extends CakeTestCase { App::build(); } +/** + * test that pluginPath can find paths for plugins. + * + * @return void + */ + function testThemePath() { + App::build(array( + 'views' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views' . DS) + )); + $path = App::themePath('test_theme'); + $expected = TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views' . DS . 'themed' . DS . 'test_theme' . DS; + $this->assertEqual($path, $expected); + + $path = App::themePath('TestTheme'); + $expected = TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views' . DS . 'themed' . DS . 'test_theme' . DS; + $this->assertEqual($path, $expected); + + App::build(); + } + /** * testClassLoading method * diff --git a/cake/tests/cases/libs/controller/components/request_handler.test.php b/cake/tests/cases/libs/controller/components/request_handler.test.php index f57a1cccd..b653de26c 100644 --- a/cake/tests/cases/libs/controller/components/request_handler.test.php +++ b/cake/tests/cases/libs/controller/components/request_handler.test.php @@ -299,6 +299,51 @@ class RequestHandlerComponentTest extends CakeTestCase { $this->assertEqual($this->Controller->viewPath, 'request_handler_test' . DS . 'js'); } +/** + * test that respondAs works as expected. + * + * @return void + */ + function testRespondAs() { + $debug = Configure::read('debug'); + Configure::write('debug', 0); + + $RequestHandler = $this->getMock('RequestHandlerComponent', array('_header')); + $RequestHandler->expects($this->at(0))->method('_header') + ->with('Content-type: application/json'); + $RequestHandler->expects($this->at(1))->method('_header') + ->with('Content-type: text/xml'); + + $result = $RequestHandler->respondAs('json'); + $this->assertTrue($result); + + $result = $RequestHandler->respondAs('text/xml'); + $this->assertTrue($result); + + Configure::write('debug', $debug); + } + +/** + * test that attachment headers work with respondAs + * + * @return void + */ + function testRespondAsWithAttachment() { + $debug = Configure::read('debug'); + Configure::write('debug', 0); + + $RequestHandler = $this->getMock('RequestHandlerComponent', array('_header')); + $RequestHandler->expects($this->at(0))->method('_header') + ->with('Content-Disposition: attachment; filename="myfile.xml"'); + $RequestHandler->expects($this->at(1))->method('_header') + ->with('Content-type: application/xml'); + + $result = $RequestHandler->respondAs('xml', array('attachment' => 'myfile.xml')); + $this->assertTrue($result); + + Configure::write('debug', $debug); + } + /** * test that calling renderAs() more than once continues to work. * @@ -559,9 +604,8 @@ class RequestHandlerComponentTest extends CakeTestCase { 'views' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views'. DS) ), true); - $this->Controller->RequestHandler = new NoStopRequestHandler($this); - $this->Controller->RequestHandler->request = $this->Controller->request; - $this->Controller->RequestHandler->expectOnce('_stop'); + $this->Controller->RequestHandler = $this->getMock('RequestHandlerComponent', array('_stop')); + $this->Controller->RequestHandler->expects($this->once())->method('_stop'); ob_start(); $this->Controller->RequestHandler->beforeRedirect( @@ -604,7 +648,7 @@ class RequestHandlerComponentTest extends CakeTestCase { } /** - * assure that beforeRedirect with a status code will correctly set the status header + * assure that beforeRedirect with a status code will correctly set the status header * * @return void */ diff --git a/cake/tests/cases/libs/controller/components/session.test.php b/cake/tests/cases/libs/controller/components/session.test.php index 62c6b2fb5..1debd798b 100644 --- a/cake/tests/cases/libs/controller/components/session.test.php +++ b/cake/tests/cases/libs/controller/components/session.test.php @@ -339,7 +339,7 @@ class SessionComponentTest extends CakeTestCase { $Session->destroy('Test'); $this->assertNull($Session->read('Test')); } - + /** * testSessionTimeout method * @@ -352,25 +352,30 @@ class SessionComponentTest extends CakeTestCase { Configure::write('Security.level', 'low'); $Session = new SessionComponent(); $Session->write('Test', 'some value'); - $this->assertEqual($_SESSION['Config']['timeout'], Security::inactiveMins()); + + $this->assertEqual($Session->sessionTime, mktime() + (300 * Configure::read('Session.timeout'))); + $this->assertEqual($_SESSION['Config']['timeout'], 10); $this->assertEqual($_SESSION['Config']['time'], $Session->sessionTime); $this->assertEqual($Session->time, mktime()); - $this->assertEqual($_SESSION['Config']['time'], $Session->time + (Security::inactiveMins() * Configure::read('Session.timeout'))); + $this->assertEqual($_SESSION['Config']['time'], $Session->time + (Security::inactiveMins() * Configure::read('Session.timeout'))); session_destroy(); Configure::write('Security.level', 'medium'); $Session = new SessionComponent(); $Session->write('Test', 'some value'); - $this->assertEqual($_SESSION['Config']['timeout'], Security::inactiveMins()); + $this->assertEqual($Session->sessionTime, mktime() + (100 * Configure::read('Session.timeout'))); + $this->assertEqual($_SESSION['Config']['timeout'], 10); $this->assertEqual($_SESSION['Config']['time'], $Session->sessionTime); $this->assertEqual($Session->time, mktime()); - $this->assertEqual($_SESSION['Config']['time'], $Session->time + (Security::inactiveMins() * Configure::read('Session.timeout'))); - + $this->assertEqual($_SESSION['Config']['time'], $Session->time + (Security::inactiveMins() * Configure::read('Session.timeout'))); + + session_destroy(); Configure::write('Security.level', 'high'); $Session = new SessionComponent(); $Session->write('Test', 'some value'); - $this->assertEqual($_SESSION['Config']['timeout'], Security::inactiveMins()); + $this->assertEqual($Session->sessionTime, mktime() + (10 * Configure::read('Session.timeout'))); + $this->assertEqual($_SESSION['Config']['timeout'], 10); $this->assertEqual($_SESSION['Config']['time'], $Session->sessionTime); $this->assertEqual($Session->time, mktime()); $this->assertEqual($_SESSION['Config']['time'], $Session->time + (Security::inactiveMins() * Configure::read('Session.timeout'))); diff --git a/cake/tests/cases/libs/view/helper.test.php b/cake/tests/cases/libs/view/helper.test.php index 840597442..a2b97ec83 100644 --- a/cake/tests/cases/libs/view/helper.test.php +++ b/cake/tests/cases/libs/view/helper.test.php @@ -492,6 +492,35 @@ class HelperTest extends CakeTestCase { Configure::write('Asset.timestamp', $_timestamp); } +/** + * test assetTimestamp with plugins and themes + * + * @return void + */ + function testAssetTimestampPluginsAndThemes() { + $_timestamp = Configure::read('Asset.timestamp'); + Configure::write('Asset.timestamp', 'force'); + App::build(array( + 'plugins' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'plugins' . DS), + 'views' => array(TEST_CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'test_app' . DS . 'views' . DS), + )); + + $result = $this->Helper->assetTimestamp('/test_plugin/css/test_plugin_asset.css'); + $this->assertPattern('#/test_plugin/css/test_plugin_asset.css\?[0-9]+$#', $result, 'Missing timestamp plugin'); + + $result = $this->Helper->assetTimestamp('/test_plugin/css/i_dont_exist.css'); + $this->assertPattern('#/test_plugin/css/i_dont_exist.css\?$#', $result, 'No error on missing file'); + + $result = $this->Helper->assetTimestamp('/theme/test_theme/js/theme.js'); + $this->assertPattern('#/theme/test_theme/js/theme.js\?[0-9]+$#', $result, 'Missing timestamp theme'); + + $result = $this->Helper->assetTimestamp('/theme/test_theme/js/non_existant.js'); + $this->assertPattern('#/theme/test_theme/js/non_existant.js\?$#', $result, 'No error on missing file'); + + App::build(); + Configure::write('Asset.timestamp', $_timestamp); + } + /** * testFieldsWithSameName method * diff --git a/cake/tests/cases/libs/view/helpers/form.test.php b/cake/tests/cases/libs/view/helpers/form.test.php index 46d6f9d5f..58266ec35 100644 --- a/cake/tests/cases/libs/view/helpers/form.test.php +++ b/cake/tests/cases/libs/view/helpers/form.test.php @@ -2034,6 +2034,19 @@ class FormHelperTest extends CakeTestCase { } } +/** + * test input name with leading integer, ensure attributes are generated correctly. + * + * @return void + */ + function testInputWithLeadingInteger() { + $result = $this->Form->text('0.Node.title'); + $expected = array( + 'input' => array('name' => 'data[0][Node][title]', 'id' => '0NodeTitle', 'type' => 'text') + ); + $this->assertTags($result, $expected); + } + /** * test form->input() with select type inputs. * diff --git a/cake/tests/cases/libs/view/view.test.php b/cake/tests/cases/libs/view/view.test.php index 935a42d89..d4009798b 100644 --- a/cake/tests/cases/libs/view/view.test.php +++ b/cake/tests/cases/libs/view/view.test.php @@ -896,6 +896,14 @@ class ViewTest extends CakeTestCase { $View->association = 'Comment'; $View->field = 'user_id'; $this->assertEqual($View->entity(), array('Comment', 'user_id')); + + $View->model = 0; + $View->association = null; + $View->field = 'Node'; + $View->fieldSuffix = 'title'; + $View->entityPath = '0.Node.title'; + $expected = array(0, 'Node', 'title'); + $this->assertEqual($View->entity(), $expected); } /**