Merge remote-tracking branch 'origin/3.1'

Conflicts:
.travis.yml
This commit is contained in:
Damian Mooyman 2014-05-06 10:24:28 +12:00
commit b3699281c0
16 changed files with 592 additions and 234 deletions

View File

@ -26,6 +26,9 @@ matrix:
env: DB=MYSQL CORE_RELEASE=master
- php: 5.4
env: DB=MYSQL CORE_RELEASE=master BEHAT_TEST=1
allow_failures:
- php: 5.6
env: DB=MYSQL CORE_RELEASE=master
before_script:
- composer self-update
@ -38,7 +41,7 @@ before_script:
- php ~/travis-support/travis_setup_php54_webserver.php --if-env BEHAT_TEST
script:
- "if [ \"$BEHAT_TEST\" = \"\" ]; then phpunit cms/tests; fi"
- "if [ \"$BEHAT_TEST\" = \"\" ]; then vendor/bin/phpunit cms/tests; fi"
- "if [ \"$BEHAT_TEST\" = \"1\" ]; then vendor/bin/behat @cms; fi"
after_failure:

View File

@ -16,6 +16,14 @@ class AssetAdmin extends LeftAndMain implements PermissionProvider{
private static $tree_class = 'Folder';
/**
* Amount of results showing on a single page.
*
* @config
* @var int
*/
private static $page_length = 15;
/**
* @config
* @see Upload->allowedMaxFileSize
@ -98,17 +106,19 @@ JS
// Don't filter list when a detail view is requested,
// to avoid edge cases where the filtered list wouldn't contain the requested
// record due to faulty session state (current folder not always encoded in URL, see #7408).
if(!$folder->ID && $this->request->requestVar('ID') === null && ($this->request->param('ID') == 'field')) {
if(!$folder->ID
&& $this->request->requestVar('ID') === null
&& ($this->request->param('ID') == 'field')
) {
return $list;
}
// Re-add previously removed "Name" filter as combined filter
// TODO Replace with composite SearchFilter once that API exists
if(isset($params['Name'])) {
$list = $list->where(sprintf(
'"Name" LIKE \'%%%s%%\' OR "Title" LIKE \'%%%s%%\'',
Convert::raw2sql($params['Name']),
Convert::raw2sql($params['Name'])
if(!empty($params['Name'])) {
$list = $list->filterAny(array(
'Name:PartialMatch' => $params['Name'],
'Title:PartialMatch' => $params['Name']
));
}
@ -117,23 +127,26 @@ JS
// If a search is conducted, check for the "current folder" limitation.
// Otherwise limit by the current folder as denoted by the URL.
if(!$params || @$params['CurrentFolderOnly']) {
if(empty($params) || !empty($params['CurrentFolderOnly'])) {
$list = $list->filter('ParentID', $folder->ID);
}
// Category filter
if(isset($params['AppCategory'])) {
if(isset(File::config()->app_categories[$params['AppCategory']])) {
$exts = File::config()->app_categories[$params['AppCategory']];
} else {
$exts = array();
}
$categorySQLs = array();
foreach($exts as $ext) $categorySQLs[] = '"File"."Name" LIKE \'%.' . $ext . '\'';
// TODO Use DataList->filterAny() once OR connectives are implemented properly
if (count($categorySQLs) > 0) {
$list = $list->where('(' . implode(' OR ', $categorySQLs) . ')');
}
if(!empty($params['AppCategory'])
&& !empty(File::config()->app_categories[$params['AppCategory']])
) {
$exts = File::config()->app_categories[$params['AppCategory']];
$list = $list->filter('Name:PartialMatch', $exts);
}
// Date filter
if(!empty($params['CreatedFrom'])) {
$fromDate = new DateField(null, null, $params['CreatedFrom']);
$list = $list->filter("Created:GreaterThanOrEqual", $fromDate->dataValue());
}
if(!empty($params['CreatedTo'])) {
$toDate = new DateField(null, null, $params['CreatedTo']);
$list = $list->filter("Created:LessThanOrEqual", $toDate->dataValue());
}
return $list;
@ -150,9 +163,9 @@ JS
$gridFieldConfig = GridFieldConfig::create()->addComponents(
new GridFieldToolbarHeader(),
new GridFieldSortableHeader(),
new GridFieldFilterHeader(),
new GridFieldFilterHeader(),
new GridFieldDataColumns(),
new GridFieldPaginator(15),
new GridFieldPaginator(self::config()->page_length),
new GridFieldEditButton(),
new GridFieldDeleteAction(),
new GridFieldDetailForm(),
@ -342,6 +355,21 @@ JS
foreach($context->getFilters() as $filter) $filter->setFullName(sprintf('q[%s]', $filter->getFullName()));
// Customize fields
$context->addField(
new HeaderField('q[Date]', _t('CMSSearch.FILTERDATEHEADING', 'Date'), 4)
);
$context->addField(
DateField::create(
'q[CreatedFrom]',
_t('CMSSearch.FILTERDATEFROM', 'From')
)->setConfig('showcalendar', true)
);
$context->addField(
DateField::create(
'q[CreatedTo]',
_t('CMSSearch.FILTERDATETO', 'To')
)->setConfig('showcalendar', true)
);
$appCategories = array(
'image' => _t('AssetAdmin.AppCategoryImage', 'Image'),
'audio' => _t('AssetAdmin.AppCategoryAudio', 'Audio'),
@ -382,7 +410,7 @@ JS
$fields = $context->getSearchFields();
$actions = new FieldList(
FormAction::create('doSearch', _t('CMSMain_left_ss.APPLY_FILTER', 'Apply Filter'))
->addExtraClass('ss-ui-action-constructive'),
->addExtraClass('ss-ui-action-constructive'),
Object::create('ResetFormAction', 'clear', _t('CMSMain_left_ss.RESET', 'Reset'))
);

View File

@ -27,6 +27,14 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
private static $subitem_class = "Member";
/**
* Amount of results showing on a single page.
*
* @config
* @var int
*/
private static $page_length = 15;
private static $allowed_actions = array(
'buildbrokenlinks',
'deleteitems',
@ -726,7 +734,7 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr
$gridFieldConfig = GridFieldConfig::create()->addComponents(
new GridFieldSortableHeader(),
new GridFieldDataColumns(),
new GridFieldPaginator(15)
new GridFieldPaginator(self::config()->page_length)
);
if($parentID){
$gridFieldConfig->addComponent(

View File

@ -4,10 +4,10 @@
*
* The simplest way of building a CMSSiteTreeFilter is to create a pagesToBeShown() method that
* returns an Iterator of maps, each entry containing the 'ID' and 'ParentID' of the pages to be
* included in the tree. The reuslt of a DB::query() can be returned directly.
* included in the tree. The result of a DB::query() can then be returned directly.
*
* If you wish to make a more complex tree, you can overload includeInTree($page) to return true/
* false depending on whether the given page should be included. Note that you will need to include
* false depending on whether the given page should be included. Note that you will need to include
* parent helper pages yourself.
*
* @package cms
@ -44,18 +44,22 @@ abstract class CMSSiteTreeFilter extends Object {
public static function get_all_filters() {
// get all filter instances
$filters = ClassInfo::subclassesFor('CMSSiteTreeFilter');
// remove abstract CMSSiteTreeFilter class
array_shift($filters);
// add filters to map
$filterMap = array();
foreach($filters as $filter) {
$filterMap[$filter] = call_user_func(array($filter, 'title'));
$filterMap[$filter] = $filter::title();
}
// ensure that 'all pages' filter is on top position
uasort($filterMap,
create_function('$a,$b', 'return ($a == "CMSSiteTreeFilter_Search") ? 1 : -1;')
);
// Ensure that 'all pages' filter is on top position and everything else is sorted alphabetically
uasort($filterMap, function($a, $b) {
return ($a === CMSSiteTreeFilter_Search::title())
? -1
: strcasecmp($a, $b);
});
return $filterMap;
}
@ -125,7 +129,68 @@ abstract class CMSSiteTreeFilter extends Object {
return (isset($this->_cache_ids[$page->ID]) && $this->_cache_ids[$page->ID]);
}
/**
* Applies the default filters to a specified DataList of pages
*
* @param DataList $query Unfiltered query
* @return DataList Filtered query
*/
protected function applyDefaultFilters($query) {
$sng = singleton('SiteTree');
foreach($this->params as $name => $val) {
if(empty($val)) continue;
switch($name) {
case 'Term':
$query = $query->filterAny(array(
'URLSegment:PartialMatch' => $val,
'Title:PartialMatch' => $val,
'MenuTitle:PartialMatch' => $val,
'Content:PartialMatch' => $val
));
break;
case 'LastEditedFrom':
$fromDate = new DateField(null, null, $val);
$query = $query->filter("LastEdited:GreaterThanOrEqual", $fromDate->dataValue());
break;
case 'LastEditedTo':
$toDate = new DateField(null, null, $val);
$query = $query->filter("LastEdited:LessThanOrEqual", $toDate->dataValue());
break;
case 'ClassName':
if($val != 'All') {
$query = $query->filter('ClassName', $val);
}
break;
default:
if($sng->hasDatabaseField($name)) {
$filter = $sng->dbObject($name)->defaultSearchFilter();
$filter->setValue($val);
$query = $query->alterDataQuery(array($filter, 'apply'));
}
}
}
return $query;
}
/**
* Maps a list of pages to an array of associative arrays with ID and ParentID keys
*
* @param DataList $pages
* @return array
*/
protected function mapIDs($pages) {
$ids = array();
if($pages) foreach($pages as $page) {
$ids[] = array('ID' => $page->ID, 'ParentID' => $page->ParentID);
}
return $ids;
}
}
/**
@ -145,13 +210,9 @@ class CMSSiteTreeFilter_DeletedPages extends CMSSiteTreeFilter {
}
public function pagesIncluded() {
$ids = array();
// TODO Not very memory efficient, but usually not very many deleted pages exist
$pages = Versioned::get_including_deleted('SiteTree');
if($pages) foreach($pages as $page) {
$ids[] = array('ID' => $page->ID, 'ParentID' => $page->ParentID);
}
return $ids;
$pages = $this->applyDefaultFilters($pages);
return $this->mapIDs($pages);
}
}
@ -168,18 +229,100 @@ class CMSSiteTreeFilter_ChangedPages extends CMSSiteTreeFilter {
}
public function pagesIncluded() {
$ids = array();
$q = new SQLQuery();
$q->setSelect(array('"SiteTree"."ID"','"SiteTree"."ParentID"'))
->setFrom('"SiteTree"')
->addLeftJoin('SiteTree_Live', '"SiteTree_Live"."ID" = "SiteTree"."ID"')
->setWhere('"SiteTree"."Version" > "SiteTree_Live"."Version"');
$pages = Versioned::get_by_stage('SiteTree', 'Stage');
$pages = $this->applyDefaultFilters($pages)
->leftJoin('SiteTree_Live', '"SiteTree_Live"."ID" = "SiteTree"."ID"')
->where('"SiteTree"."Version" > "SiteTree_Live"."Version"');
return $this->mapIDs($pages);
}
}
foreach($q->execute() as $row) {
$ids[] = array('ID'=>$row['ID'],'ParentID'=>$row['ParentID']);
}
/**
* Filters pages which have a status "Removed from Draft".
*
* @package cms
* @subpackage content
*/
class CMSSiteTreeFilter_StatusRemovedFromDraftPages extends CMSSiteTreeFilter {
static public function title() {
return _t('CMSSiteTreeFilter_StatusRemovedFromDraftPages.Title', 'Live but removed from draft');
}
/**
* Filters out all pages who's status is set to "Removed from draft".
*
* @see {@link SiteTree::getStatusFlags()}
* @return array
*/
public function pagesIncluded() {
$pages = Versioned::get_including_deleted('SiteTree');
$pages = $this->applyDefaultFilters($pages);
$pages = $pages->filterByCallback(function($page) {
// If page is removed from stage but not live
return $page->IsDeletedFromStage && $page->ExistsOnLive;
});
return $this->mapIDs($pages);
}
}
return $ids;
/**
* Filters pages which have a status "Draft".
*
* @package cms
* @subpackage content
*/
class CMSSiteTreeFilter_StatusDraftPages extends CMSSiteTreeFilter {
static public function title() {
return _t('CMSSiteTreeFilter_StatusDraftPages.Title', 'Draft unpublished pages');
}
/**
* Filters out all pages who's status is set to "Draft".
*
* @see {@link SiteTree::getStatusFlags()}
* @return array
*/
public function pagesIncluded() {
$pages = Versioned::get_by_stage('SiteTree', 'Stage');
$pages = $this->applyDefaultFilters($pages);
$pages = $pages->filterByCallback(function($page) {
// If page exists on stage but not on live
return (!$page->IsDeletedFromStage && $page->IsAddedToStage);
});
return $this->mapIDs($pages);
}
}
/**
* Filters pages which have a status "Deleted".
*
* @package cms
* @subpackage content
*/
class CMSSiteTreeFilter_StatusDeletedPages extends CMSSiteTreeFilter {
protected $childrenMethod = "AllHistoricalChildren";
static public function title() {
return _t('CMSSiteTreeFilter_StatusDeletedPages.Title', 'Deleted pages');
}
/**
* Filters out all pages who's status is set to "Deleted".
*
* @see {@link SiteTree::getStatusFlags()}
* @return array
*/
public function pagesIncluded() {
$pages = Versioned::get_including_deleted('SiteTree');
$pages = $this->applyDefaultFilters($pages);
$pages = $pages->filterByCallback(function($page) {
// Doesn't exist on either stage or live
return $page->IsDeletedFromStage && !$page->ExistsOnLive;
});
return $this->mapIDs($pages);
}
}
@ -200,54 +343,10 @@ class CMSSiteTreeFilter_Search extends CMSSiteTreeFilter {
* @return Array
*/
public function pagesIncluded() {
$sng = singleton('SiteTree');
$ids = array();
$query = new DataQuery('SiteTree');
$query->setQueriedColumns(array('ID', 'ParentID'));
foreach($this->params as $name => $val) {
$SQL_val = Convert::raw2sql($val);
switch($name) {
case 'Term':
$query->whereAny(array(
"\"URLSegment\" LIKE '%$SQL_val%'",
"\"Title\" LIKE '%$SQL_val%'",
"\"MenuTitle\" LIKE '%$SQL_val%'",
"\"Content\" LIKE '%$SQL_val%'"
));
break;
case 'LastEditedFrom':
$fromDate = new DateField(null, null, $SQL_val);
$query->where("\"LastEdited\" >= '{$fromDate->dataValue()}'");
break;
case 'LastEditedTo':
$toDate = new DateField(null, null, $SQL_val);
$query->where("\"LastEdited\" <= '{$toDate->dataValue()}'");
break;
case 'ClassName':
if($val && $val != 'All') {
$query->where("\"ClassName\" = '$SQL_val'");
}
break;
default:
if(!empty($val) && $sng->hasDatabaseField($name)) {
$filter = $sng->dbObject($name)->defaultSearchFilter();
$filter->setValue($val);
$filter->apply($query);
}
}
}
foreach($query->execute() as $row) {
$ids[] = array('ID' => $row['ID'], 'ParentID' => $row['ParentID']);
}
return $ids;
// Filter default records
$pages = Versioned::get_by_stage('SiteTree', 'Stage');
$pages = $this->applyDefaultFilters($pages);
return $this->mapIDs($pages);
}
}

View File

@ -1380,7 +1380,7 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
public function MetaTags($includeTitle = true) {
$tags = "";
if($includeTitle === true || $includeTitle == 'true') {
$tags .= "<title>" . $this->Title . "</title>\n";
$tags .= "<title>" . Convert::raw2xml($this->Title) . "</title>\n";
}
$generator = trim(Config::inst()->get('SiteTree', 'meta_generator'));

View File

@ -38,24 +38,22 @@ class SiteTreeLinkTracking extends DataExtension {
$href = Director::makeRelative($link->getAttribute('href'));
if($href) {
if(preg_match('/\[sitetree_link,id=([0-9]+)\]/i', $href, $matches)) {
$ID = $matches[1];
if(preg_match('/\[(sitetree|file)_link[,\s]id=([0-9]+)\]/i', $href, $matches)) {
$type = $matches[1];
$id = $matches[2];
// clear out any broken link classes
if($class = $link->getAttribute('class')) {
$link->setAttribute('class',
preg_replace('/(^ss-broken|ss-broken$| ss-broken )/', null, $class));
}
$linkedPages[] = $ID;
if(!DataObject::get_by_id('SiteTree', $ID)) $record->HasBrokenLink = true;
} else if(substr($href, 0, strlen(ASSETS_DIR) + 1) == ASSETS_DIR.'/') {
$candidateFile = File::find(Convert::raw2sql(urldecode($href)));
if($candidateFile) {
$linkedFiles[] = $candidateFile->ID;
} else {
$record->HasBrokenFile = true;
if($type === 'sitetree') {
if(SiteTree::get()->byID($id)) {
$linkedPages[] = $id;
} else {
$record->HasBrokenLink = true;
}
} else if($type === 'file') {
if(File::get()->byID($id)) {
$linkedFiles[] = $id;
} else {
$record->HasBrokenFile = true;
}
}
} else if($href == '' || $href[0] == '/') {
$record->HasBrokenLink = true;

View File

@ -9,6 +9,6 @@ Feature: Create a page
And I go to "/admin/pages"
And I should see a "Add new" button in CMS Content Toolbar
When I press the "Add new" button
And I check "Page"
And I select the "Page" radio button
And I press the "Create" button
Then I should see an edit page form

View File

@ -15,7 +15,7 @@ So that I can link to a external website or a page on my site
Scenario: I can link to an internal page
Given I select "awesome" in the "Content" HTML field
And I press the "Insert Link" button
When I check "Page on the site"
When I select the "Page on the site" radio button
And I fill in the "Page" dropdown with "Home"
And I fill in "my desc" for "Link description"
And I press the "Insert" button
@ -27,7 +27,7 @@ So that I can link to a external website or a page on my site
Scenario: I can link to an external URL
Given I select "awesome" in the "Content" HTML field
And I press the "Insert Link" button
When I check "Another website"
When I select the "Another website" radio button
And I fill in "http://silverstripe.org" for "URL"
And I check "Open link in a new window"
And I press the "Insert" button
@ -38,7 +38,7 @@ So that I can link to a external website or a page on my site
Scenario: I can link to a file
Given I select "awesome" in the "Content" HTML field
When I press the "Insert Link" button
When I check "Download a file"
When I select the "Download a file" radio button
And I fill in the "File" dropdown with "file1.jpg"
And I press the "Insert link" button
Then the "Content" HTML field should contain "<a href="[file_link,id=1]" target="_blank">awesome</a>"
@ -49,7 +49,7 @@ So that I can link to a external website or a page on my site
Given I fill in the "Content" HTML field with "<p>My awesome content<a name=myanchor></a></p>"
And I select "awesome" in the "Content" HTML field
When I press the "Insert Link" button
When I check "Anchor on this page"
When I select the "Anchor on this page" radio button
# Need to hard-code the id attribute of the <select> here, as there are two form fields to pick from
And I select "myanchor" from "Form_EditorToolbarLinkForm_AnchorSelector"
And I press the "Insert link" button

View File

@ -5,8 +5,8 @@ Feature: Manage files
So that I can insert them into my content efficiently
Background:
Given a "image" "assets/folder1/file1.jpg"
And a "image" "assets/folder1/folder1.1/file2.jpg"
Given a "image" "assets/folder1/file1.jpg" was created "2012-01-01 12:00:00"
And a "image" "assets/folder1/folder1.1/file2.jpg" was created "2010-01-01 12:00:00"
And a "folder" "assets/folder2"
And I am logged in with "ADMIN" permissions
And I go to "/admin/assets"
@ -75,3 +75,11 @@ Feature: Manage files
And I press the "Apply Filter" button
Then the "Files" table should contain "file1"
And the "Files" table should not contain "document"
Scenario: I can filter out files that don't match the date range
Given I expand the "Filter" CMS Panel
And I fill in "From" with "2003-01-01"
And I fill in "To" with "2011-01-01"
And I press the "Apply Filter" button
And the "Files" table should contain "file2"
And the "Files" table should not contain "file1"

View File

@ -40,12 +40,59 @@ Feature: Search for a page
Then I should not see "Recent Page" in the tree
But I should see "Old Page" in the tree
Scenario: I can include deleted pages in my search
Given a "page" "Deleted Page"
And the "page" "Deleted Page" is unpublished
And the "page" "Deleted Page" is deleted
When I press the "Apply Filter" button
Then I should not see "Deleted Page" in the tree
When I select "All pages, including deleted" from "Pages"
And I press the "Apply Filter" button
Then I should see "Deleted Page" in the tree
Then I should see "Deleted Page" in the tree
Scenario: I can include only deleted pages in my search
Given a "page" "Deleted Page"
And the "page" "Deleted Page" is unpublished
And the "page" "Deleted Page" is deleted
When I press the "Apply Filter" button
Then I should not see "Deleted Page" in the tree
When I select "Deleted pages" from "Pages"
And I press the "Apply Filter" button
Then I should see "Deleted Page" in the tree
And I should not see "About Us" in the tree
Scenario: I can include draft pages in my search
Given a "page" "Draft Page"
And the "page" "Draft Page" is not published
When I press the "Apply Filter" button
Then I should see "Draft Page" in the tree
When I select "Draft unpublished pages" from "Pages"
And I press the "Apply Filter" button
Then I should see "Draft Page" in the tree
And I should not see "About Us" in the tree
Scenario: I can include changed pages in my search
When I click on "About Us" in the tree
Then I should see an edit page form
When I fill in the "Content" HTML field with "my new content"
And I press the "Save draft" button
Then I should see "Saved" in the "button#Form_EditForm_action_save" element
When I go to "/admin/pages"
And I expand the "Filter" CMS Panel
When I select "Changed pages" from "Pages"
And I press the "Apply Filter" button
Then I should see "About Us" in the tree
And I should not see "Home" in the tree
Scenario: I can include live pages in my search
Given a "page" "Live Page"
And the "page" "Live Page" is published
And the "page" "Live Page" is deleted
When I press the "Apply Filter" button
Then I should not see "Live Page" in the tree
When I select "Live but removed from draft" from "Pages"
And I press the "Apply Filter" button
Then I should see "Live Page" in the tree
And I should not see "About Us" in the tree

View File

@ -56,7 +56,8 @@ class CMSSiteTreeFilterTest extends SapphireTest {
$changedPage->Title = 'Changed';
$changedPage->write();
$f = new CMSSiteTreeFilter_ChangedPages();
// Check that only changed pages are returned
$f = new CMSSiteTreeFilter_ChangedPages(array('Term' => 'Changed'));
$results = $f->pagesIncluded();
$this->assertTrue($f->isPageIncluded($changedPage));
@ -66,6 +67,11 @@ class CMSSiteTreeFilterTest extends SapphireTest {
array('ID' => $changedPage->ID, 'ParentID' => 0),
$results[0]
);
// Check that only changed pages are returned
$f = new CMSSiteTreeFilter_ChangedPages(array('Term' => 'No Matches'));
$results = $f->pagesIncluded();
$this->assertEquals(0, count($results));
}
public function testDeletedPagesFilter() {
@ -79,9 +85,77 @@ class CMSSiteTreeFilterTest extends SapphireTest {
sprintf('"SiteTree_Live"."ID" = %d', $deletedPageID)
);
$f = new CMSSiteTreeFilter_DeletedPages();
$results = $f->pagesIncluded();
$f = new CMSSiteTreeFilter_DeletedPages(array('Term' => 'Page'));
$this->assertTrue($f->isPageIncluded($deletedPage));
// Check that only changed pages are returned
$f = new CMSSiteTreeFilter_DeletedPages(array('Term' => 'No Matches'));
$this->assertFalse($f->isPageIncluded($deletedPage));
}
public function testStatusDraftPagesFilter() {
$draftPage = $this->objFromFixture('Page', 'page4');
$draftPage->publish('Stage', 'Stage');
$draftPage = Versioned::get_one_by_stage(
'SiteTree',
'Stage',
sprintf('"SiteTree"."ID" = %d', $draftPage->ID)
);
// Check draft page is shown
$f = new CMSSiteTreeFilter_StatusDraftPages(array('Term' => 'Page'));
$this->assertTrue($f->isPageIncluded($draftPage));
// Check filter respects parameters
$f = new CMSSiteTreeFilter_StatusDraftPages(array('Term' => 'No Match'));
$this->assertEmpty($f->isPageIncluded($draftPage));
// Ensures empty array returned if no data to show
$f = new CMSSiteTreeFilter_StatusDraftPages();
$draftPage->delete();
$this->assertEmpty($f->isPageIncluded($draftPage));
}
public function testStatusRemovedFromDraftFilter() {
$removedDraftPage = $this->objFromFixture('Page', 'page6');
$removedDraftPage->doPublish();
$removedDraftPage->deleteFromStage('Stage');
$removedDraftPage = Versioned::get_one_by_stage(
'SiteTree',
'Live',
sprintf('"SiteTree"."ID" = %d', $removedDraftPage->ID)
);
// Check live-only page is included
$f = new CMSSiteTreeFilter_StatusRemovedFromDraftPages(array('LastEditedFrom' => '2000-01-01 00:00'));
$this->assertTrue($f->isPageIncluded($removedDraftPage));
// Check filter is respected
$f = new CMSSiteTreeFilter_StatusRemovedFromDraftPages(array('LastEditedTo' => '1999-01-01 00:00'));
$this->assertEmpty($f->isPageIncluded($removedDraftPage));
// Ensures empty array returned if no data to show
$f = new CMSSiteTreeFilter_StatusRemovedFromDraftPages();
$removedDraftPage->delete();
$this->assertEmpty($f->isPageIncluded($removedDraftPage));
}
public function testStatusDeletedFilter() {
$deletedPage = $this->objFromFixture('Page', 'page7');
$deletedPage->publish('Stage', 'Live');
$deletedPageID = $deletedPage->ID;
// Can't use straight $blah->delete() as that blows it away completely and test fails
$deletedPage->deleteFromStage('Live');
$deletedPage->deleteFromStage('Draft');
$checkParentExists = Versioned::get_latest_version('SiteTree', $deletedPageID);
// Check deleted page is included
$f = new CMSSiteTreeFilter_StatusDeletedPages(array('Title' => 'Page'));
$this->assertTrue($f->isPageIncluded($checkParentExists));
// Check filter is respected
$f = new CMSSiteTreeFilter_StatusDeletedPages(array('Title' => 'Bobby'));
$this->assertFalse($f->isPageIncluded($checkParentExists));
}
}

View File

@ -5,6 +5,18 @@ Page:
Title: Page 2
page3:
Title: Page 3
page4:
Title: Page 4
page5:
Title: Page 5
Content: 'Default text'
page6:
Title: Page 6
page7:
Title: Page 7
page7a:
Parent: =>Page.page7
Title: Page 7a
page2a:
Parent: =>Page.page2
Title: Page 2a

View File

@ -46,18 +46,6 @@ class SiteTreeBrokenLinksTest extends SapphireTest {
$this->assertTrue($rp->HasBrokenLink, 'Broken redirector page IS marked as such');
}
public function testBrokenAssetLinks() {
$obj = $this->objFromFixture('Page','content');
$obj->Content = '<a href="assets/nofilehere.pdf">this is a broken link to a pdf file</a>';
$obj->syncLinkTracking();
$this->assertTrue($obj->HasBrokenFile, 'Page has a broken file');
$obj->Content = '<a href="assets/privacypolicy.pdf">this is not a broken file link</a>';
$obj->syncLinkTracking();
$this->assertFalse($obj->HasBrokenFile, 'Page does NOT have a broken file');
}
public function testDeletingFileMarksBackedPagesAsBroken() {
// Test entry
$file = new File();
@ -65,7 +53,10 @@ class SiteTreeBrokenLinksTest extends SapphireTest {
$file->write();
$obj = $this->objFromFixture('Page','content');
$obj->Content = '<a href="assets/test-file.pdf">link to a pdf file</a>';
$obj->Content = sprintf(
'<p><a href="[file_link,id=%d]">Working Link</a></p>',
$file->ID
);
$obj->write();
$this->assertTrue($obj->doPublish());
// Confirm that it isn't marked as broken to begin with

View File

@ -31,6 +31,16 @@ class SiteTreeHtmlEditorFieldTest extends FunctionalTest {
$editor->saveInto($sitetree);
$sitetree->write();
$this->assertEquals(array(), $sitetree->LinkTracking()->getIdList(), 'Link tracking is removed when links are.');
// Legacy support - old CMS versions added link shortcodes with spaces instead of commas
$editor->setValue("<a href=\"[sitetree_link id=$aboutID]\">Example Link</a>");
$editor->saveInto($sitetree);
$sitetree->write();
$this->assertEquals(
array($aboutID => $aboutID),
$sitetree->LinkTracking()->getIdList(),
'Link tracking with space instead of comma in shortcode works.'
);
}
public function testFileLinkTracking() {
@ -38,7 +48,10 @@ class SiteTreeHtmlEditorFieldTest extends FunctionalTest {
$editor = new HtmlEditorField('Content');
$fileID = $this->idFromFixture('File', 'example_file');
$editor->setValue('<a href="assets/example.pdf">Example File</a>');
$editor->setValue(sprintf(
'<p><a href="[file_link,id=%d]">Example File</a></p>',
$fileID
));
$editor->saveInto($sitetree);
$sitetree->write();
$this->assertEquals (
@ -49,6 +62,19 @@ class SiteTreeHtmlEditorFieldTest extends FunctionalTest {
$editor->saveInto($sitetree);
$sitetree->write();
$this->assertEquals(array(), $sitetree->ImageTracking()->getIdList(), 'Asset tracking is removed with links.');
// Legacy support - old CMS versions added link shortcodes with spaces instead of commas
$editor->setValue(sprintf(
'<p><a href="[file_link id=%d]">Example File</a></p>',
$fileID
));
$editor->saveInto($sitetree);
$sitetree->write();
$this->assertEquals(
array($fileID => $fileID),
$sitetree->ImageTracking()->getIDList(),
'Link tracking with space instead of comma in shortcode works.'
);
}
public function testImageInsertion() {
@ -94,7 +120,7 @@ class SiteTreeHtmlEditorFieldTest extends FunctionalTest {
);
}
public function testBrokenLinkTracking() {
public function testBrokenSiteTreeLinkTracking() {
$sitetree = new SiteTree();
$editor = new HtmlEditorField('Content');
@ -117,14 +143,38 @@ class SiteTreeHtmlEditorFieldTest extends FunctionalTest {
$this->assertFalse((bool) $sitetree->HasBrokenLink);
}
public function testBrokenFileLinkTracking() {
$sitetree = new SiteTree();
$editor = new HtmlEditorField('Content');
$this->assertFalse((bool) $sitetree->HasBrokenFile);
$editor->setValue('<p><a href="[file_link,id=0]">Broken Link</a></p>');
$editor->saveInto($sitetree);
$sitetree->write();
$this->assertTrue($sitetree->HasBrokenFile);
$editor->setValue(sprintf (
'<p><a href="[file_link,id=%d]">Working Link</a></p>',
$this->idFromFixture('File', 'example_file')
));
$sitetree->HasBrokenFile = false;
$editor->saveInto($sitetree);
$sitetree->write();
$this->assertFalse((bool) $sitetree->HasBrokenFile);
}
public function testBrokenLinkHighlighting() {
$sitetree = new SiteTree();
$editor = new HtmlEditorField('Content');
// SiteTree link highlighting
$editor->setValue('<a href="[sitetree_link,id=0]">Broken Link</a>');
$element = new SimpleXMLElement(html_entity_decode((string) new SimpleXMLElement($editor->Field())));
$this->assertContains('ss-broken', (string) $element['class'], 'A broken link class is added to broken links');
$this->assertContains('ss-broken', (string) $element['class'], 'A broken SiteTree link is highlighted');
$editor->setValue(sprintf (
'<a href="[sitetree_link,id=%d]">Working Link</a>',
@ -133,6 +183,20 @@ class SiteTreeHtmlEditorFieldTest extends FunctionalTest {
$element = new SimpleXMLElement(html_entity_decode((string) new SimpleXMLElement($editor->Field())));
$this->assertNotContains('ss-broken', (string) $element['class']);
// File link highlighting
$editor->setValue('<a href="[file_link,id=0]">Broken Link</a>');
$element = new SimpleXMLElement(html_entity_decode((string) new SimpleXMLElement($editor->Field())));
$this->assertContains('ss-broken', (string) $element['class'], 'A broken File link is highlighted');
$editor->setValue(sprintf (
'<a href="[file_link,id=%d]">Working Link</a>',
$this->idFromFixture('File', 'example_file')
));
$element = new SimpleXMLElement(html_entity_decode((string) new SimpleXMLElement($editor->Field())));
$this->assertNotContains('ss-broken', (string) $element['class']);
}
}

View File

@ -944,6 +944,29 @@ class SiteTreeTest extends SapphireTest {
Config::inst()->update('SiteTree', 'meta_generator', $generator);
}
/**
* Tests SiteTree::MetaTags
* Note that this test makes no assumption on the closing of tags (other than <title></title>)
*/
public function testMetaTags() {
$this->logInWithPermission('ADMIN');
$page = $this->objFromFixture('Page', 'metapage');
// Test with title
$meta = $page->MetaTags();
$charset = Config::inst()->get('ContentNegotiator', 'encoding');
$this->assertContains('<meta http-equiv="Content-type" content="text/html; charset='.$charset.'"', $meta);
$this->assertContains('<meta name="description" content="The &lt;br /&gt; and &lt;br&gt; tags"', $meta);
$this->assertContains('<link rel="canonical" href="http://www.mysite.com/html-and-xml"', $meta);
$this->assertContains('<meta name="x-page-id" content="'.$page->ID.'"', $meta);
$this->assertContains('<meta name="x-cms-edit-link" content="'.$page->CMSEditLink().'" />', $meta);
$this->assertContains('<title>HTML &amp; XML</title>', $meta);
// Test without title
$meta = $page->MetaTags(false);
$this->assertNotContains('<title>', $meta);
}
/**
* Test that orphaned pages are handled correctly
*/

View File

@ -1,104 +1,107 @@
Group:
editors:
Title: Editors
admins:
Title: Administrators
allsections:
Title: All Section Editors
securityadmins:
Title: Security Admins
editors:
Title: Editors
admins:
Title: Administrators
allsections:
Title: All Section Editors
securityadmins:
Title: Security Admins
Permission:
admins:
Code: ADMIN
Group: =>Group.admins
editors:
Code: CMS_ACCESS_CMSMain
Group: =>Group.editors
allsections:
Code: CMS_ACCESS_LeftAndMain
Group: =>Group.allsections
securityadmins:
Code: CMS_ACCESS_SecurityAdmin
Group: =>Group.securityadmins
admins:
Code: ADMIN
Group: =>Group.admins
editors:
Code: CMS_ACCESS_CMSMain
Group: =>Group.editors
allsections:
Code: CMS_ACCESS_LeftAndMain
Group: =>Group.allsections
securityadmins:
Code: CMS_ACCESS_SecurityAdmin
Group: =>Group.securityadmins
Member:
editor:
FirstName: Test
Surname: Editor
Groups: =>Group.editors
admin:
FirstName: Test
Surname: Administrator
Groups: =>Group.admins
allsections:
Groups: =>Group.allsections
securityadmin:
Groups: =>Group.securityadmins
editor:
FirstName: Test
Surname: Editor
Groups: =>Group.editors
admin:
FirstName: Test
Surname: Administrator
Groups: =>Group.admins
allsections:
Groups: =>Group.allsections
securityadmin:
Groups: =>Group.securityadmins
Page:
home:
Title: Home
CanEditType: OnlyTheseUsers
EditorGroups: =>Group.admins
about:
Title: About Us
CanEditType: OnlyTheseUsers
EditorGroups: =>Group.admins
staff:
Title: Staff
URLSegment: my-staff
Parent: =>Page.about
ceo:
Title: CEO
Parent: =>Page.staff
staffduplicate:
Title: Staff
URLSegment: my-staff
Parent: =>Page.about
products:
Title: Products
CanEditType: OnlyTheseUsers
EditorGroups: =>Group.editors
product1:
Title: 1.1 Test Product
Parent: =>Page.products
CanEditType: Inherit
product2:
Title: Another Product
Parent: =>Page.products
CanEditType: Inherit
product3:
Title: Another Product
Parent: =>Page.products
CanEditType: Inherit
product4:
Title: Another Product
Parent: =>Page.products
CanEditType: OnlyTheseUsers
EditorGroups: =>Group.admins
contact:
Title: Contact Us
object:
Title: Object
controller:
Title: Controller
numericonly:
Title: 1930
home:
Title: Home
CanEditType: OnlyTheseUsers
EditorGroups: =>Group.admins
about:
Title: About Us
CanEditType: OnlyTheseUsers
EditorGroups: =>Group.admins
staff:
Title: Staff
URLSegment: my-staff
Parent: =>Page.about
ceo:
Title: CEO
Parent: =>Page.staff
staffduplicate:
Title: Staff
URLSegment: my-staff
Parent: =>Page.about
products:
Title: Products
CanEditType: OnlyTheseUsers
EditorGroups: =>Group.editors
product1:
Title: 1.1 Test Product
Parent: =>Page.products
CanEditType: Inherit
product2:
Title: Another Product
Parent: =>Page.products
CanEditType: Inherit
product3:
Title: Another Product
Parent: =>Page.products
CanEditType: Inherit
product4:
Title: Another Product
Parent: =>Page.products
CanEditType: OnlyTheseUsers
EditorGroups: =>Group.admins
contact:
Title: Contact Us
object:
Title: Object
controller:
Title: Controller
numericonly:
Title: 1930
metapage:
Title: 'HTML & XML'
MetaDescription: 'The <br /> and <br> tags'
ExtraMeta: '<link rel="canonical" href="http://www.mysite.com/html-and-xml" />'
SiteTreeTest_Conflicted:
parent:
Title: Parent
parent:
Title: Parent
ErrorPage:
404:
Title: Page not Found
ErrorCode: 404
404:
Title: Page not Found
ErrorCode: 404
RedirectorPage:
external:
Title: External
URLSegment: external
RedirectionType: External
ExternalURL: "http://www.google.com?a&b"
external:
Title: External
URLSegment: external
RedirectionType: External
ExternalURL: "http://www.google.com?a&b"