Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Updated for SS3

Removed Search Facility - Will be added as a separate module

Signed-off-by: Aram Balakjian <aram@aabweb.co.uk>
  • Loading branch information...
commit 1de092f2cf0cbade77bc153582e86b5da7fe262e 1 parent e49b500
Aram Balakjian authored
View
9 README.md
@@ -1,5 +1,14 @@
# DataObjectAsPage Module #
+## Maintainers
+
+ * Aram Balakjian (Nickname: willr, wrossiter)
+ <aram at aabweb dot co dot uk>
+
+## Requirements
+
+ * SilverStripe 3.0
+
## Overview ##
The module provides functionality for displaying DataObjects managed via ModelAdmin to appear as though they were
View
22 _config.php
@@ -6,14 +6,20 @@
define('MOD_DOAP_DIR',rtrim(array_pop($folders),DIRECTORY_SEPARATOR));
unset($folders);
+/*
+ * This fixesthe URL segment editor in model admin
+ *
+ * Note this will prevent any URL segment editor buttons from having translations
+ */
+DataObject::add_extension('LeftAndMain', 'DataObjectAsPageLeftAndMain');
-Object::add_extension('SiteTree', 'SiteTreeDoapSearchable');
-Object::add_extension('DataObjectAsPage', 'DoapSearchable');
-Object::add_extension('Page_Controller', 'DoapPage');
+/*
+ * Add this line to your _config.php to enable versioning on your DataObjectAsPage classes.
+ *
+ * Unfortunately this will to apply to all your DOAP classes as it needs to apply to the root DOAP class.
+ *
+ DataObjectAsPage::enable_versioning();
+ *
+ */
-//DataObject::add_extension('File', 'FileDoapSearchable');
-//Sitemap
-Director::addRules(10, array(
- 'search' => 'DoapSearch_Controller'
-));
View
49 code/Controllers/DoapSearch_Controller.php
@@ -1,49 +0,0 @@
-<?php
-
-class DoapSearch_Controller extends Page_Controller
-{
- static $allowed_actions = array(
- 'SearchForm'
- );
-
- function SearchForm()
- {
- $searchText = 'Search';
-
- if($this->request) {
- $searchText = $this->request->getVar('Search');
- }
-
- $fields = new FieldSet(
- $TextField = new TextField('Search')
- );
-
- //Action
- $submit = new FormAction("results", 'Go');
-
- $actions = new FieldSet($submit);
-
- $form = new DoapSearchForm($this, 'SearchForm', $fields, $actions);
- $form->addSearchableClasses(array('DataObjectAsPage'));
-
- if($this->Link())
- {
- $form->setFormAction($this->Link() . 'SearchForm');
- }
-
- return $form;
- }
-
- public function Link()
- {
- return Director::baseURL() . 'search/';
- }
-
-
- public function getSearchQuery($data = null) {
- // legacy usage: $data was defaulting to $_REQUEST, parameter not passed in doc.silverstripe.com tutorials
- $data = $_REQUEST;
-
- return (isset($data['Search'])) ? Convert::raw2xml($data['Search']) : '';
- }
-}
View
491 code/DataObjects/DataObjectAsPage.php
@@ -3,12 +3,11 @@
* Base class for DataObjects that behave like pages
*
*/
-class DataObjectAsPage extends DataObject {
+class DataObjectAsPage extends DataObject{
static $listing_page_class = 'DataObjectAsPageHolder';
static $db = array (
- "Status" => "Varchar",
'URLSegment' => 'Varchar(100)',
'Title' => 'Varchar(255)',
'MetaTitle' => 'Varchar(255)',
@@ -18,42 +17,14 @@ class DataObjectAsPage extends DataObject {
static $defaults = array(
'Title'=>'New Item',
- 'URLSegment' => 'new-item',
- 'Status' => 'Draft'
+ 'URLSegment' => 'new-item'
);
static $summary_fields = array(
'Title' => 'Title',
- 'URLSegment' => 'URLSegment',
- 'Status' => 'Status'
- );
-
- static $allowed_actions = array(
- 'doPublish',
- 'doUnpublish'
- );
-
- static $extensions = array(
- "Versioned('Stage', 'Live')"
- );
-
- //Better Search (Requires Better Search Module)
- static $indexes = array(
- "SearchFields" => "fulltext (Title, MetaDescription, Content)",
- "TitleSearchFields" => "fulltext (Title)",
- "URLSegment" => true
- );
-
- static $frontend_searchable_fields = array(
- 'Title',
- 'MetaDescription',
- 'Content'
+ 'URLSegment' => 'URLSegment'
);
- static $search_heading = "Title";
-
- static $search_content = "Content";
-
public static $default_sort = 'Created DESC';
//Return the Title for use in Menu2
@@ -67,9 +38,9 @@ public function canView($member = null)
{
//If this is draft check for permissions to view draft content
//getSearchResultItem is needed to ensure unpublished items don't show up in search results
- if($this->Status == 'Draft')
+ if($this->isVersioned && Versioned::current_stage() == 'Stage' && $this->Status == 'Draft')
{
- return (Permission::check('VIEW_DRAFT_CONTENT') && Versioned::current_stage() == 'Stage');
+ return (Permission::check('VIEW_DRAFT_CONTENT'));
}
elseif(Controller::curr()->hasMethod("canView"))
{
@@ -85,137 +56,282 @@ public function canPublish($member = null)
}
//Chek if current user can view
- public function canUnPublish($member = null)
+ public function canDeleteFromLive($member = null)
{
return true;
}
-
+
//Create duplicate button
public function getCMSActions()
{
- $Actions = parent::getCMSActions();
-
-
- if ($this->canPublish())
+ $actions = parent::getCMSActions();
+
+ $minorActions = CompositeField::create()->setTag('fieldset')->addExtraClass('ss-ui-buttonset');
+ $actions = new FieldList($minorActions);
+
+ if($this->ID)
{
- //Create the Save & Publish action
- $PublishAction = FormAction::create('doPublish', 'Save & Publish');
- $PublishAction->describe("Publish this item");
- $Actions->insertFirst($PublishAction);
- }
+ if($this->isVersioned)
+ {
+ if($this->isPublished() && $this->canPublish() && $this->canDeleteFromLive()) {
+ // "unpublish"
+ $minorActions->push(
+ FormAction::create('unpublish', _t('SiteTree.BUTTONUNPUBLISH', 'Unpublish'), 'delete')
+ ->setDescription(_t('SiteTree.BUTTONUNPUBLISHDESC', 'Remove this page from the published site'))
+ ->addExtraClass('ss-ui-action-destructive')->setAttribute('data-icon', 'unpublish')
+ );
+ }
+
+ if($this->canEdit()) {
+
+ if($this->canDelete()) {
+ // "delete"
+ $minorActions->push(
+ FormAction::create('delete','Delete')->addExtraClass('delete ss-ui-action-destructive')
+ ->setAttribute('data-icon', 'decline')
+ );
+ }
+
+ if($this->hasChangesOnStage()) {
+ if($this->isPublished() && $this->canEdit()) {
+ // "rollback"
+ $minorActions->push(
+ FormAction::create('rollback', _t('SiteTree.BUTTONCANCELDRAFT', 'Cancel draft changes'), 'delete')
+ ->setDescription(_t('SiteTree.BUTTONCANCELDRAFTDESC', 'Delete your draft and revert to the currently published page'))
+ );
+ }
+ }
+
+ if ($this->canCreate())
+ {
+ //Create the Duplicate action
+ $minorActions->push( FormAction::create('duplicate', 'Duplicate')
+ ->setDescription("Duplicate this item")
+ );
+ }
+ // "save"
+ $minorActions->push(
+ FormAction::create('doSave',_t('CMSMain.SAVEDRAFT','Save Draft'))->setAttribute('data-icon', 'addpage')
+ );
+ }
- $SaveAction = FormAction::create('doSaveToDraft', 'Save');
- $SaveAction->describe("Save a draft of this item");
- $Actions->insertFirst($SaveAction);
-
- if ($this->canCreate())
- {
- //Create the Duplicate action
- $DuplicateAction = FormAction::create('duplicate', 'Duplicate Object');
- $DuplicateAction->describe("Duplicate this item");
- //add it to the existing actions
- $Actions->insertFirst($DuplicateAction);
+ if($this->canPublish()) {
+ // "publish"
+ $actions->push(
+ FormAction::create('publish', _t('SiteTree.BUTTONSAVEPUBLISH', 'Save & Publish'))
+ ->addExtraClass('ss-ui-action-constructive')->setAttribute('data-icon', 'accept')
+ );
+ }
+ }
+ else
+ {
+ if($this->canDelete()) {
+ // "delete"
+ $minorActions->push(
+ FormAction::create('delete','Delete')->addExtraClass('delete ss-ui-action-destructive')
+ ->setAttribute('data-icon', 'decline')
+ );
+ }
+
+ if ($this->canCreate())
+ {
+ //Create the Duplicate action
+ $minorActions->push( FormAction::create('duplicate', 'Duplicate')
+ ->setDescription("Duplicate this item")
+ );
+ }
+
+ // "save"
+ $actions->push(
+ FormAction::create('doSave',_t('CMSMain.SAVE','Save'))->addExtraClass('ss-ui-action-constructive')->setAttribute('data-icon', 'addpage')
+ );
+ }
+
}
-
- if($this->Status != 'Draft' && $this->canUnPublish())
+ else
{
- //Create the Unpublish action
- $unPublishAction = FormAction::create('doUnpublish', 'Unpublish');
- $unPublishAction->describe("Unpublish this item");
- $unPublishAction->addExtraClass('delete');
- $Actions->insertFirst($unPublishAction);
+ //Change the Save label to 'Create'
+ $actions->push(FormAction::create('doSave', _t('GridFieldDetailForm.Create', 'Create'))
+ ->setUseButtonTag(true)
+ ->addExtraClass('ss-ui-action-constructive')
+ ->setAttribute('data-icon', 'add'));
}
-
- $DeleteAction = FormAction::create('doDeleteItem', 'Delete this item');
- $DeleteAction->describe("Delete this item");
- $Actions->insertFirst($DeleteAction);
- $ListViewAction = FormAction::create('listview', 'Go back to list');
- $ListViewAction->describe("Return to the list");
- $Actions->insertFirst($ListViewAction);
-
- return $Actions;
+ return $actions;
}
-
+
+
public function getCMSFields()
{
$fields = parent::getCMSFields();
-
+
//Add the status/view link
if($this->ID)
{
- $color = '#E88F31';
- $links = '<a target="_blank" href="' . $this->Link() . '?stage=Stage">Draft</a>';
- $status = $this->Status;
-
- if($this->Status == 'Published')
+ if($this->isVersioned)
{
- $color = '#000';
- $links .= ' | <a target="_blank" href="' . $this->Link() . '?stage=Live">Published</a>';
+ $status = $this->Status;
- if($this->hasChangesOnStage())
+ $color = '#E88F31';
+ $links = sprintf(
+ "<a target=\"_blank\" class=\"ss-ui-button\" data-icon=\"preview\" href=\"%s\">%s</a>", $this->Link() . '?Stage=stage', 'Draft'
+ );
+
+ if($this->Status == 'Published')
{
- $status .= ' (changed)';
- $color = '#428620';
+ $color = '#000';
+ $links .= sprintf(
+ "<a target=\"_blank\" class=\"ss-ui-button\" data-icon=\"preview\" href=\"%s\">%s</a>", $this->Link() . '?Stage=live', 'Published'
+ );;
+
+ if($this->hasChangesOnStage())
+ {
+ $status .= ' (changed)';
+ $color = '#428620';
+ }
}
+
+ $statusPill = '<h3 class="doapTitle" style="background: '.$color.';">'. $status . '</h3>';
+ }
+ else
+ {
+ $links = sprintf(
+ "<a target=\"_blank\" class=\"ss-ui-button\" data-icon=\"preview\" href=\"%s\">%s</a>", $this->Link() . '?Stage=stage', 'View'
+ );
+
+ $statusPill = "";
}
- $fields->insertFirst(new LiteralField('',
+ $fields->addFieldToTab('Root.Main', new LiteralField('',
'<div class="doapToolbar">
- <h3 class="doapTitle" style="background: '.$color.';">'. $status . '</h3>
+ ' . $statusPill . '
<p class="doapViewLinks">
- Page View:' . $links . '
+ ' . $links . '
</p>
- </div>
- '
+ </div>'
));
}
- $fields->addFieldToTab('Root.Main', new TextField('Title'));
-
- $fields->addFieldToTab('Root.Main', new HTMLEditorField('Content'));
-
//Remove Scafolded fields
$fields->removeFieldFromTab('Root.Main', 'URLSegment');
$fields->removeFieldFromTab('Root.Main', 'Status');
$fields->removeFieldFromTab('Root.Main', 'Version');
+ $fields->removeFieldFromTab('Root.Main', 'MetaTitle');
+ $fields->removeFieldFromTab('Root.Main', 'MetaDescription');
$fields->removeByName('Versions');
+ $fields->addFieldToTab('Root.Main', new TextField('Title'));
- //URLSegment
- $fields->addFieldToTab('Root.Metadata',
- new FieldGroup("URL",
- new LabelField('BaseUrlLabel', Director::absoluteBaseURL() . 'listing-page/show/'),
- new UniqueRestrictedTextField("URLSegment",
- "URL Segment",
- "Event",
- "Another event is using that URL. URL must be unique for each product",
- "[^a-z0-9-]+",
- "-",
- "URLs can only be made up of letters, digits and hyphens.",
- "",
- "",
- "",
- 50
- ),
- new LabelField('TrailingSlashLabel',"/")
- )
- );
+ if($this->ID)
+ {
+ $urlsegment = new SiteTreeURLSegmentField("URLSegment", $this->fieldLabel('URLSegment'));
+ $urlsegment->setURLPrefix(Director::absoluteBaseURL() . 'listing-page/show/');
+
+ $helpText = _t('SiteTreeURLSegmentField.HelpChars', ' Special characters are automatically converted or removed.');
+ $urlsegment->setHelpText($helpText);
+ $fields->addFieldToTab('Root.Main', $urlsegment);
+ }
- //MetaData fields
- $fields->addFieldToTab('Root.Metadata', new TextField('MetaTitle', 'Meta Title'));
- $fields->addFieldToTab('Root.Metadata', new TextField('MetaDescription', 'Meta Description'));
-
+ $fields->addFieldToTab('Root.Main', new HTMLEditorField('Content'));
+
+ $fields->addFieldToTab('Root.Main',new ToggleCompositeField('Metadata', 'Metadata',
+ array(
+ new TextField("MetaTitle", $this->fieldLabel('MetaTitle')),
+ new TextareaField("MetaDescription", $this->fieldLabel('MetaDescription'))
+ )
+ ));
+
+ //$fields->push(new HiddenField('PreviewURL', 'Preview URL', $this->StageLink()));
+ //$fields->push(new TextField('CMSEditURL', 'Preview URL', $this->CMSEditLink()));
+
return $fields;
}
+ public static function enable_versioning()
+ {
+ DataObject::add_extension('DataObjectAsPage','VersionedDataObjectAsPage');
+ DataObject::add_extension('DataObjectAsPage',"Versioned('Stage', 'Live')");
+ }
+
+ function getisVersioned()
+ {
+ return $this->hasExtension('Versioned');
+ }
+
+ /*
+ * Produce the correct breadcrumb trail for use on the DataObject Item Page
+ */
+ public function Breadcrumbs($maxDepth = 20, $unlinked = false, $stopAtPageType = false, $showHidden = false)
+ {
+ $page = Controller::curr();
+ $pages = array();
+
+ while(
+ $page
+ && (!$maxDepth || count($pages) < $maxDepth)
+ && (!$stopAtPageType || $page->ClassName != $stopAtPageType)
+ ) {
+ if($showHidden || $page->ShowInMenus || ($page->ID == $this->ID)) {
+ $pages[] = $page;
+ }
+
+ $page = $page->Parent;
+ }
+
+ $pages[] = $this;
+
+ $template = new SSViewer('BreadcrumbsTemplate');
+
+ return $template->process($this->customise(new ArrayData(array(
+ 'Pages' => new ArrayList($pages)
+ ))));
+ }
+
+ /*
+ * Generate custom metatags to display on the DataObject Item page
+ */
+ public function MetaTags($includeTitle = true)
+ {
+ $tags = "";
+ if($includeTitle === true || $includeTitle == 'true') {
+ $tags .= "<title>" . Convert::raw2xml(($this->MetaTitle)
+ ? $this->MetaTitle
+ : $this->Title) . "</title>\n";
+ }
+
+ $tags .= "<meta name=\"generator\" content=\"SilverStripe - http://silverstripe.org\" />\n";
+
+ $charset = ContentNegotiator::get_encoding();
+ $tags .= "<meta http-equiv=\"Content-type\" content=\"text/html; charset=$charset\" />\n";
+
+ if($this->MetaDescription) {
+ $tags .= "<meta name=\"description\" content=\"" . Convert::raw2att($this->MetaDescription) . "\" />\n";
+ }
+
+ $this->extend('MetaTags', $tags);
+
+ return $tags;
+ }
+
+ /**
+ * Check if this page has been published.
+ *
+ * @return boolean True if this page has been published.
+ */
+ function isPublished()
+ {
+ return (DB::query("SELECT \"ID\" FROM \"DataObjectAsPage_Live\" WHERE \"ID\" = $this->ID")->value())
+ ? true
+ : false;
+ }
+
/**
* Create a duplicate of this node. Doesn't affect joined data - create a
* custom overloading of this if you need such behaviour.
*
* @return SiteTree The duplicated object.
*/
- public function duplicate($doWrite = true)
+ public function doDuplicate($doWrite = true)
{
$item = parent::duplicate(false);
$this->extend('onBeforeDuplicate', $item);
@@ -239,7 +355,8 @@ public function duplicate($doWrite = true)
* @uses SiteTreeDecorator->onBeforePublish()
* @uses SiteTreeDecorator->onAfterPublish()
*/
- function doPublish() {
+ function doPublish()
+ {
if (!$this->canPublish()) return false;
$original = Versioned::get_one_by_stage("DataObjectAsPage", "Live", "\"DataObjectAsPage\".\"ID\" = $this->ID");
@@ -247,6 +364,7 @@ function doPublish() {
// Handle activities undertaken by decorators
$this->invokeWithExtensions('onBeforePublish', $original);
+
$this->Status = "Published";
//$this->PublishedByID = Member::currentUser()->ID;
$this->write();
@@ -265,7 +383,7 @@ function doPublish() {
function doUnpublish()
{
if(!$this->ID) return false;
- if (!$this->canUnPublish()) return false;
+ if (!$this->canDeleteFromLive()) return false;
$this->extend('onBeforeUnpublish');
@@ -291,8 +409,8 @@ function doUnpublish()
return true;
}
- function doDelete() {
-
+ function doDelete()
+ {
$this->doUnpublish();
$oldMode = Versioned::get_reading_mode();
@@ -306,7 +424,24 @@ function doDelete() {
return $result;
}
+
+
+ /**
+ * Revert the draft changes: replace the draft content with the content on live
+ */
+ function doRevertToLive()
+ {
+ $this->publish("Live", "Stage", false);
+ // Use a clone to get the updates made by $this->publish
+ $clone = DataObject::get_by_id("DataObjectAsPage", $this->ID);
+ $clone->writeWithoutVersion();
+
+ $this->extend('onAfterRevertToLive');
+
+ return $clone;
+ }
+
/**
* Check whether this DO has changes which are not published
*/
@@ -317,48 +452,50 @@ public function hasChangesOnStage()
return ($latestPublishedVersion < $latestVersion);
}
-
+
/*
* Get the listing page to view this Event on (used in Link functions below)
*/
function getListingPage(){
+ $listingClass = $this->stat('listing_class');
+
if(Controller::curr()->ClassName == $this->stat('listing_class'))
{
- $ListingPage = Controller::curr();
+ $listingPage = Controller::curr();
}
else
{
- //Needed for search results to work ($this->EventTypeID returns nothing)
- $Item = DataObject::get_by_id($this->ClassName, $this->ID);
-
- $ListingPage = DataObject::get_one($this->stat('listing_class'));
+ $listingPage = $listingClass::get()->First();
}
- return $ListingPage;
+ return $listingPage;
}
/*
* Generate the link to this DataObject Item page
*/
- function Link($ExtraURLVar = null)
+ function Link($extraURLVar = null)
{
//Hack for search results
- if($Item = DataObject::get_by_id(get_class($this), $this->ID))
+ if($item = DataObject::get_by_id(get_class($this), $this->ID))
{
//Build link
- if($ListingPage = $Item->getListingPage())
+ if($listingPage = $item->getListingPage())
{
- return $ListingPage->Link('show/' . $Item->URLSegment . '/' . $ExtraURLVar);
+ return $listingPage->Link('show/' . $item->URLSegment . '/' . $extraURLVar);
}
}
}
function absoluteLink($appendVal = null)
{
- return $this->getListingPage()->absoluteLink('show/' . $this->URLSegment . $appendVal);
+ if($listingPage = $this->getListingPage())
+ {
+ return $listingPage->absoluteLink('show/' . $this->URLSegment . $appendVal);
+ }
}
-
+
/*
* Return the correct linking mode, for use in menus
*/
@@ -377,73 +514,103 @@ public function LinkingMode()
/*
* Set URLSegment to be unique on write
- */
+ */
public function onBeforeWrite()
{
parent::onBeforeWrite();
-
+
//Set MetaData
if(!$this->MetaTitle)
{
$this->MetaTitle = $this->Title;
}
+
// If there is no URLSegment set, generate one from Title
if((!$this->URLSegment || $this->URLSegment == 'new-item') && $this->Title != 'New Item')
{
- $this->URLSegment = SiteTree::generateURLSegment($this->Title);
+ $this->URLSegment = $this->generateURLSegment($this->Title);
}
else if($this->isChanged('URLSegment'))
{
// Make sure the URLSegment is valid for use in a URL
$segment = preg_replace('/[^A-Za-z0-9]+/','-',$this->URLSegment);
$segment = preg_replace('/-+/','-',$segment);
-
+
// If after sanitising there is no URLSegment, give it a reasonable default
if(!$segment) {
$segment = "item-$this->ID";
- }
- $this->URLSegment = $segment;
}
-
- // Ensure that this object has a non-conflicting URLSegment value.
+ $this->URLSegment = $segment;
+ }
+
+ // Ensure that this object has a non-conflicting URLSegment value.
$count = 2;
-
+
$URLSegment = $this->URLSegment;
$ID = $this->ID;
-
+
while($this->LookForExistingURLSegment($URLSegment, $ID))
{
$URLSegment = preg_replace('/-[0-9]+$/', null, $URLSegment) . '-' . $count;
$count++;
}
-
+
$this->URLSegment = $URLSegment;
+
}
-
+
function onAfterWrite() {
parent::onAfterWrite();
- // Clear out obselete versions of records since there is no way to role back to previous versions yet.
- if(DB::query("SELECT \"ID\" FROM \"DataObjectAsPage\" WHERE \"ID\" = $this->ID")->value()) {
-
- $LiveVersionID = DB::query("SELECT \"Version\" FROM \"DataObjectAsPage_Live\" WHERE \"ID\" = $this->ID")->value();
- $DraftVersionID = DB::query("SELECT \"Version\" FROM \"DataObjectAsPage\" WHERE \"ID\" = $this->ID")->value();
-
- if($LiveVersionID){
- DB::query("DELETE FROM DataObjectAsPage_versions WHERE RecordID = $this->ID AND Version != '" . $DraftVersionID . "' AND Version != '" . $LiveVersionID . "'");
- } else {
- DB::query("DELETE FROM DataObjectAsPage_versions WHERE RecordID = $this->ID AND Version != '" . $DraftVersionID . "'");
- }
+
+ if($this->ID && $this->isVersioned)
+ {
+ // Clear out obselete versions of records since there is no way to role back to previous versions yet.
+ if(DB::query("SELECT \"ID\" FROM \"DataObjectAsPage\" WHERE \"ID\" = $this->ID")->value()) {
+
+ $LiveVersionID = DB::query("SELECT \"Version\" FROM \"DataObjectAsPage_Live\" WHERE \"ID\" = $this->ID")->value();
+ $DraftVersionID = DB::query("SELECT \"Version\" FROM \"DataObjectAsPage\" WHERE \"ID\" = $this->ID")->value();
+
+ if($LiveVersionID){
+ DB::query("DELETE FROM DataObjectAsPage_versions WHERE RecordID = $this->ID AND Version != '" . $DraftVersionID . "' AND Version != '" . $LiveVersionID . "'");
+ } else {
+ DB::query("DELETE FROM DataObjectAsPage_versions WHERE RecordID = $this->ID AND Version != '" . $DraftVersionID . "'");
+ }
+ }
}
+
}
-
+
//Test whether the URLSegment exists already on another Item
public function LookForExistingURLSegment($URLSegment, $ID)
{
- $Where = "`DataObjectAsPage`.`URLSegment` = '" . $URLSegment . "' AND `DataObjectAsPage`.`ID` != $ID";
- $Item = (DataObject::get_one('DataObjectAsPage', $Where));
+ $where = "URLSegment = '" . $URLSegment . "' AND ID != $ID";
+ $item = DataObjectAsPage::get()->where($where)->first();
+
+ return $item;
+ }
+
+ /**
+ * Generate a URL segment based on the title provided.
+ *
+ * If {@link Extension}s wish to alter URL segment generation, they can do so by defining
+ * updateURLSegment(&$url, $title). $url will be passed by reference and should be modified.
+ * $title will contain the title that was originally used as the source of this generated URL.
+ * This lets extensions either start from scratch, or incrementally modify the generated URL.
+ *
+ * @param string $title Page title.
+ * @return string Generated url segment
+ */
+ function generateURLSegment($title){
+ $filter = URLSegmentFilter::create();
+ $t = $filter->filter($title);
+
+ // Fallback to generic page name if path is empty (= no valid, convertable characters)
+ if(!$t || $t == '-' || $t == '-1') $t = "page-$this->ID";
- return $Item;
+ // Hook for extensions
+ $this->extend('updateURLSegment', $t, $title);
+
+ return $t;
}
-
}
View
15 code/Decorators/DataObjectAsPageLeftAndMain.php
@@ -0,0 +1,15 @@
+<?php
+/**
+ * Fixes URL segment editor issues in model admin without breaking Pages
+ *
+ * Note. This will stop translation of URL segment field buttons.
+ *
+ */
+class DataObjectAsPageLeftAndMain extends Extension{
+
+ function init() {
+ Requirements::block(CMS_DIR . '/javascript/SiteTreeURLSegmentField.js');
+ Requirements::javascript(MOD_DOAP_DIR . '/javascript/SiteTreeURLSegmentField_modeladmin.js');
+ }
+
+}
View
19 code/Decorators/DoapPage.php
@@ -1,19 +0,0 @@
-<?php
-
-class DoapPage extends Extension
-{
- function extraStatics()
- {
- return array(
- );
- }
- /**
- * Custom Site search form
- */
- function SearchForm()
- {
- $form = Singleton("DoapSearch_Controller")->SearchForm();
-
- return $form;
- }
-}
View
21 code/Decorators/DoapSearchable.php
@@ -1,21 +0,0 @@
-<?php
-
-class DoapSearchable extends Extension
-{
- function extraStatics()
- {
- return array(
- 'indexes' => array(
- "SearchFields" => "fulltext (Title, MetaDescription, Content)",
- "TitleSearchFields" => "fulltext (Title)"
- )
- );
- }
-
- //Strange search needs this to have access to all fields in results
- public function getSearchResultItem()
- {
- return DataObject::get_by_id($this->owner->ClassName, $this->owner->ID);
- }
-
-}
View
13 code/Decorators/FileDoapSearchable.php
@@ -1,13 +0,0 @@
-<?php
-
-class FileDoapSearchable extends DataObjectDecorator
-{
- function extraStatics()
- {
- return array(
- 'indexes' => array(
- "SearchFields" => "fulltext (Filename,Title,Content)"
- )
- );
- }
-}
View
16 code/Decorators/SiteTreeDoapSearchable.php
@@ -1,16 +0,0 @@
-<?php
-
-class SiteTreeDoapSearchable extends SiteTreeDecorator
-{
- function extraStatics()
- {
- return array(
- 'indexes' => array(
- "SearchFields" => "fulltext (Title, MenuTitle, Content, MetaTitle, MetaDescription, MetaKeywords)",
- "TitleSearchFields" => "fulltext (Title)",
- "URLSegment" => true
- )
- );
- }
-
-}
View
20 code/Decorators/VersionedDataObjectAsPage.php
@@ -0,0 +1,20 @@
+<?php
+
+class VersionedDataObjectAsPage extends DataExtension{
+
+ static $db = array(
+ "Status" => "Varchar"
+ );
+
+ static $summary_fields = array(
+ 'Status' => 'Status'
+ );
+
+ static $defaults = array(
+ 'Status' => 'Draft'
+ );
+
+ static $versioning = array(
+ "Stage", "Live"
+ );
+}
View
24 code/Forms/DataObjectAsPageTableField.php
@@ -1,24 +0,0 @@
-<?php
-
-class DataObjectAsPageTableField extends TableListField {
-
- function handleItem($request) {
- return new DataObjectAsPageTableField_ItemRequest($this, $request->param('ID'));
- }
-}
-
-class DataObjectAsPageTableField_ItemRequest extends TableListField_ItemRequest {
-
- function delete($request) {
- // Protect against CSRF on destructive action
- $token = $this->ctf->getForm()->getSecurityToken();
- if(!$token->checkRequest($request)) return $this->httpError('400');
-
- if($this->ctf->Can('delete') !== true) {
- return false;
- }
-
- $this->dataObj()->doDelete();
- }
-}
-?>
View
352 code/Forms/DoapSearchForm.php
@@ -1,352 +0,0 @@
-<?php
-
-class DoapSearchForm extends Form {
-
- /**
- * @var boolean $showInSearchTurnOn
- * @deprecated 2.3 SiteTree->ShowInSearch should always be respected
- */
- protected $showInSearchTurnOn;
-
- /**
- * @deprecated 2.3 Use {@link $pageLength}.
- */
- protected $numPerPage;
-
- /**
- * @var int $pageLength How many results are shown per page.
- * Relies on pagination being implemented in the search results template.
- */
- protected $pageLength = 10;
-
- /**
- * Classes to search
- */
- protected $classesToSearch = array("SiteTree");
-
- private $customSearchClasses = array();
-
- private static function add_search_fields($object, $record)
- {
- $object->setField('Title',$record['Title']);
- $object->setField('Content',$record['Content']);
- return $object;
- }
-
- public function addSearchableClasses($classes)
- {
- $map = array();
- foreach($classes as $class) {
-
- $SNG = singleton($class);
-
- if($SNG->stat('frontend_searchable_fields'))
- {
- $map[$class]['Fields'] = $SNG->stat('frontend_searchable_fields');
- }
- else
- {
- $map[$class]['Fields'] = $SNG->stat('searchable_fields');
- }
-
- $map[$class]['Title'] = $SNG->stat('search_heading');
- $map[$class]['Content'] = $SNG->stat('search_content');
- }
- $this->customSearchClasses = $map;
- }
-
- /**
- *
- * @param Controller $controller
- * @param string $name The name of the form (used in URL addressing)
- * @param FieldSet $fields Optional, defaults to a single field named "Search". Search logic needs to be customized
- * if fields are added to the form.
- * @param FieldSet $actions Optional, defaults to a single field named "Go".
- * @param boolean $showInSearchTurnOn DEPRECATED 2.3
- */
- function __construct($controller, $name, $fields = null, $actions = null, $showInSearchTurnOn = true) {
- $this->showInSearchTurnOn = $showInSearchTurnOn;
-
- if(!$fields) {
- $fields = new FieldSet(
- new TextField('Search', _t('SearchForm.SEARCH', 'Search')
- ));
- }
-
- if(singleton('SiteTree')->hasExtension('Translatable')) {
- $fields->push(new HiddenField('locale', 'locale', Translatable::get_current_locale()));
- }
-
- if(!$actions) {
- $actions = new FieldSet(
- new FormAction("getResults", _t('SearchForm.GO', 'Go'))
- );
- }
-
- parent::__construct($controller, $name, $fields, $actions);
-
- $this->setFormMethod('get');
-
- $this->disableSecurityToken();
- }
-
- public function forTemplate() {
- return $this->renderWith(array(
- 'SearchForm',
- 'Form'
- ));
- }
-
- function classesToSearch($classes) {
- $this->classesToSearch = $classes;
- }
-
- /**
- * Return dataObjectSet of the results using $_REQUEST to get info from form.
- * Wraps around {@link searchEngine()}.
- *
- * @param int $pageLength DEPRECATED 2.3 Use SearchForm->pageLength
- * @param array $data Request data as an associative array. Should contain at least a key 'Search' with all searched keywords.
- * @return DataObjectSet
- */
- public function getResults($pageLength = null, $data = null){
- // legacy usage: $data was defaulting to $_REQUEST, parameter not passed in doc.silverstripe.com tutorials
- if(!isset($data)) $data = $_REQUEST;
-
- // set language (if present)
- if(singleton('SiteTree')->hasExtension('Translatable') && isset($data['locale'])) {
- $origLocale = Translatable::get_current_locale();
- Translatable::set_current_locale($data['locale']);
- }
-
- $keywords = $data['Search'];
-
- $andProcessor = create_function('$matches','
- return " +" . $matches[2] . " +" . $matches[4] . " ";
- ');
- $notProcessor = create_function('$matches', '
- return " -" . $matches[3];
- ');
-
- $keywords = preg_replace_callback('/()("[^()"]+")( and )("[^"()]+")()/i', $andProcessor, $keywords);
- $keywords = preg_replace_callback('/(^| )([^() ]+)( and )([^ ()]+)( |$)/i', $andProcessor, $keywords);
- $keywords = preg_replace_callback('/(^| )(not )("[^"()]+")/i', $notProcessor, $keywords);
- $keywords = preg_replace_callback('/(^| )(not )([^() ]+)( |$)/i', $notProcessor, $keywords);
-
- $keywords = $this->addStarsToKeywords($keywords);
-
- if(strpos($keywords, '"') !== false || strpos($keywords, '+') !== false || strpos($keywords, '-') !== false || strpos($keywords, '*') !== false) {
- $results = $this->searchEngine($keywords, $pageLength, "Relevance DESC", "", true);
- } else {
- $results = $this->searchEngine($keywords, $pageLength);
- }
-
- // filter by permission
- if($results) foreach($results as $result) {
- if(!$result->canView()) $results->remove($result);
- }
-
- // reset locale
- if(singleton('SiteTree')->hasExtension('Translatable') && isset($data['locale'])) {
- Translatable::set_current_locale($origLocale);
- }
-
- return $results;
- }
-
- protected function addStarsToKeywords($keywords) {
- if(!trim($keywords)) return "";
- // Add * to each keyword
- $splitWords = split(" +" , trim($keywords));
- while(list($i,$word) = each($splitWords)) {
- if($word[0] == '"') {
- while(list($i,$subword) = each($splitWords)) {
- $word .= ' ' . $subword;
- if(substr($subword,-1) == '"') break;
- }
- } else {
- $word .= '*';
- }
- $newWords[] = $word;
- }
- return implode(" ", $newWords);
- }
-
-
-
- public function searchEngine($keywords, $pageLength = null, $sortBy = "Relevance DESC", $extraFilter = "", $booleanSearch = false, $alternativeFileFilter = "", $invertedMatch = false) {
- if(!$pageLength) $pageLength = $this->pageLength;
- $fileFilter = '';
- $keywords = addslashes($keywords);
-
- $extraFilters = array('SiteTree' => '', 'File' => '');
- foreach($this->customSearchClasses as $class => $arr)
- $extraFilters[$class] = "";
-
- if($booleanSearch) $boolean = "IN BOOLEAN MODE";
- if($extraFilter) {
- $extraFilters['SiteTree'] = " AND $extraFilter";
-
- if($alternativeFileFilter) $extraFilters['File'] = " AND $alternativeFileFilter";
- else $extraFilters['File'] = $extraFilters['SiteTree'];
-
- foreach($this->customSearchClasses as $class => $arr)
- $extraFilters[$class] = " AND $extraFilter";
-
- }
-
- if($this->showInSearchTurnOn) $extraFilters['SiteTree'] .= " AND showInSearch <> 0";
-
- $start = isset($_GET['start']) ? (int)$_GET['start'] : 0;
- $limit = $start . ", " . (int) $pageLength;
-
- $notMatch = $invertedMatch ? "NOT " : "";
- if($keywords) {
- $match['SiteTree'] = "MATCH (Title, MenuTitle, Content, MetaTitle, MetaDescription, MetaKeywords) AGAINST ('$keywords' $boolean)";
- $match['File'] = "MATCH (Filename, Title, Content) AGAINST ('$keywords' $boolean) AND ClassName = 'File'";
-
- // We make the relevance search by converting a boolean mode search into a normal one
- $relevanceKeywords = str_replace(array('*','+','-'),'',$keywords);
- $relevance['SiteTree'] = "MATCH (Title) AGAINST ('$relevanceKeywords') + MATCH (Title, MenuTitle, Content, MetaTitle, MetaDescription, MetaKeywords) AGAINST ('$relevanceKeywords')";
- $relevance['File'] = "MATCH (Filename, Title, Content) AGAINST ('$relevanceKeywords')";
- } else {
- $relevance['SiteTree'] = $relevance['File'] = 1;
- $match['SiteTree'] = $match['File'] = "1 = 1";
- }
-
-
- if($keywords) {
- foreach($this->customSearchClasses as $class => $arr) {
- $match[$class] = "MATCH (".implode(',',$arr['Fields']).") AGAINST ('$keywords' $boolean)";
- $relevanceKeywords = str_replace(array('*','+','-'),'',$keywords);
- $relevance[$class] = "MATCH (".implode(',',$arr['Fields']).") AGAINST ('$relevanceKeywords')";
- }
- }
- else {
- foreach($this->customSearchClasses as $class => $arr) {
- $relevance[$class] = 1;
- $match[$class] = "1 = 1";
- }
- }
-
-
- // Generate initial queries and base table names
- $joinClasses = array();
- $baseClasses = array('SiteTree' => '', 'File' => '');
- foreach($this->classesToSearch as $class) {
- $queries[$class] = singleton($class)->extendedSQL($notMatch . $match[$class] . $extraFilters[$class], "");
- $baseClasses[$class] = reset($queries[$class]->from);
- }
-
- foreach($this->customSearchClasses as $class => $arr) {
- $queries[$class] = singleton($class)->extendedSQL($notMatch . $match[$class] . $extraFilters[$class], "");
- $baseClasses[$class] = reset($queries[$class]->from);
- // get any classes we need to join on
- while($from = next($queries[$class]->from)){
- $joinClasses[$class] = $from;
- }
- }
-
-
- // Make column selection lists
- $select = array(
- 'SiteTree' => array("ClassName","$baseClasses[SiteTree].ID","ParentID","Title","URLSegment","Content","LastEdited","Created","_utf8'' AS Filename", "_utf8'' AS Name", "$relevance[SiteTree] AS Relevance", "CanViewType"),
- 'File' => array("ClassName","$baseClasses[File].ID","_utf8'' AS ParentID","Title","_utf8'' AS URLSegment","Content","LastEdited","Created","Filename","Name","$relevance[File] AS Relevance","NULL AS CanViewType"),
- );
-
- foreach($this->customSearchClasses as $class => $arr) {
- $select[$class] = array(
- "ClassName",
- "$baseClasses[$class].ID",
- "NULL AS ParentID",
- $arr['Title'] . " AS Title",
- "NULL AS URLSegment",
- $arr['Content'] . " AS Content",
- "LastEdited",
- "Created",
- "NULL AS Filename",
- "NULL AS Name",
- "$relevance[$class] AS Relevance",
- "NULL AS CanViewType"
- );
- }
-
- // Process queries
- foreach($this->classesToSearch as $class) {
- // There's no need to do all that joining
- $queries[$class]->from = array(str_replace('`','',$baseClasses[$class]) => $baseClasses[$class]);
- $queries[$class]->select = $select[$class];
- $queries[$class]->orderby = null;
- }
-
-
- foreach($this->customSearchClasses as $class => $arr) {
- $queries[$class]->from = array(str_replace('`','', $baseClasses[$class]) => $baseClasses[$class]);
- if (isset($joinClasses[$class])){
-
- $queries[$class]->from[] = $joinClasses[$class];
- }
- $queries[$class]->select = $select[$class];
- $queries[$class]->orderby = null;
- }
-
-
- // Combine queries
- $querySQLs = array();
- $totalCount = 0;
- foreach($queries as $query) {
- $querySQLs[] = $query->sql();
- $totalCount += $query->unlimitedRowCount();
- }
- $fullQuery = implode(" UNION ", $querySQLs) . " ORDER BY $sortBy LIMIT $limit";
- // Get records
- $records = DB::query($fullQuery);
- //print $fullQuery;
-
- foreach($records as $record) {
- $o = new $record['ClassName']($record);
- if(!$o instanceof SiteTree)
- $objects[] = self::add_search_fields($o, $record);
- else
- $objects[] = $o;
- }
- if(isset($objects)) $doSet = new DataObjectSet($objects);
- else $doSet = new DataObjectSet();
-
- $doSet->setPageLimits($start, $pageLength, $totalCount);
- return $doSet;
- }
-
- /**
- * Get the search query for display in a "You searched for ..." sentence.
- *
- * @param array $data
- * @return string
- */
- public function getSearchQuery($data = null) {
- // legacy usage: $data was defaulting to $_REQUEST, parameter not passed in doc.silverstripe.com tutorials
- if(!isset($data)) $data = $_REQUEST;
-
- return Convert::raw2xml($data['Search']);
- }
-
- /**
- * Set the maximum number of records shown on each page.
- *
- * @param int $length
- */
- public function setPageLength($length) {
- $this->pageLength = $length;
- }
-
- /**
- * @return int
- */
- public function getPageLength() {
- // legacy handling for deprecated $numPerPage
- return (isset($this->numPerPage)) ? $this->numPerPage : $this->pageLength;
- }
-
-}
-
-?>
View
29 code/Forms/VersionedGridFieldDeleteAction.php
@@ -0,0 +1,29 @@
+<?php
+
+
+class VersionedGridFieldDeleteAction extends GridFieldDeleteAction
+{
+ /**
+ * Handle the actions and apply any changes to the GridField
+ *
+ * @param GridField $gridField
+ * @param string $actionName
+ * @param mixed $arguments
+ * @param array $data - form data
+ * @return void
+ */
+ public function handleAction(GridField $gridField, $actionName, $arguments, $data) {
+ if($actionName == 'deleterecord') {
+ $item = $gridField->getList()->byID($arguments['RecordID']);
+ if(!$item) {
+ return;
+ }
+ if($actionName == 'deleterecord' && !$item->canDelete()) {
+ throw new ValidationException(_t('GridFieldAction_Delete.DeletePermissionsFailure',"No delete permissions"),0);
+ }
+ if($actionName == 'deleterecord') {
+ $item->doDelete();
+ }
+ }
+ }
+}
View
207 code/Forms/VersionedGridFieldDetailForm.php
@@ -0,0 +1,207 @@
+<?php
+
+class VersionedGridFieldDetailForm extends GridFieldDetailForm {
+
+
+}
+
+class VersionedGridFieldDetailForm_ItemRequest extends GridFieldDetailForm_ItemRequest {
+
+ function ItemEditForm()
+ {
+ $form = parent::ItemEditForm();
+ $record = $this->record;
+
+ $actions = $record->getCMSActions();
+ $form->setActions($actions);
+
+ return $form;
+ }
+
+ /*
+ //Unable to get preview working for now
+ public function LinkPreview()
+ {
+ $record = $this->record;
+
+ $baseLink = $record->CMSEditLink();
+
+ return $baseLink;
+ }
+ */
+
+ public function doSave($data, $form) {
+ $new_record = $this->record->ID == 0;
+ $controller = Controller::curr();
+
+ try {
+ $form->saveInto($this->record);
+ $this->record->write();
+ $this->gridField->getList()->add($this->record);
+ } catch(ValidationException $e) {
+ $form->sessionMessage($e->getResult()->message(), 'bad');
+ $responseNegotiator = new PjaxResponseNegotiator(array(
+ 'CurrentForm' => function() use(&$form) {
+ return $form->forTemplate();
+ },
+ 'default' => function() use(&$controller) {
+ return $controller->redirectBack();
+ }
+ ));
+ if($controller->getRequest()->isAjax()){
+ $controller->getRequest()->addHeader('X-Pjax', 'CurrentForm');
+ }
+ return $responseNegotiator->respond($controller->getRequest());
+ }
+
+ // TODO Save this item into the given relationship
+
+ $message = sprintf(
+ _t('GridFieldDetailForm.Saved', 'Saved %s %s'),
+ $this->record->singular_name(),
+ '<a href="' . $this->Link('edit') . '">"' . htmlspecialchars($this->record->Title, ENT_QUOTES) . '"</a>'
+ );
+
+ $form->sessionMessage($message, 'good');
+
+ if($new_record) {
+ return Controller::curr()->redirect($this->Link());
+ } elseif($this->gridField->getList()->byId($this->record->ID)) {
+ // Return new view, as we can't do a "virtual redirect" via the CMS Ajax
+ // to the same URL (it assumes that its content is already current, and doesn't reload)
+ return $this->edit(Controller::curr()->getRequest());
+ } else {
+ // Changes to the record properties might've excluded the record from
+ // a filtered list, so return back to the main view if it can't be found
+ $noActionURL = $controller->removeAction($data['url']);
+ $controller->getRequest()->addHeader('X-Pjax', 'Content');
+ return $controller->redirect($noActionURL, 302);
+ }
+ }
+
+ public function publish($data, $form)
+ {
+ try {
+
+ if($record = $this->record)
+ {
+ if (!$record->canPublish()) {
+ throw new ValidationException(_t('GridFieldDetailForm.DeletePermissionsFailure',"No publish permissions"),0);
+ }
+
+ $record->doPublish();
+ }
+
+ } catch(ValidationException $e) {
+ $this->executeException($form,$e);
+ }
+
+ return $this->completeAction($form, $data, 'Published');
+ }
+
+
+ public function unpublish($data, $form)
+ {
+ try {
+ if($record = $this->record)
+ {
+ if (!$record->canDeleteFromLive()) {
+ throw new ValidationException(_t('GridFieldDetailForm.DeletePermissionsFailure',"No unpublish permissions"),0);
+ }
+
+ $record->doUnpublish();
+ }
+ } catch(ValidationException $e) {
+ $this->executeException($form,$e);
+ }
+
+ return $this->completeAction($form, $data, 'Unplublished');
+ }
+
+ public function delete($data, $form)
+ {
+ try {
+ $toDelete = $this->record;
+ if (!$toDelete->canDelete()) {
+ throw new ValidationException(_t('GridFieldDetailForm.DeletePermissionsFailure',"No delete permissions"),0);
+ }
+
+ //This extra line is needed to remove the records with this ID from the versions table.
+ DB::query("DELETE FROM DataObjectAsPage_versions WHERE RecordID = '$record->ID'");
+ $record->doDelete();
+ } catch(ValidationException $e) {
+ $this->executeException($form,$e);
+ }
+
+ return $this->completeAction($form, $data, 'Deleted');
+ }
+
+ public function rollback($data, $form) {
+
+ try {
+ if($record = $this->record)
+ {
+ $reverted = $record->doRevertToLive();
+ }
+
+ } catch(ValidationException $e) {
+ $this->executeException($form,$e);
+ }
+
+ $this->record = $reverted;
+
+ return $this->completeAction($form, $data, 'Draft changed cancelled for');
+ }
+
+ public function duplicate($data, $form, $request) {
+
+ try {
+ if($record = $this->record)
+ {
+ //Duplicate the object
+ $clone = $record->doDuplicate();
+ }
+
+ } catch(ValidationException $e) {
+ $this->executeException($form,$e);
+ }
+
+ $this->record = $clone;
+
+ return $this->completeAction($form, $data, 'Duplicated');
+ }
+
+
+ /*
+ * Consolidating code, repeated in each action funciton above
+ */
+ private function completeAction($form, $data, $message)
+ {
+ $fullMessage = $message . " " . $this->record->singular_name() . " " . htmlspecialchars($this->record->Title, ENT_QUOTES);
+
+ $form->sessionMessage($fullMessage, 'good');
+
+ if($this->gridField->getList()->byId($this->record->ID))
+ {
+ return $this->edit(Controller::curr()->getRequest());
+ }
+ else
+ {
+ // Changes to the record properties might've excluded the record from
+ // a filtered list, so return back to the main view if it can't be found
+ $noActionURL = $controller->removeAction($data['url']);
+ $controller->getRequest()->addHeader('X-Pjax', 'Content');
+ return $controller->redirect($noActionURL, 302);
+ }
+ }
+
+
+ /*
+ * Consolidating code, repeated in each action funciton above
+ */
+ private function executeException($form, $e)
+ {
+ $form->sessionMessage($e->getResult()->message(), 'bad');
+ return Controller::curr()->redirectBack();
+ }
+}
View
130 code/ModelAdmin/DataObjectAsPageAdmin.php
@@ -2,113 +2,53 @@
class DataObjectAsPageAdmin extends ModelAdmin
{
- public static $record_controller_class = "DataObjectAsPageAdmin_RecordController";
- protected $resultsTableClassName = 'DataObjectAsPageTableField';
public function init()
{
parent::init();
- // Remove all the junk that will break ModelAdmin
- Requirements::javascript(MOD_DOAP_DIR . '/javascript/jquery.dataobjectaspageadmin.js');
+ //Styling for preview links and status
Requirements::CSS(MOD_DOAP_DIR . '/css/dataobjectaspageadmin.css');
}
-}
+
+ function getEditForm($id = null, $fields = null) {
-class DataObjectAsPageAdmin_RecordController extends ModelAdmin_RecordController
-{
- public function doPublish($data, $form, $request)
- {
- $record = $this->currentRecord;
+ $form = parent::getEditForm($id = null, $fields = null);
+
+ if(Singleton($this->modelClass)->isVersioned)
+ {
+ $listfield = $form->Fields()->fieldByName($this->modelClass);
- if($record && !$record->canPublish())
- return Security::permissionFailure($this);
-
- $form->saveInto($record);
-
- $record->doPublish();
-
- if(Director::is_ajax()) {
- return $this->edit($request);
- } else {
- Director::redirectBack();
- }
- }
-
- public function doSaveToDraft($data, $form, $request) {
- $form->saveInto($this->currentRecord);
+ $gridFieldConfig = $listfield->getConfig();
- try {
- $this->currentRecord->write();
- } catch(ValidationException $e) {
- $form->sessionMessage($e->getResult()->message(), 'bad');
- }
-
- // Behaviour switched on ajax.
- if(Director::is_ajax()) {
- return $this->edit($request);
- } else {
- Director::redirectBack();
+ $gridFieldConfig->getComponentByType('GridFieldDetailForm')
+ ->setItemRequestClass('VersionedGridFieldDetailForm_ItemRequest');
+
+ $gridFieldConfig->removeComponentsByType('GridFieldDeleteAction');
+ $gridFieldConfig->addComponent(new VersionedGridFieldDeleteAction());
}
+
+ return $form;
}
- public function doUnpublish($data, $form, $request)
- {
- $record = $this->currentRecord;
-
- if($record && !$record->canUnPublish())
- return Security::permissionFailure($this);
-
- $record->doUnpublish();
-
- if(Director::is_ajax()) {
- return $this->edit($request);
- } else {
- Director::redirectBack();
- }
- }
-
- public function doDeleteItem($data, $form, $request)
- {
- $record = $this->currentRecord;
-
- if($record && !$record->canDelete())
- return Security::permissionFailure();
-
- //This extra line is needed to remove the records with this ID from the versions table.
- DB::query("DELETE FROM DataObjectAsPage_versions WHERE RecordID = '$record->ID'");
- $record->doDelete();
-
- if(Director::is_ajax()) {
- $this->edit($request);
- } else {
- Director::redirectBack();
- }
- }
-
- public function duplicate($data, $form, $request) {
-
- //Duplicate the object
- $Clone = $this->currentRecord->duplicate();
+ /*
+ * Temporary Fix for HTML editor Image/Link popup
+ */
+ public function EditorToolbar() {
+ return new ModelAdminHtmlEditorField_Toolbar($this, $this->name."/EditorToolbar");
+ }
+}
- //Set the view to be our new duplicate
- $this->currentRecord = $Clone;
-
- if(Director::is_ajax()) {
- return $this->edit($request);
- } else {
- Director::redirectBack();
- }
- }
-
- public function EditForm() {
- $form = parent::EditForm();
- $fields = $form->Actions();
- $fields->removeByName('action_doSave');
- $fields->removeByName('action_doDelete');
- $fields->removeByName('action_goForward');
- $fields->removeByName('action_goBack');
- $form->setActions ($fields);
- return $form;
- }
+/*
+ * Temporary Fix for HTML editor Image/Link popup
+ */
+class ModelAdminHtmlEditorField_Toolbar extends HtmlEditorField_Toolbar {
+
+ public function forTemplate() {
+ return sprintf(
+ '<div id="cms-editor-dialogs" data-url-linkform="%s" data-url-mediaform="%s"></div>',
+ Controller::join_links($this->controller->Link(), $this->name, 'LinkForm', 'forTemplate'),
+ Controller::join_links($this->controller->Link(), $this->name, 'MediaForm', 'forTemplate')
+ );
+ }
}
View
108 code/Pages/DataObjectAsPageHolder.php
@@ -29,60 +29,34 @@ public function getCMSFields()
}
/*
- * Produce the correct breadcrumb trail for use on the DataObject Item Page
- */
- public function ItemBreadcrumbs($Item, $Other = Null)
+ * Get Items which are to be displayed on this listing page
+ */
+ public function FetchItems($itemClass, $filter = '', $sort = Null, $joins = Null, $limit = Null)
{
- $Breadcrumbs = parent::Breadcrumbs($maxDepth = 20, $unlinked = false, $stopAtPageType = false, $showHidden = false);
-
- $Parts = explode(self::$breadcrumbs_delimiter, $Breadcrumbs);
-
- $NumOfParts = count($Parts);
-
- $Parts[$NumOfParts-1] = ("<a href=\"" . $this->Link() . "\">" . Convert::raw2xml($this->Title) . "</a>");
+ $results = $itemClass::get()->where($filter);
- if($Other)
+ if($joins)
{
- $Parts[$NumOfParts] = "<a href=\"" . $Item->Link() . "\">" . Convert::raw2xml($Item->Title) . "</a>";
- $Parts[] = Convert::raw2xml($Other);
+ foreach($joins as $type => $join)
+ {
+ if($results->hasMethod($type))
+ {
+ $results = $results->$type($table, $join);
+ }
+ }
}
- else
+
+ if($sort)
{
- $Parts[$NumOfParts] = Convert::raw2xml($Item->Title);
+ $results = $results->sort($sort);
}
-
- return implode(self::$breadcrumbs_delimiter, $Parts);
- }
-
- /*
- * Generate custom metatags to display on the DataObject Item page
- */
- public function ItemMetaTags($item = null)
- {
- $tags = parent::MetaTags(false);
-
- //explode to find each meta tag
- $tagArray = explode('<meta', $tags);
- for($i=0; $i<count($tagArray); $i++)
+
+ if($limit)
{
- //check if tag is a description: replace if it is
- if(strpos($tagArray[$i], 'name="description"'))
- {
- $tagArray[$i] = " name=\"description\" content=\"" . Convert::raw2att($item->MetaDescription) . "\" />\n";
- }
+ $results = $results->limit($limit);
}
- //rebuild string
- $tags = implode('<meta', $tagArray);
-
- return $tags;
- }
-
- /*
- * Get Items which are to be displayed on this listing page
- */
- public function FetchItems($ItemClass, $Filter = '', $Sort = Null, $Join = Null, $Limit = null)
- {
- return DataObject::get($ItemClass, $Filter, $Sort, $Join, $Limit);
+
+ return $results;
}
/*
@@ -134,32 +108,24 @@ class DataObjectAsPageHolder_Controller extends Page_Controller
/*
* Returns the items to list on this page pagintated or Limited
*/
- function Items($Limit = null)
+ function Items($limit = null)
{
- //Set Pagination if no limit set
- if(!$Limit && $this->Paginate)
- {
- //Pagination
- if(!isset($_GET['start']) || !is_numeric($_GET['start']) || (int)$_GET['start'] < 1){
- $_GET['start'] = 0;
- }
-
- $Offset = (int)$_GET['start'];
-
- $Limit = "{$Offset}, {$this->ItemsPerPage}" ;
- }
-
//Set custom filter
- $Where = ($this->hasMethod('getItemsWhere')) ? $this->getItemsWhere() : Null;
-
+ $where = ($this->hasMethod('getItemsWhere')) ? $this->getItemsWhere() : Null;
//Set custom sort
- $Sort = ($this->hasMethod('getItemsSort')) ? $this->getItemsSort() : $this->stat('item_sort');
-
+ $sort = ($this->hasMethod('getItemsSort')) ? $this->getItemsSort() : $this->stat('item_sort');
//Set custom join
- $Join = ($this->hasMethod('getItemsJoin')) ? $this->getItemsJoin() : Null;
+ $join = ($this->hasMethod('getItemsJoin')) ? $this->getItemsJoin() : Null;
//QUERY
- $items = $this->FetchItems($this->Stat('item_class'), $Where, $Sort, $Join, $Limit);
+ $items = $this->FetchItems($this->Stat('item_class'), $where, $sort, $join, $limit);
+
+ //Paginate the list
+ if(!$limit && $this->Paginate)
+ {
+ $items = new PaginatedList($items, $this->request);
+ $items->setPageLength($this->ItemsPerPage);
+ }
return $items;
}
@@ -170,17 +136,18 @@ function Items($Limit = null)
public function getCurrentItem($itemID = null)
{
$params = $this->request->allParams();
+ $class = $this->stat('item_class');
if($itemID)
{
- return DataObject::get_by_id($this->stat('item_class'), $itemID);
+ return $class::get()->byID($itemID);
}
elseif(isset($params['ID']))
{
//Sanitize
$URL = Convert::raw2sql($params['ID']);
- return DataObject::get_one($this->stat('item_class'), "URLSegment = '" . $URL . "'");
+ return $class::get()->filter("URLSegment", $URL)->first();
}
}
@@ -197,9 +164,8 @@ function show()
{
$data = array(
'Item' => $item,
- 'Breadcrumbs' => $this->ItemBreadcrumbs($item),
- 'MetaTitle' => $item->MetaTitle,
- 'MetaTags' => $this->ItemMetaTags($item),
+ 'Breadcrumbs' => $item->Breadcrumbs(),
+ 'MetaTags' => $item->MetaTags(),
'BackLink' => base64_decode($this->request->getVar('backlink'))
);
View
19 css/dataobjectaspageadmin.css
@@ -2,11 +2,10 @@ form p.checkbox {
margin: 0px;
}
.doapToolbar{
- border: 1px solid #ccc;
- background: #fff;
+ border-bottom: 1px solid rgba(201, 205, 206, 0.8);
margin-bottom: 15px;
height: 25px;
- padding: 5px;
+ padding: 10px 10px 15px;
}
.doapToolbar h3.doapTitle{
@@ -24,22 +23,12 @@ form p.checkbox {
font-weight: normal;
margin: 0;
}
- #right .doapToolbar p.doapViewLinks{
+ .doapToolbar p.doapViewLinks{
float: right;
padding: 0;
margin: 0;
}
.doapToolbar p.doapViewLinks a{
display: inline-block;
- border-radius: 4px;
- border: 1px solid #555;
- background: #999;
- color: #fff;
- padding: 3px 10px;
- margin: 0 5px;
- text-decoration: none;
- font-size: 14px;
- }
- .doapToolbar p.doapViewLinks a:hover{
- background: #aaa;
+ margin-right: 5px;
}
View
209 javascript/SiteTreeURLSegmentField_modeladmin.js
@@ -0,0 +1,209 @@
+(function($) {
+ $.entwine('ss', function($) {
+ /**
+ * Class: .field.urlsegment
+ *
+ * Input validation on the URLSegment field
+ */
+ $('.field.urlsegment:not(.readonly)').entwine({
+
+ /**
+ * Constructor: onmatch
+ */
+ onmatch : function() {
+ // Only initialize the field if it contains an editable field.
+ // This ensures we don't get bogus previews on readonly fields.
+ if(this.find(':text').length) {
+ this._addActions(); // add elements and actions for editing
+ this.edit(); // toggle
+ this._autoInputWidth(); // set width of input field
+ }
+
+ this._super();
+ },
+ onunmatch: function() {
+ this._super();
+ },
+
+ /**
+ * Function: edit
+ *
+ * Toggles the edit state of the field
+ *
+ * Return URLSegemnt val()
+ *
+ * Parameters:
+ * (Bool) auto (optional, triggers a second toggle)
+ */
+ edit: function(auto) {
+
+ var field = this.find(':text'),
+ holder = this.find('.preview'),
+ edit = this.find('.edit'),
+ update = this.find('.update'),
+ cancel = this.find('.cancel'),
+ help = this.find('.help');
+
+ // transfer current value to holder
+ holder.text(field.val());
+
+ // toggle elements
+ if (field.is(':visible')) {
+ update.hide();
+ cancel.hide();
+ field.hide();
+ holder.show();
+ edit.show();
+ help.hide();
+ } else {
+ edit.hide();
+ holder.hide();
+ field.show();
+ update.show();
+ cancel.show();
+ help.show();
+ }
+
+ // field updated from another fields value
+ // reset to original state
+ if (auto) this.edit();
+
+ return field.val();
+ },
+
+ /**
+ * Function: update
+ *
+ * Commits the change of the URLSegment to the field
+ * Optional: pass in (String)
+ * to update the URLSegment
+ */
+ update: function() {
+
+ var self = this,
+ field = this.find(':text'),
+ holder = this.find('.preview'),
+ currentVal = holder.text(),
+ updateVal,
+ title = arguments[0];
+
+ if (title && title !== "") {
+ updateVal = title;
+ } else {
+ updateVal = field.val();
+ }
+
+ if (currentVal != updateVal) {
+ self.addClass('loading');
+ self.suggest(updateVal, function(data) {
+ var newVal = decodeURIComponent(data.value);
+ field.val(newVal);
+ self.edit(title);
+ self.removeClass('loading');
+ });
+ } else {
+ self.edit();
+ }
+ },
+
+ /**
+ * Function: cancel
+ *
+ * Cancels any changes to the field
+ *
+ * Return URLSegemnt val()
+ *
+ */
+ cancel: function() {
+ var field = this.find(':text'),
+ holder = this.find('.preview');
+ field.val(holder.text());
+ this.edit();
+
+ return field.val();
+ },
+
+ /**
+ * Function: suggest
+ *
+ * Return a value matching the criteria.
+ *
+ * Parameters:
+ * (String) val
+ * (Function) callback
+ */
+ suggest: function(val, callback) {
+ var field = this.find(':text'), urlParts = $.path.parseUrl(this.closest('form').attr('action')),
+ url = urlParts.hrefNoSearch + '/field/' + field.attr('name') + '/suggest/?value=' + encodeURIComponent(val);
+ if(urlParts.search) url += '&' + urlParts.search.replace(/^\?/, '');
+
+ $.get(
+ url,
+ function(data) {callback.apply(this, arguments);}
+ );
+
+ },
+ _addActions: function() {
+ var self = this,