Merge branch '3.1'

This commit is contained in:
Andrew Short 2013-07-10 18:27:19 +10:00
commit 8a62593754
4 changed files with 174 additions and 46 deletions

View File

@ -973,7 +973,7 @@ class LeftAndMain extends Controller implements PermissionProvider {
if(substr($SQL_id,0,3) != 'new') { if(substr($SQL_id,0,3) != 'new') {
$record = DataObject::get_by_id($className, $SQL_id); $record = DataObject::get_by_id($className, $SQL_id);
if($record && !$record->canEdit()) return Security::permissionFailure($this); if($record && !$record->canEdit()) return Security::permissionFailure($this);
if(!$record || !$record->ID) throw new HTTPResponse_Exception("Bad record ID #" . (int)$data['ID'], 404); if(!$record || !$record->ID) $this->httpError(404, "Bad record ID #" . (int)$data['ID']);
} else { } else {
if(!singleton($this->stat('tree_class'))->canCreate()) return Security::permissionFailure($this); if(!singleton($this->stat('tree_class'))->canCreate()) return Security::permissionFailure($this);
$record = $this->getNewItem($SQL_id, false); $record = $this->getNewItem($SQL_id, false);
@ -994,7 +994,7 @@ class LeftAndMain extends Controller implements PermissionProvider {
$record = DataObject::get_by_id($className, Convert::raw2sql($data['ID'])); $record = DataObject::get_by_id($className, Convert::raw2sql($data['ID']));
if($record && !$record->canDelete()) return Security::permissionFailure(); if($record && !$record->canDelete()) return Security::permissionFailure();
if(!$record || !$record->ID) throw new HTTPResponse_Exception("Bad record ID #" . (int)$data['ID'], 404); if(!$record || !$record->ID) $this->httpError(404, "Bad record ID #" . (int)$data['ID']);
$record->delete(); $record->delete();

View File

@ -23,7 +23,7 @@ class SS_ConfigManifest {
* the current environment. * the current environment.
* @var array * @var array
*/ */
protected $variantKeySpec = array(); protected $variantKeySpec = false;
/** /**
* All the _config.php files. Need to be included every request & can't be cached. Not variant specific. * All the _config.php files. Need to be included every request & can't be cached. Not variant specific.
@ -88,10 +88,7 @@ class SS_ConfigManifest {
$this->includeTests = $includeTests; $this->includeTests = $includeTests;
// Get the Zend Cache to load/store cache into // Get the Zend Cache to load/store cache into
$this->cache = SS_Cache::factory('SS_Configuration', 'Core', array( $this->cache = $this->getCache();
'automatic_serialization' => true,
'lifetime' => null
));
// Unless we're forcing regen, try loading from cache // Unless we're forcing regen, try loading from cache
if (!$forceRegen) { if (!$forceRegen) {
@ -102,7 +99,7 @@ class SS_ConfigManifest {
} }
// If we don't have a variantKeySpec (because we're forcing regen, or it just wasn't in the cache), generate it // If we don't have a variantKeySpec (because we're forcing regen, or it just wasn't in the cache), generate it
if (!$this->variantKeySpec) { if (false === $this->variantKeySpec) {
$this->regenerate($includeTests); $this->regenerate($includeTests);
} }
@ -110,6 +107,18 @@ class SS_ConfigManifest {
$this->buildYamlConfigVariant(); $this->buildYamlConfigVariant();
} }
/**
* Provides a hook for mock unit tests despite no DI
* @return Zend_Cache_Frontend
*/
protected function getCache()
{
return SS_Cache::factory('SS_Configuration', 'Core', array(
'automatic_serialization' => true,
'lifetime' => null
));
}
/** /**
* Register a callback to be called whenever the calculated merged config changes * Register a callback to be called whenever the calculated merged config changes
* *

View File

@ -6,8 +6,8 @@ The CMS interface works just like any other part of your website: It consists of
PHP controllers, templates, CSS stylesheets and JavaScript. Because it uses the PHP controllers, templates, CSS stylesheets and JavaScript. Because it uses the
same base elements, it is relatively easy to extend. same base elements, it is relatively easy to extend.
As an example, we're going to add a permanent "bookmarks" bar to popular pages As an example, we're going to add a permanent "bookmarks" link list to popular pages
at the bottom of the CMS. A page can be bookmarked by a CMS author through a into the main CMS menu. A page can be bookmarked by a CMS author through a
simple checkbox. simple checkbox.
For a deeper introduction to the inner workings of the CMS, please refer to our For a deeper introduction to the inner workings of the CMS, please refer to our
@ -24,48 +24,35 @@ the common `Page` object (a new PHP class `MyPage` will look for a `MyPage.ss` t
We can use this to create a different base template with `LeftAndMain.ss` We can use this to create a different base template with `LeftAndMain.ss`
(which corresponds to the `LeftAndMain` PHP controller class). (which corresponds to the `LeftAndMain` PHP controller class).
Copy the template markup of the base implementation at `framework/admin/templates/LeftAndMain.ss` Copy the template markup of the base implementation at `framework/admin/templates/Includes/LeftAndMain_Menu.ss`
into `mysite/templates/LeftAndMain.ss`. It will automatically be picked up by into `mysite/templates/Includes/LeftAndMain_Menu.ss`. It will automatically be picked up by
the CMS logic. Add a new section after the `$Content` tag: the CMS logic. Add a new section into the `<ul class="cms-menu-list">`
:::ss :::ss
... ...
<div class="cms-container" data-layout-type="border"> <ul class="cms-menu-list">
$Menu <!-- ... -->
$Content <li class="bookmarked-link first">
<div class="cms-bottom-bar south"> <a href="admin/pages/edit/show/1">Edit "My popular page"</a>
<ul> </li>
<li><a href="admin/page/edit/show/1">Edit "My popular page"</a></li> <li class="bookmarked-link last">
<li><a href="admin/page/edit/show/99">Edit "My other page"</a></li> <a href="admin/pages/edit/show/99">Edit "My other page"</a>
</li>
</ul> </ul>
</div>
</div>
... ...
Refresh the CMS interface with `admin/?flush=all`, and you should see the new Refresh the CMS interface with `admin/?flush=all`, and you should see those
bottom bar with some hardcoded links. We'll make these dynamic further down. hardcoded links underneath the left-hand menu. We'll make these dynamic further down.
You might have noticed that we didn't write any JavaScript to add our layout
manager. The important piece of information is the `south` class in our new
`<div>` structure, plus the height value in our CSS. It instructs the existing
parent layout how to render the element. This layout manager
([jLayout](http://www.bramstein.com/projects/jlayout/)) allows us to build
complex layouts with minimal JavaScript configuration.
See [layout reference](../reference/layout) for more specific information on
CMS layouting.
## Include custom CSS in the CMS ## Include custom CSS in the CMS
In order to show the links in one line, we'll add some CSS, and get it to load In order to show the links a bit separated from the other menu entries,
we'll add some CSS, and get it to load
with the CMS interface. Paste the following content into a new file called with the CMS interface. Paste the following content into a new file called
`mysite/css/BookmarkedPages.css`: `mysite/css/BookmarkedPages.css`:
:::css :::css
.cms-bottom-bar {height: 20px; padding: 5px; background: #C6D7DF;} .bookmarked-link.first {margin-top: 1em;}
.cms-bottom-bar ul {list-style: none; margin: 0; padding: 0;}
.cms-bottom-bar ul li {float: left; margin-left: 1em;}
.cms-bottom-bar a {color: #444444;}
Load the new CSS file into the CMS, by setting the `LeftAndMain.extra_requirements_css` Load the new CSS file into the CMS, by setting the `LeftAndMain.extra_requirements_css`
[configuration value](/topics/configuration). [configuration value](/topics/configuration).
@ -139,9 +126,12 @@ Find the `<ul>` you created earlier in `mysite/admin/templates/LeftAndMain.ss`
and replace it with the following: and replace it with the following:
:::ss :::ss
<ul> <ul class="cms-menu-list">
<!-- ... -->
<% loop $BookmarkedPages %> <% loop $BookmarkedPages %>
<li class="bookmarked-link $FirstLast">
<li><a href="admin/pages/edit/show/$ID">Edit "$Title"</a></li> <li><a href="admin/pages/edit/show/$ID">Edit "$Title"</a></li>
</li>
<% end_loop %> <% end_loop %>
</ul> </ul>

View File

@ -8,6 +8,11 @@ class ConfigManifestTest_ConfigManifestAccess extends SS_ConfigManifest {
class ConfigManifestTest extends SapphireTest { class ConfigManifestTest extends SapphireTest {
/**
* This is a helper method for getting a new manifest
* @param $name
* @return any
*/
protected function getConfigFixtureValue($name) { protected function getConfigFixtureValue($name) {
$manifest = new SS_ConfigManifest(dirname(__FILE__).'/fixtures/configmanifest', true, true); $manifest = new SS_ConfigManifest(dirname(__FILE__).'/fixtures/configmanifest', true, true);
return $manifest->get('ConfigManifestTest', $name); return $manifest->get('ConfigManifestTest', $name);
@ -20,16 +25,140 @@ class ConfigManifestTest extends SapphireTest {
return sprintf('Reference path "%s" failed to parse correctly', $path); return sprintf('Reference path "%s" failed to parse correctly', $path);
} }
/**
* A helper method to return a mock of the cache in order to test expectations and reduce dependency
* @return Zend_Cache_Core
*/
protected function getCacheMock() {
return $this->getMock(
'Zend_Cache_Core',
array('load', 'save'),
array(),
'',
false
);
}
/**
* A helper method to return a mock of the manifest in order to test expectations and reduce dependency
* @param $methods
* @return SS_ConfigManifest
*/
protected function getManifestMock($methods) {
return $this->getMock(
'SS_ConfigManifest',
$methods,
array(), // no constructor arguments
'', // default
false // don't call the constructor
);
}
/**
* Test the caching functionality when we are forcing regeneration
*
* 1. Test that regenerate is called in the default case and that cache->load isn't
* 2. Test that save is called correctly after the regeneration
*/
public function testCachingForceRegeneration() {
// Test that regenerate is called correctly.
$manifest = $this->getManifestMock(array('getCache', 'regenerate', 'buildYamlConfigVariant'));
$manifest->expects($this->once()) // regenerate should be called once
->method('regenerate')
->with($this->equalTo(true)); // includeTests = true
// Set up a cache where we expect load to never be called
$cache = $this->getCacheMock();
$cache->expects($this->never())
->method('load');
$manifest->expects($this->any())
->method('getCache')
->will($this->returnValue($cache));
$manifest->__construct(dirname(__FILE__).'/fixtures/configmanifest', true, true);
// Test that save is called correctly
$manifest = $this->getManifestMock(array('getCache'));
$cache = $this->getCacheMock();
$cache->expects($this->atLeastOnce())
->method('save');
$manifest->expects($this->any())
->method('getCache')
->will($this->returnValue($cache));
$manifest->__construct(dirname(__FILE__).'/fixtures/configmanifest', true, true);
}
/**
* Test the caching functionality when we are not forcing regeneration
*
* 1. Test that load is called
* 2. Test the regenerate is called when the cache is unprimed
* 3. Test that when there is a value in the cache regenerate isn't called
*/
public function testCachingNotForceRegeneration() {
// Test that load is called
$manifest = $this->getManifestMock(array('getCache', 'regenerate', 'buildYamlConfigVariant'));
// Load should be called twice
$cache = $this->getCacheMock();
$cache->expects($this->exactly(2))
->method('load');
$manifest->expects($this->any())
->method('getCache')
->will($this->returnValue($cache));
$manifest->__construct(dirname(__FILE__).'/fixtures/configmanifest', true, false);
// Now test that regenerate is called because the cache is unprimed
$manifest = $this->getManifestMock(array('getCache', 'regenerate', 'buildYamlConfigVariant'));
$cache = $this->getCacheMock();
$cache->expects($this->exactly(2))
->method('load')
->will($this->onConsecutiveCalls(false, false));
$manifest->expects($this->any())
->method('getCache')
->will($this->returnValue($cache));
$manifest->expects($this->once())
->method('regenerate')
->with($this->equalTo(false)); //includeTests = false
$manifest->__construct(dirname(__FILE__).'/fixtures/configmanifest', false, false);
// Now test that when there is a value in the cache that regenerate isn't called
$manifest = $this->getManifestMock(array('getCache', 'regenerate', 'buildYamlConfigVariant'));
$cache = $this->getCacheMock();
$cache->expects($this->exactly(2))
->method('load')
->will($this->onConsecutiveCalls(array(), array()));
$manifest->expects($this->any())
->method('getCache')
->will($this->returnValue($cache));
$manifest->expects($this->never())
->method('regenerate');
$manifest->__construct(dirname(__FILE__).'/fixtures/configmanifest', false, false);
}
/** /**
* This test checks the processing of before and after reference paths (module-name/filename#fragment) * This test checks the processing of before and after reference paths (module-name/filename#fragment)
* This method uses fixture/configmanifest/mysite/_config/addyamlconfigfile.yml as a fixture * This method uses fixture/configmanifest/mysite/_config/addyamlconfigfile.yml as a fixture
*/ */
public function testAddYAMLConfigFileReferencePathParsing() { public function testAddYAMLConfigFileReferencePathParsing() {
// Use a mock to avoid testing unrelated functionality // Use a mock to avoid testing unrelated functionality
$manifest = $this->getMockBuilder('SS_ConfigManifest') $manifest = $this->getManifestMock(array('addModule'));
->disableOriginalConstructor()
->setMethods(array('addModule'))
->getMock();
// This tests that the addModule method is called with the correct value // This tests that the addModule method is called with the correct value
$manifest->expects($this->once()) $manifest->expects($this->once())