diff --git a/core/ManifestBuilder.php b/core/ManifestBuilder.php index edf5c26c0..5538f2ffe 100644 --- a/core/ManifestBuilder.php +++ b/core/ManifestBuilder.php @@ -153,7 +153,7 @@ class ManifestBuilder { * * @param string $baseDir Optional: Absolute path to theme directory for testing e.g. "/Users/sharvey/Sites/test24/themes" * @param boolean $includeSubThemes If set to TRUE, sub-themes such as "blackcandy_blog" are included too - * @return array indexed array of theme directories + * @return array Listing of theme directories */ public static function get_themes($baseDir = null, $includeSubThemes = false) { // If no base directory specified, the default is the project root @@ -168,7 +168,7 @@ class ManifestBuilder { if(strpos($file, '_') === false) { $include = true; } - if($include) $themes[] = $file; + if($include) $themes[$file] = $file; } } closedir($handle); diff --git a/core/SSViewer.php b/core/SSViewer.php index 29efbd35f..cd2c45afc 100755 --- a/core/SSViewer.php +++ b/core/SSViewer.php @@ -87,6 +87,11 @@ class SSViewer { */ protected static $current_theme = null; + /** + * @var string + */ + protected static $cached_theme = null; + /** * Create a template from a string instead of a .ss file * @@ -106,8 +111,18 @@ class SSViewer { /** * @return string */ - static function current_theme() { - return self::$current_theme; + static function current_theme($cached = true) { + if($cached && self::$cached_theme) { + // First case is to return the cached user theme so we don't requery SiteConfig again + return self::$cached_theme; + } else { + // Need to refresh the cache from the SiteConfig + $theme = SiteConfig::current_site_config()->Theme; + self::$cached_theme = $theme; + } + // Fall back to the default SSViewer::set_theme() behaviour + if(!$theme) $theme = self::$current_theme; + return $theme; } /** @@ -141,13 +156,13 @@ class SSViewer { else $templateFolder = null; // Use the theme template if available - if(self::$current_theme && isset($_TEMPLATE_MANIFEST[$template]['themes'][self::$current_theme])) { + if(self::current_theme() && isset($_TEMPLATE_MANIFEST[$template]['themes'][self::current_theme()])) { $this->chosenTemplates = array_merge( - $_TEMPLATE_MANIFEST[$template]['themes'][self::$current_theme], + $_TEMPLATE_MANIFEST[$template]['themes'][self::current_theme()], $this->chosenTemplates ); - if(isset($_GET['debug_request'])) Debug::message("Found template '$template' from main theme '" . self::$current_theme . "': " . var_export($_TEMPLATE_MANIFEST[$template]['themes'][self::$current_theme], true)); + if(isset($_GET['debug_request'])) Debug::message("Found template '$template' from main theme '" . self::current_theme() . "': " . var_export($_TEMPLATE_MANIFEST[$template]['themes'][self::current_theme()], true)); } // Fall back to unthemed base templates @@ -257,8 +272,8 @@ class SSViewer { */ public static function getTemplateFileByType($identifier, $type) { global $_TEMPLATE_MANIFEST; - if(self::$current_theme && isset($_TEMPLATE_MANIFEST[$identifier]['themes'][self::$current_theme][$type])) { - return $_TEMPLATE_MANIFEST[$identifier]['themes'][self::$current_theme][$type]; + if(self::current_theme() && isset($_TEMPLATE_MANIFEST[$identifier]['themes'][self::current_theme()][$type])) { + return $_TEMPLATE_MANIFEST[$identifier]['themes'][self::current_theme()][$type]; } else if(isset($_TEMPLATE_MANIFEST[$identifier][$type])){ return $_TEMPLATE_MANIFEST[$identifier][$type]; } else { diff --git a/core/model/SiteConfig.php b/core/model/SiteConfig.php index 3b5fd5d53..a63fbe2cd 100644 --- a/core/model/SiteConfig.php +++ b/core/model/SiteConfig.php @@ -17,6 +17,7 @@ class SiteConfig extends DataObject { static $db = array( "Title" => "Varchar(255)", "Tagline" => "Varchar(255)", + "Theme" => "Varchar(255)", "CanViewType" => "Enum('Anyone, LoggedInUsers, OnlyTheseUsers', 'Anyone')", "CanEditType" => "Enum('LoggedInUsers, OnlyTheseUsers', 'LoggedInUsers')", "CanCreateTopLevelType" => "Enum('LoggedInUsers, OnlyTheseUsers', 'LoggedInUsers')", @@ -28,6 +29,12 @@ class SiteConfig extends DataObject { "CreateTopLevelGroups" => "Group" ); + protected static $disabled_themes = array(); + + public static function disable_theme($theme) { + self::$disabled_themes[$theme] = $theme; + } + /** * Get the fields that are sent to the CMS. In * your decorators: updateEditFormFields(&$fields) @@ -39,7 +46,8 @@ class SiteConfig extends DataObject { new TabSet("Root", new Tab('Main', $titleField = new TextField("Title", _t('SiteConfig.SITETITLE', "Site title")), - $taglineField = new TextField("Tagline", _t('SiteConfig.SITETAGLINE', "Site Tagline/Slogan")) + $taglineField = new TextField("Tagline", _t('SiteConfig.SITETAGLINE', "Site Tagline/Slogan")), + new DropdownField("Theme", _t('SiteConfig.THEME', 'Theme'), $this->getAvailableThemes(), '', null, _t('SiteConfig.DEFAULTTHEME', '(Use default theme)')) ), new Tab('Access', new HeaderField('WhoCanViewHeader', _t('SiteConfig.VIEWHEADER', "Who can view pages on this site?"), 2), @@ -89,6 +97,19 @@ class SiteConfig extends DataObject { $this->extend('updateEditFormFields', $fields); return $fields; } + + /** + * Get all available themes that haven't been marked as disabled. + * @param string $baseDir Optional alternative theme base directory for testing + * @return array of theme directory names + */ + public function getAvailableThemes($baseDir = null) { + $themes = ManifestBuilder::get_themes($baseDir); + foreach(self::$disabled_themes as $theme) { + if(isset($themes[$theme])) unset($themes[$theme]); + } + return $themes; + } /** * Get the actions that are sent to the CMS. In diff --git a/core/model/SiteTree.php b/core/model/SiteTree.php index a753f9a34..734ad8b81 100755 --- a/core/model/SiteTree.php +++ b/core/model/SiteTree.php @@ -395,6 +395,25 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid return Controller::join_links($base, '/', $action); } + /** + * Get the absolute URL for this page on the Live site. + */ + public function getAbsoluteLiveLink($includeStageEqualsLive = true) { + $live = Versioned::get_one_by_stage('SiteTree', 'Live', '"SiteTree"."ID" = ' . $this->ID); + + if($live) { + $link = $live->AbsoluteLink(); + + if($includeStageEqualsLive) { + $link .= '?stage=Live'; + } + + return $link; + + } + } + + /** * Return a CSS identifier generated from this page's link. * diff --git a/css/TableListField.css b/css/TableListField.css index c5c9f9ff7..61e3968cb 100644 --- a/css/TableListField.css +++ b/css/TableListField.css @@ -37,15 +37,17 @@ table.CMSList thead th { background-repeat: repeat-x; background-position: left bottom; background-color: #ebeadb; - height: 24px; border-right: 1px solid #aca899; border-left: 1px solid #ffffff; + white-space: nowrap; + padding: 3px; + font-size: 12px; + text-align: left; } table.TableField thead th span, .TableListField table.data thead th span { - display: block; - float: left; + font-size: 12px; } table.TableField thead th a, @@ -57,8 +59,6 @@ table.CMSList thead th a { table.TableField thead th span.sortLink, .TableListField table.data thead th span.sortLink, table.CMSList thead th span.sortLink { - width: 16px; - height: 16px; overflow: hidden; } diff --git a/forms/TreeMultiselectField.php b/forms/TreeMultiselectField.php index de2ceb7b1..d5caa87d4 100755 --- a/forms/TreeMultiselectField.php +++ b/forms/TreeMultiselectField.php @@ -123,6 +123,7 @@ HTML; $field = new TreeMultiselectField_Readonly($this->name, $this->title, $this->sourceObject, $this->keyField, $this->labelField); $field->addExtraClass($this->extraClass()); $field->setForm($this->form); + $field->setValue($this->value); return $field; } } diff --git a/javascript/TableListField.js b/javascript/TableListField.js index c857458d5..0259673af 100755 --- a/javascript/TableListField.js +++ b/javascript/TableListField.js @@ -17,15 +17,15 @@ TableListField.prototype = { rules['#'+this.id+' th'] = { initialize: function() { var sortLinks = $$('span.sortLinkHidden a', this); - if(sortLinks) Element.hide(sortLinks[0]); + if(sortLinks) sortLinks[0].style.visibility = 'hidden'; }, onmouseover: function(e) { var sortLinks = $$('span.sortLinkHidden a', this); - if(sortLinks) Element.show(sortLinks[0]); + if(sortLinks) sortLinks[0].style.visibility = 'visible'; }, onmouseout: function(e) { var sortLinks = $$('span.sortLinkHidden a', this); - if(sortLinks) Element.hide(sortLinks[0]); + if(sortLinks) sortLinks[0].style.visibility = 'hidden'; } }; diff --git a/templates/TableListField.ss b/templates/TableListField.ss index 7c7d38b59..84c538f92 100755 --- a/templates/TableListField.ss +++ b/templates/TableListField.ss @@ -18,17 +18,16 @@ $Title - - <% if SortDirection = desc %> - <% _t('SORTDESC', 'Sort in descending order') %> - <% else %> - <% _t('SORTASC', 'Sort in ascending order') %> - <% end_if %> + <% if SortDirection = desc %> + <% _t('SORTDESC', 'Sort in descending order') %> + <% else %> + <% _t('SORTASC', 'Sort in ascending order') %> + <% end_if %>   <% else %> - $Title + $Title <% end_if %> <% end_control %> diff --git a/tests/ManifestBuilderTest.php b/tests/ManifestBuilderTest.php index d906d61d5..839446cc9 100644 --- a/tests/ManifestBuilderTest.php +++ b/tests/ManifestBuilderTest.php @@ -140,15 +140,15 @@ class ManifestBuilderTest extends SapphireTest { mkdir($testThemeBaseDir . $ds . 'darkshades_blog'); $this->assertEquals(array( - 'blackcandy', - 'darkshades' + 'blackcandy' => 'blackcandy', + 'darkshades' => 'darkshades' ), ManifestBuilder::get_themes($testThemeBaseDir), 'Our test theme directory contains 2 themes'); $this->assertEquals(array( - 'blackcandy', - 'blackcandy_blog', - 'darkshades', - 'darkshades_blog' + 'blackcandy' => 'blackcandy', + 'blackcandy_blog' => 'blackcandy_blog', + 'darkshades' => 'darkshades', + 'darkshades_blog' => 'darkshades_blog' ), ManifestBuilder::get_themes($testThemeBaseDir, true), 'Our test theme directory contains 2 themes and 2 sub-themes'); // Remove all the test themes we created diff --git a/tests/SSViewerTest.php b/tests/SSViewerTest.php index 69041f48e..7adc8ff28 100644 --- a/tests/SSViewerTest.php +++ b/tests/SSViewerTest.php @@ -1,6 +1,30 @@ Theme; + $config->Theme = ''; + $config->write(); + + SSViewer::set_theme('mytheme'); + $this->assertEquals('mytheme', SSViewer::current_theme(), 'Current theme is the default - user has not defined one'); + + $config->Theme = 'myusertheme'; + $config->write(); + $this->assertEquals('myusertheme', SSViewer::current_theme(), 'Current theme is a user defined one'); + + // Set the theme back to the original + $config->Theme = $oldTheme; + $config->write(); + } + /** * Test that a template without a tag still renders. */ diff --git a/tests/model/SiteConfigTest.php b/tests/model/SiteConfigTest.php index 9008b7c4d..758dea537 100644 --- a/tests/model/SiteConfigTest.php +++ b/tests/model/SiteConfigTest.php @@ -53,6 +53,27 @@ class SiteConfigTest extends SapphireTest { $this->assertFalse($pageEn->canEdit($translatorDe)); $this->assertTrue($pageEn->canEdit($translatorEn)); } + + function testAvailableThemes() { + $config = SiteConfig::current_site_config(); + $ds = DIRECTORY_SEPARATOR; + $testThemeBaseDir = TEMP_FOLDER . $ds . 'test-themes'; + + if(file_exists($testThemeBaseDir)) Filesystem::removeFolder($testThemeBaseDir); + mkdir($testThemeBaseDir); + mkdir($testThemeBaseDir . $ds . 'blackcandy'); + mkdir($testThemeBaseDir . $ds . 'blackcandy_blog'); + mkdir($testThemeBaseDir . $ds . 'darkshades'); + mkdir($testThemeBaseDir . $ds . 'darkshades_blog'); + + $themes = $config->getAvailableThemes($testThemeBaseDir); + $this->assertContains('blackcandy', $themes, 'Test themes contain blackcandy theme'); + $this->assertContains('darkshades', $themes, 'Test themes contain darkshades theme'); + + SiteConfig::disable_theme('darkshades'); + $themes = $config->getAvailableThemes($testThemeBaseDir); + $this->assertFalse(in_array('darkshades', $themes), 'Darkshades was disabled - it is no longer available'); + } } ?> \ No newline at end of file