diff --git a/lib/Cake/Test/Case/View/ViewTest.php b/lib/Cake/Test/Case/View/ViewTest.php index dea04508730..7fe844c2e6b 100644 --- a/lib/Cake/Test/Case/View/ViewTest.php +++ b/lib/Cake/Test/Case/View/ViewTest.php @@ -68,6 +68,75 @@ public function nocache_multiple_element() { } } +/** + * ThemePostsController class + * + * @package Cake.Test.Case.View + */ +class ThemePostsController extends Controller { + +/** + * name property + * + * @var string 'ThemePosts' + */ + public $name = 'ThemePosts'; + + public $theme = null; + +/** + * index method + * + * @return void + */ + public function index() { + $this->set('testData', 'Some test data'); + $test2 = 'more data'; + $test3 = 'even more data'; + $this->set(compact('test2', 'test3')); + } +} + +/** + * TestThemeView class + * + * @package Cake.Test.Case.View + */ +class TestThemeView extends View { + +/** + * renderElement method + * + * @param mixed $name + * @param array $params + * @return void + */ + public function renderElement($name, $params = array()) { + return $name; + } + +/** + * getViewFileName method + * + * @param mixed $name + * @return void + */ + public function getViewFileName($name = null) { + return $this->_getViewFileName($name); + } + +/** + * getLayoutFileName method + * + * @param mixed $name + * @return void + */ + public function getLayoutFileName($name = null) { + return $this->_getLayoutFileName($name); + } + +} + /** * TestView class * @@ -191,14 +260,25 @@ public function setUp() { $this->PostsController->viewPath = 'Posts'; $this->PostsController->index(); $this->View = new View($this->PostsController); + + $themeRequest = new CakeRequest('posts/index'); + $this->ThemeController = new Controller($themeRequest); + $this->ThemePostsController = new ThemePostsController($themeRequest); + $this->ThemePostsController->viewPath = 'posts'; + $this->ThemePostsController->index(); + $this->ThemeView = new View($this->ThemePostsController); + App::build(array( 'plugins' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS), - 'View' => array( - CAKE . 'Test' . DS . 'test_app' . DS . 'View'. DS - ) + 'View' => array(CAKE . 'Test' . DS . 'test_app' . DS . 'View'. DS) ), true); + App::objects('plugins', null, false); + CakePlugin::load(array('TestPlugin', 'TestPlugin', 'PluginJs')); Configure::write('debug', 2); + + CakePlugin::loadAll(); + } /** @@ -212,6 +292,46 @@ public function tearDown() { unset($this->View); unset($this->PostsController); unset($this->Controller); + unset($this->ThemeView); + unset($this->ThemePostsController); + unset($this->ThemeController); + + } +/** + * testGetTemplate method + * + * @return void + */ + public function testGetTemplate() { + $this->Controller->plugin = null; + $this->Controller->name = 'Pages'; + $this->Controller->viewPath = 'Pages'; + $this->Controller->action = 'display'; + $this->Controller->params['pass'] = array('home'); + + $ThemeView = new TestThemeView($this->Controller); + $ThemeView->theme = 'TestTheme'; + $expected = CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS .'Pages' . DS .'home.ctp'; + $result = $ThemeView->getViewFileName('home'); + $this->assertEquals($expected, $result); + + $expected = CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS . 'Themed' . DS . 'TestTheme' . DS . 'Posts' . DS .'index.ctp'; + $result = $ThemeView->getViewFileName('/Posts/index'); + $this->assertEquals($expected, $result); + + $expected = CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS . 'Themed' . DS . 'TestTheme' . DS . 'Layouts' . DS .'default.ctp'; + $result = $ThemeView->getLayoutFileName(); + $this->assertEquals($expected, $result); + + $ThemeView->layoutPath = 'rss'; + $expected = CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS . 'Layouts' . DS . 'rss' . DS . 'default.ctp'; + $result = $ThemeView->getLayoutFileName(); + $this->assertEquals($expected, $result); + + $ThemeView->layoutPath = 'Emails' . DS . 'html'; + $expected = CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS . 'Layouts' . DS . 'Emails' . DS . 'html' . DS . 'default.ctp'; + $result = $ThemeView->getLayoutFileName(); + $this->assertEquals($expected, $result); } /** @@ -236,6 +356,32 @@ public function testPluginGetTemplate() { $this->assertEquals($expected, $result); } +/** + * testPluginGetTemplate method + * + * @return void + */ + public function testPluginThemedGetTemplate() { + $this->Controller->plugin = 'TestPlugin'; + $this->Controller->name = 'TestPlugin'; + $this->Controller->viewPath = 'Tests'; + $this->Controller->action = 'index'; + $this->Controller->theme = 'TestTheme'; + + $ThemeView = new TestThemeView($this->Controller); + $expected = CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS . 'Themed' . DS . 'TestTheme' . DS . 'Plugin' . DS . 'TestPlugin' . DS . 'Tests' . DS .'index.ctp'; + $result = $ThemeView->getViewFileName('index'); + $this->assertEquals($expected, $result); + + $expected = CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS . 'Themed' . DS . 'TestTheme' . DS . 'Plugin' . DS . 'TestPlugin' . DS . 'Layouts' . DS .'plugin_default.ctp'; + $result = $ThemeView->getLayoutFileName('plugin_default'); + $this->assertEquals($expected, $result); + + $expected = CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS . 'Themed' . DS . 'TestTheme' . DS . 'Layouts' . DS .'default.ctp'; + $result = $ThemeView->getLayoutFileName('default'); + $this->assertEquals($expected, $result); + } + /** * test that plugin/$plugin_name is only appended to the paths it should be. * @@ -400,6 +546,21 @@ public function testMissingView() { $View = new TestView($this->Controller); ob_start(); $result = $View->getViewFileName('does_not_exist'); + + $this->ThemeController->plugin = null; + $this->ThemeController->name = 'Pages'; + $this->ThemeController->viewPath = 'Pages'; + $this->ThemeController->action = 'display'; + $this->ThemeController->theme = 'my_theme'; + + $this->ThemeController->params['pass'] = array('home'); + + $View = new TestThemeView($this->ThemeController); + ob_start(); + $result = $View->getViewFileName('does_not_exist'); + $expected = str_replace(array("\t", "\r\n", "\n"), "", ob_get_clean()); + $this->assertRegExp("/PagesController::/", $expected); + $this->assertRegExp("/views(\/|\\\)themed(\/|\\\)my_theme(\/|\\\)pages(\/|\\\)does_not_exist.ctp/", $expected); } /** @@ -418,6 +579,19 @@ public function testMissingLayout() { ob_start(); $result = $View->getLayoutFileName(); $expected = str_replace(array("\t", "\r\n", "\n"), "", ob_get_clean()); + + $this->ThemeController->plugin = null; + $this->ThemeController->name = 'Posts'; + $this->ThemeController->viewPath = 'posts'; + $this->ThemeController->layout = 'whatever'; + $this->ThemeController->theme = 'my_theme'; + + $View = new TestThemeView($this->ThemeController); + ob_start(); + $result = $View->getLayoutFileName(); + $expected = str_replace(array("\t", "\r\n", "\n"), "", ob_get_clean()); + $this->assertRegExp("/Missing Layout/", $expected); + $this->assertRegExp("/views(\/|\\\)themed(\/|\\\)my_theme(\/|\\\)layouts(\/|\\\)whatever.ctp/", $expected); } /** @@ -1262,4 +1436,27 @@ public function testPropertySettingMagicGet() { $this->assertTrue(isset($this->View->action)); $this->assertTrue(!empty($this->View->action)); } + +/** + * test memory leaks that existed in _paths at one point. + * + * @return void + */ + public function testMemoryLeakInPaths() { + $this->ThemeController->plugin = null; + $this->ThemeController->name = 'Posts'; + $this->ThemeController->viewPath = 'posts'; + $this->ThemeController->layout = 'whatever'; + $this->ThemeController->theme = 'TestTheme'; + + $View = new View($this->ThemeController); + $View->element('test_element'); + + $start = memory_get_usage(); + for ($i = 0; $i < 10; $i++) { + $View->element('test_element'); + } + $end = memory_get_usage(); + $this->assertLessThanOrEqual($start + 5000, $end); + } } diff --git a/lib/Cake/View/ThemeView.php b/lib/Cake/View/ThemeView.php index 13cfcafb0b7..3b36aa07814 100644 --- a/lib/Cake/View/ThemeView.php +++ b/lib/Cake/View/ThemeView.php @@ -22,53 +22,10 @@ /** * Theme view class * - * Allows the creation of multiple themes to be used in an app. Theme views are regular view files - * that can provide unique HTML and static assets. If theme views are not found for the current view - * the default app view files will be used. You can set `$this->theme` and `$this->viewClass = 'Theme'` - * in your Controller to use the ThemeView. - * - * Example of theme path with `$this->theme = 'SuperHot';` Would be `app/View/Themed/SuperHot/Posts` + * Stub class for 2.1 Compatibility * * @package Cake.View */ class ThemeView extends View { -/** - * Constructor for ThemeView sets $this->theme. - * - * @param Controller $controller Controller object to be rendered. - */ - public function __construct($controller) { - parent::__construct($controller); - if ($controller) { - $this->theme = $controller->theme; - } - } - -/** - * Return all possible paths to find view files in order - * - * @param string $plugin The name of the plugin views are being found for. - * @param boolean $cached Set to true to force dir scan. - * @return array paths - * @todo Make theme path building respect $cached parameter. - */ - protected function _paths($plugin = null, $cached = true) { - $paths = parent::_paths($plugin, $cached); - $themePaths = array(); - - if (!empty($this->theme)) { - $count = count($paths); - for ($i = 0; $i < $count; $i++) { - if (strpos($paths[$i], DS . 'Plugin' . DS) === false - && strpos($paths[$i], DS . 'Cake' . DS . 'View') === false) { - if ($plugin) { - $themePaths[] = $paths[$i] . 'Themed'. DS . $this->theme . DS . 'Plugin' . DS . $plugin . DS; - } - $themePaths[] = $paths[$i] . 'Themed'. DS . $this->theme . DS; - } - } - $paths = array_merge($themePaths, $paths); - } - return $paths; - } + } diff --git a/lib/Cake/View/View.php b/lib/Cake/View/View.php index df019bdaba7..db5db15b183 100644 --- a/lib/Cake/View/View.php +++ b/lib/Cake/View/View.php @@ -33,6 +33,12 @@ * and then inserted into the selected layout. This also means you can pass data from the view to the * layout using `$this->set()` * + * Since 2.1, the base View class also includes support for themes by default. Theme views are regular + * view files that can provide unique HTML and static assets. If theme views are not found for the + * current view the default app view files will be used. You can set `$this->theme = 'mytheme'` + * in your Controller to use the Themes. + * + * Example of theme path with `$this->theme = 'SuperHot';` Would be `app/View/Themed/SuperHot/Posts` * * @package Cake.View * @property CacheHelper $Cache @@ -299,6 +305,9 @@ public function __construct($controller) { $this->{$var} = $controller->{$var}; } $this->_eventManager = $controller->getEventManager(); + if (!empty($controller->theme)) { + $this->theme = $controller->theme; + } } $this->Helpers = new HelperCollection($this); $this->Blocks = new ViewBlock(); @@ -1061,6 +1070,20 @@ protected function _paths($plugin = null, $cached = true) { } $paths = array_unique(array_merge($paths, $viewPaths, array_keys($corePaths))); + if (!empty($this->theme)) { + $themePaths = array(); + $count = count($paths); + for ($i = 0; $i < $count; $i++) { + if (strpos($paths[$i], DS . 'Plugin' . DS) === false + && strpos($paths[$i], DS . 'Cake' . DS . 'View') === false) { + if ($plugin) { + $themePaths[] = $paths[$i] . 'Themed'. DS . $this->theme . DS . 'Plugin' . DS . $plugin . DS; + } + $themePaths[] = $paths[$i] . 'Themed'. DS . $this->theme . DS; + } + } + $paths = array_merge($themePaths, $paths); + } if ($plugin !== null) { return $paths; }