diff --git a/_config.php b/_config.php index 6e95a905..ca38bc80 100644 --- a/_config.php +++ b/_config.php @@ -29,13 +29,6 @@ Director::addRules(1, array( '$URLSegment//$Action/$ID/$OtherID' => 'ModelAsController', )); -// Register default side reports -SS_Report::register("SideReport", "SideReport_EmptyPages"); -SS_Report::register("SideReport", "SideReport_RecentlyEdited"); -if (class_exists('SubsiteReportWrapper')) SS_Report::register('ReportAdmin', 'SubsiteReportWrapper("BrokenLinksReport")',-20); -else SS_Report::register('ReportAdmin', 'BrokenLinksReport',-20); - - /** * Register the default internal shortcodes. */ diff --git a/code/controllers/CMSMain.php b/code/controllers/CMSMain.php index 15e4f280..5e941d6f 100644 --- a/code/controllers/CMSMain.php +++ b/code/controllers/CMSMain.php @@ -44,6 +44,9 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr 'SiteTreeAsUL', 'getshowdeletedsubtree', 'batchactions', + 'ListView', + 'getListView', + 'listchildren', ); public function init() { @@ -157,7 +160,6 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr function SearchForm() { // get all page types in a dropdown-compatible format $pageTypes = SiteTree::page_type_classes(); - array_unshift($pageTypes, _t('CMSMain.PAGETYPEANYOPT','Any')); $pageTypes = array_combine($pageTypes, $pageTypes); asort($pageTypes); @@ -456,6 +458,7 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr * @return Form */ public function getEditForm($id = null, $fields = null) { + if(!$id) $id = $this->currentPageID(); $form = parent::getEditForm($id); @@ -528,7 +531,6 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr } $this->extend('updateEditForm', $form); - return $form; } else if($id) { return new Form($this, "EditForm", new FieldList( @@ -537,6 +539,95 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr } } + /** + * Returns the pages meet a certain criteria as {@see CMSSiteTreeFilter} or the subpages of a parent page + * defaulting to no filter and show all pages in first level. + * Doubles as search results, if any search parameters are set through {@link SearchForm()}. + * + * @return SS_List + */ + public function getList(&$filterOn) { + $list = new DataList($this->stat('tree_class')); + + $request = $this->request; + $filter = null; + $ids = array(); + if($filterClass = $request->requestVar('FilterClass')){ + if(!is_subclass_of($filterClass, 'CMSSiteTreeFilter')) { + throw new Exception(sprintf('Invalid filter class passed: %s', $filterClass)); + } + $filter = new $filterClass($request->requestVars()); + $filterOn = true; + foreach($pages=$filter->pagesIncluded() as $pageMap){ + $ids[] = $pageMap['ID']; + } + if(count($ids)) $list->where('"'.$this->stat('tree_class').'"."ID" IN ('.implode(",", $ids).')'); + }else{ + $parentID = 0; + if($this->urlParams['Action'] == 'listchildren' && $this->urlParams['ID']){ + $parentID = $this->urlParams['ID']; + } + $list->filter("ParentID", $parentID); + } + + return $list; + } + + public function getListView(){ + $filterOn = false; + $list = $this->getList($filterOn); + $gridFieldConfig = GridFieldConfig::create()->addComponents( + new GridFieldSortableHeader(), + new GridFieldDataColumns(), + new GridFieldPaginator(15) + ); + $gridField = new GridField('Page','Pages', $list, $gridFieldConfig); + + if($filterOn){ + $gridField->setDisplayFields(array( + 'getTreeTitle' => _t('SiteTree.PAGETITLE', 'Page Title'), + 'Created' => _t('SiteTree.CREATED', 'Date Created'), + 'LastEdited' => _t('SiteTree.LASTUPDATED', 'Last Updated'), + )); + }else{ + $gridField->setDisplayFields(array( + 'listChildrenLink' => "", + 'getTreeTitle' => _t('SiteTree.PAGETITLE', 'Page Title'), + 'Created' => _t('SiteTree.CREATED', 'Date Created'), + 'LastEdited' => _t('SiteTree.LASTUPDATED', 'Last Updated'), + )); + } + + $gridField->setFieldCasting(array( + 'Created' => 'Date->Ago', + 'LastEdited' => 'Date->Ago', + )); + + $gridField->setFieldFormatting(array( + 'getTreeTitle' => '$value' + )); + + $listview = new Form( + $this, + 'ListView', + new FieldList($gridField), + new FieldList() + ); + + $this->extend('updateListView', $listview); + + $listview->disableSecurityToken(); + return $listview; + } + + public function getListViewHTML(){ + return $this->getListView()->forTemplate(); + } + + public function ListView() { + return $this->getListView(); + } + public function currentPageID() { $id = parent::currentPageID(); @@ -549,6 +640,14 @@ class CMSMain extends LeftAndMain implements CurrentPageIdentifier, PermissionPr return $id; } + + public function listchildren(){ + if(Director::is_ajax()){ + return $this->getListViewHTML(); + }else{ + return $this; + } + } //------------------------------------------------------------------------------------------// // Data saving handlers diff --git a/code/controllers/CMSSiteTreeFilter.php b/code/controllers/CMSSiteTreeFilter.php index d6c6bfaf..9df361e9 100644 --- a/code/controllers/CMSSiteTreeFilter.php +++ b/code/controllers/CMSSiteTreeFilter.php @@ -185,7 +185,7 @@ class CMSSiteTreeFilter_Search extends CMSSiteTreeFilter { switch($name) { // Match against URLSegment, Title, MenuTitle & Content case 'Term': - $where[] = "\"URLSegment\" LIKE '%$val%' OR \"Title\" LIKE '%$val%' OR \"MenuTitle\" LIKE '%$val%' OR \"Content\" LIKE '%$val%'"; + if($val) $where[] = "\"URLSegment\" LIKE '%$val%' OR \"Title\" LIKE '%$val%' OR \"MenuTitle\" LIKE '%$val%' OR \"Content\" LIKE '%$val%'"; break; // Match against date case 'LastEditedFrom': diff --git a/code/controllers/ContentController.php b/code/controllers/ContentController.php index 1e0099ae..a4a49205 100644 --- a/code/controllers/ContentController.php +++ b/code/controllers/ContentController.php @@ -458,7 +458,7 @@ HTML;     Email: $username
    Password: $password

-

For security reasons you should now delete the install files, unless you are planning to reinstall later (requires admin login, see above). The web server also now only needs write access to the "assets" folder, you can remove write access from all other folders. Click here to delete the install files.

+

For security reasons you should now delete the install files, unless you are planning to reinstall later (requires admin login, see above). The web server also now only needs write access to the "assets" folder, you can remove write access from all other folders. Click here to delete the install files.

HTML ); diff --git a/code/controllers/ReportAdmin.php b/code/controllers/ReportAdmin.php index 8e3f370e..1594eb5e 100644 --- a/code/controllers/ReportAdmin.php +++ b/code/controllers/ReportAdmin.php @@ -15,17 +15,34 @@ class ReportAdmin extends LeftAndMain implements PermissionProvider { static $url_segment = 'reports'; - static $url_rule = '/$Action/$ID'; + static $url_rule = '/$ReportClass/$Action'; static $menu_title = 'Reports'; static $template_path = null; // defaults to (project)/templates/email static $tree_class = 'SS_Report'; + + public static $url_handlers = array( + '$ReportClass/$Action' => 'handleAction' + ); + + /** + * Variable that describes which report we are currently viewing based on the URL (gets set in init method) + * @var String + */ + protected $reportClass; + + protected $reportObject; public function init() { parent::init(); + //set the report we are currently viewing from the URL + $this->reportClass = (isset($this->urlParams['ReportClass'])) ? $this->urlParams['ReportClass'] : null; + $allReports = SS_Report::get_reports(); + $this->reportObject = (isset($allReports[$this->reportClass])) ? $allReports[$this->reportClass] : null; + Requirements::css(CMS_DIR . '/css/screen.css'); // Set custom options for TinyMCE specific to ReportAdmin @@ -58,12 +75,6 @@ class ReportAdmin extends LeftAndMain implements PermissionProvider { return false; } - function currentPageID() { - $id = parent::currentPageID(); - $reports = SS_Report::get_reports('ReportAdmin'); - return (isset($reports[$id])) ? $reports[$id] : null; - } - /** * Return a SS_List of SS_Report subclasses * that are available for use. @@ -72,7 +83,7 @@ class ReportAdmin extends LeftAndMain implements PermissionProvider { */ public function Reports() { $output = new ArrayList(); - foreach(SS_Report::get_reports('ReportAdmin') as $report) { + foreach(SS_Report::get_reports() as $report) { if($report->canView()) $output->push($report); } return $output; @@ -90,12 +101,39 @@ class ReportAdmin extends LeftAndMain implements PermissionProvider { * @return boolean */ public static function has_reports() { - return sizeof(SS_Report::get_reports('ReportAdmin')) > 0; + return sizeof(SS_Report::get_reports()) > 0; } - - public function updatereport() { - // FormResponse::load_form($this->EditForm()->forTemplate()); - // return FormResponse::respond(); + + /** + * Returns the Breadcrumbs for the ReportAdmin + * @return ArrayList + */ + public function Breadcrumbs() { + $items = parent::Breadcrumbs(); + + // The root element should explicitly point to the root node. + // Uses session state for current record otherwise. + $items[0]->Link = singleton('ReportAdmin')->Link(); + + if ($this->reportObject) { + //build breadcrumb trail to the current report + $items->push(new ArrayData(array( + 'Title' => $this->reportObject->title(), + 'Link' => Controller::join_links($this->Link(), '?' . http_build_query(array('q' => $this->request->requestVar('q')))) + ))); + } + + return $items; + } + + /** + * Returns the link to the report admin section, or the specific report that is currently displayed + * @return String + */ + public function Link($action = null) { + $link = parent::Link($action); + if ($this->reportObject) $link = $this->reportObject->getLink($action); + return $link; } function providePermissions() { @@ -107,5 +145,74 @@ class ReportAdmin extends LeftAndMain implements PermissionProvider { ) ); } + + public function getEditForm($id = null, $fields = null) { + $fields = new FieldList(); + + $report = $this->reportObject; + + if($report) { + // List all reports + $gridFieldConfig = GridFieldConfig::create()->addComponents( + new GridFieldToolbarHeader(), + new GridFieldSortableHeader(), + new GridFieldDataColumns(), + new GridFieldPaginator(), + new GridFieldPrintButton(), + new GridFieldExportButton() + ); + $gridField = new GridField('Report',$report->title(), $report->sourceRecords(array(), null, null), $gridFieldConfig); + $displayFields = array(); + $fieldCasting = array(); + $fieldFormatting = array(); + + // Parse the column information + foreach($report->columns() as $source => $info) { + if(is_string($info)) $info = array('title' => $info); + + if(isset($info['formatting'])) $fieldFormatting[$source] = $info['formatting']; + if(isset($info['csvFormatting'])) $csvFieldFormatting[$source] = $info['csvFormatting']; + if(isset($info['casting'])) $fieldCasting[$source] = $info['casting']; + + if(isset($info['link']) && $info['link']) { + $link = singleton('CMSPageEditController')->Link('show'); + $fieldFormatting[$source] = '$value'; + } + + $displayFields[$source] = isset($info['title']) ? $info['title'] : $source; + } + $gridField->setDisplayFields($displayFields); + $gridField->setFieldCasting($fieldCasting); + $gridField->setFieldFormatting($fieldFormatting); + + $fields->push($gridField); + } else { + // List all reports + $gridFieldConfig = GridFieldConfig::create()->addComponents( + new GridFieldToolbarHeader(), + new GridFieldSortableHeader(), + new GridFieldDataColumns(), + new GridFieldPaginator() + ); + $gridField = new GridField('Reports','Reports', $this->Reports(), $gridFieldConfig); + $gridField->setDisplayFields(array( + 'title' => 'Title', + 'description' => 'Description' + )); + $gridField->setFieldFormatting(array( + 'title' => '$value' + )); + $gridField->addExtraClass('all-reports-gridfield'); + $fields->push($gridField); + } + + $actions = new FieldList(); + $form = new Form($this, "EditForm", $fields, $actions); + $form->addExtraClass('cms-edit-form cms-panel-padded center ' . $this->BaseCSSClasses()); + + $this->extend('updateEditForm', $form); + + return $form; + } } diff --git a/code/model/SiteTree.php b/code/model/SiteTree.php index 0222b107..6a6862c9 100644 --- a/code/model/SiteTree.php +++ b/code/model/SiteTree.php @@ -2475,6 +2475,13 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid Deprecation::notice('3.0', 'Use getTreeTitle() instead.'); return $this->getTreeTitle(); } + + function listChildrenLink(){ + if($num = $this->numChildren()){ + $link = singleton('CMSPagesController')->Link('listchildren')."/".$this->ID; + return ''.$num.''; + } + } /** * getTreeTitle will return three html DOM elements, an empty with diff --git a/code/reports/Report.php b/code/reports/Report.php index 57182c4d..2226396f 100644 --- a/code/reports/Report.php +++ b/code/reports/Report.php @@ -35,19 +35,13 @@ * Showing reports to the user * =========================== * - * Right now, all subclasses of SS_Report will be shown in the ReportAdmin. However, we are planning - * on adding an explicit registration mechanism, so that you can decide which reports go in the - * report admin, and which go elsewhere (such as the side panel in the CMS). + * Right now, all subclasses of SS_Report will be shown in the ReportAdmin. In SS3 there is only + * one place where reports can go, so this class is greatly simplifed from from its version in SS2. * * @package cms * @subpackage reports */ class SS_Report extends ViewableData { - /** - * Report registry populated by {@link SS_Report::register()} - */ - private static $registered_reports = array(); - /** * This is the title of the report, * used by the ReportAdmin templates. @@ -70,6 +64,22 @@ class SS_Report extends ViewableData { * Set by overriding in your subclass. */ protected $dataClass = 'SiteTree'; + + /** + * A field that specifies the sort order of this report + * @var int + */ + protected $sort = 0; + + /** + * Reports which should not be collected and returned in get_reports + * @var array + */ + public static $excluded_reports = array( + 'SS_Report', + 'SS_ReportWrapper', + 'SideReportWrapper' + ); /** * Return the title of this report. @@ -93,22 +103,12 @@ class SS_Report extends ViewableData { return $this->description; } - /** - * Return a FieldList specifying the search criteria for this report. - * - * Override this method to define search criteria. - */ - function parameterFields() { - return null; - } - /** * Return the {@link SQLQuery} that provides your report data. */ function sourceQuery($params) { if($this->hasMethod('sourceRecords')) { $query = new SS_Report_FakeQuery($this, 'sourceRecords', $params); - $query->setSortColumnMethod('sortColumns'); return $query; } else { user_error("Please override sourceQuery()/sourceRecords() and columns() or, if necessary, override getReportField()", E_USER_ERROR); @@ -126,29 +126,7 @@ class SS_Report extends ViewableData { } } - /** - * Return an map of columns for your report. - * - The map keys will be the source columns for your report (in TableListField dot syntax) - * - The values can either be a string (the column title), or a map containing the following - * column parameters: - * - title: The column title - * - formatting: A formatting string passed to {@link TableListField::setFieldFormatting()} - */ - function columns() { - user_error("Please override sourceQuery() and columns() or, if necessary, override getReportField()", E_USER_ERROR); - } - - function sortColumns() { - return array_keys($this->columns()); - } - - /** - * Return the number of records in this report with no filters applied. - */ - function count() { - return (int)$this->sourceQuery(array())->unlimitedRowCount(); - } - + /** * Return the data class for this report */ @@ -156,6 +134,93 @@ class SS_Report extends ViewableData { return $this->dataClass; } + function getLink($action = null) { + return Controller::join_links( + 'admin/reports/', + "$this->class", + '/', // trailing slash needed if $action is null! + "$action" + ); + } + + + + /** + * @deprecated 3.0 + * All subclasses of SS_Report now appear in the report admin, no need to register or unregister. + * + * Register a report. + * @param $list The list to add the report to: "ReportAdmin" or "SideReports" + * @param $reportClass The class of the report to add. + * @param $priority The priority. Higher numbers will appear furhter up in the reports list. + * The default value is zero. + */ + static function register($list, $reportClass, $priority = 0) { + Deprecation::notice('3.0', 'All subclasses of SS_Report now appear in the report admin, no need to register'); + } + + /** + * @deprecated 3.0 + * All subclasses of SS_Report now appear in the report admin, no need to register or unregister. + */ + static function unregister($list, $reportClass) { + self::add_excluded_reports($reportClass); + } + + /** + * Exclude certain reports classes from the list of Reports in the CMS + * @param $reportClass Can be either a string with the report classname or an array of reports classnames + */ + static function add_excluded_reports($reportClass) { + if (is_array($reportClass)) { + self::$excluded_reports = array_merge(self::$excluded_reports, $reportClass); + } else { + if (is_string($reportClass)) { + //add to the excluded reports, so this report doesn't get used + self::$excluded_reports[] = $reportClass; + } + } + } + + + /** + * Return an array of excluded reports. That is, reports that will not be included in + * the list of reports in report admin in the CMS. + * @return array + */ + static function get_excluded_reports() { + return self::$excluded_reports; + } + + /** + * Return the SS_Report objects making up the given list. + * @return ArrayList an arraylist of SS_Report objects + */ + static function get_reports() { + $reports = ClassInfo::subclassesFor(get_called_class()); + + $reportsArray = array(); + if ($reports && count($reports) > 0) { + //collect reports into array with an attribute for 'sort' + foreach($reports as $report) { + if (in_array($report, self::$excluded_reports)) continue; //don't use the SS_Report superclass + $reportObj = new $report; + if (method_exists($reportObj,'sort')) $reportObj->sort = $reportObj->sort(); //use the sort method to specify the sort field + $reportsArray[$report] = $reportObj; + } + } + + //convert array into ArrayList + $list = ArrayList::create($reportsArray); + + //sort + $list->sort('sort'); + + return $list; + } + + /////////////////////// UI METHODS /////////////////////// + /** * Returns a FieldList with which to create the CMS editing form. @@ -273,72 +338,10 @@ class SS_Report extends ViewableData { * @return string */ function TreeTitle() { - return $this->title();/* . ' (' . $this->count() . ')'; - this is too slow atm */ + return $this->title(); } - /** - * Return the ID of this Report class. - * Because it doesn't have a number, we - * use the class name as the ID. - * - * @return string - */ - function ID() { - return $this->class; - } - - ///////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Register a report. - * @param $list The list to add the report to: "ReportAdmin" or "SideReports" - * @param $reportClass The class of the report to add. - * @param $priority The priority. Higher numbers will appear furhter up in the reports list. - * The default value is zero. - */ - static function register($list, $reportClass, $priority = 0) { - if(strpos($reportClass, '(') === false && (!class_exists($reportClass) || !is_subclass_of($reportClass,'SS_Report'))) { - user_error("SS_Report::register(): '$reportClass' is not a subclass of SS_Report", E_USER_WARNING); - return; - } - - self::$registered_reports[$list][$reportClass] = $priority; - } - - /** - * Unregister a report, removing it from the list - */ - static function unregister($list, $reportClass) { - unset(self::$registered_reports[$list][$reportClass]); - } - - /** - * Return the SS_Report objects making up the given list. - * @return An array of SS_Report objects - */ - static function get_reports($list) { - $output = array(); - if(isset(self::$registered_reports[$list])) { - $listItems = self::$registered_reports[$list]; - // Sort by priority, preserving internal order of items with the same priority - $groupedItems = array(); - foreach($listItems as $k => $v) { - $groupedItems[$v][] = $k; - } - krsort($groupedItems); - $sortedListItems = call_user_func_array('array_merge', $groupedItems); - - foreach($sortedListItems as $report) { - if(strpos($report,'(') === false) $reportObj = new $report; - else $reportObj = eval("return new $report;"); - - $output[$reportObj->ID()] = $reportObj; - } - } - - return $output; - } } @@ -370,7 +373,7 @@ class SS_Report_FakeQuery extends SQLQuery { $this->method = $method; $this->params = $params; } - + /** * Provide a method that will return a list of columns that can be used to sort. */ diff --git a/javascript/CMSMain.js b/javascript/CMSMain.js index 36abf2ac..1e5bfd8b 100644 --- a/javascript/CMSMain.js +++ b/javascript/CMSMain.js @@ -3,6 +3,22 @@ */ (function($) { $.entwine('ss', function($){ + + $('#pages-controller-cms-content').entwine({ + /** + * we need to check if the current url contains a sub url 'listchildren' and + * select its list view if it does, otherwise use the default tabs() call which is + * using cookie options + */ + redrawTabs: function() { + if(window.location.href.match(/listchildren/)){ + this.rewriteHashlinks(); + this.tabs({ selected: 1 }); + }else{ + this._super(); + } + } + }); /** * Class: #Form_SearchForm @@ -48,8 +64,10 @@ //this.find(':submit').attr('disabled', true); this.find(':submit[name=action_doSearchTree]').addClass('loading'); - - this._reloadSitetree(this.serializeArray()); + + var params = this.serializeArray(); + this._reloadSitetree(params); + this._reloadListview(params); return false; }, @@ -64,8 +82,11 @@ // TODO Enable checkbox tree controls this.find('.checkboxAboveTree :checkbox').attr('disabled', 'false'); + this.resetForm(); + //the dropdown field wont be reset due to it is applied to chosen.js so need to treated specially + this.find('.field.dropdown select').val('').trigger("liszt:updated"); this._reloadSitetree(); - + this._reloadListview(); return false; }, @@ -87,6 +108,45 @@ errorMessage('Could not filter site tree
' + response.responseText); } ); + }, + + _reloadListview: function(params){ + $('.cms-list').refresh(params); + + } + }); + + $('#cms-content-listview .cms-list').entwine({ + refresh: function(params){ + var self = this; + + $.ajax({ + url: this.data('url-list'), + data: params, + success: function(data, status, xhr) { + self.html(data); + }, + error: function(xhr, status, e) { + errorMessage(e); + } + }); + }, + replace: function(url){ + if(window.History.enabled) { + var container = $('.cms-container') + container.loadPanel(url, '', {selector: '.cms-list form'}); + } else { + window.location = $.path.makeUrlAbsolute(url, $('base').attr('href')); + } + } + }); + + $('.cms-list .list-children-link').entwine({ + onclick: function(e) { + this.closest('.cms-list').replace(this.attr('href')); + e.preventDefault(); + return false; + } }); diff --git a/templates/Includes/CMSPagesController_Content.ss b/templates/Includes/CMSPagesController_Content.ss index 9d0b09c9..f8341c53 100644 --- a/templates/Includes/CMSPagesController_Content.ss +++ b/templates/Includes/CMSPagesController_Content.ss @@ -1,4 +1,4 @@ -