Вход Регистрация
Файл: cms/code/controllers/CMSMain.php
Строк: 1747
<?php
/**
 * The main "content" area of the CMS.
 *
 * This class creates a 2-frame layout - left-tree and right-form - to sit beneath the main
 * admin menu.
 *
 * @package cms
 * @subpackage controller
 * @todo Create some base classes to contain the generic functionality that will be replicated.
 */
class CMSMain extends LeftAndMain implements CurrentPageIdentifierPermissionProvider {

    private static 
$url_segment 'pages';

    private static 
$url_rule '/$Action/$ID/$OtherID';

    
// Maintain a lower priority than other administration sections
    // so that Director does not think they are actions of CMSMain
    
private static $url_priority 39;

    private static 
$menu_title 'Edit Page';

    private static 
$menu_priority 10;

    private static 
$tree_class "SiteTree";

    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(
        
'archive',
        
'buildbrokenlinks',
        
'deleteitems',
        
'DeleteItemsForm',
        
'dialog',
        
'duplicate',
        
'duplicatewithchildren',
        
'publishall',
        
'publishitems',
        
'PublishItemsForm',
        
'submit',
        
'EditForm',
        
'SearchForm',
        
'SiteTreeAsUL',
        
'getshowdeletedsubtree',
        
'batchactions',
        
'treeview',
        
'listview',
        
'ListViewForm',
        
'childfilter',
    );

    
/**
     * Enable legacy batch actions.
     * @deprecated since version 4.0
     * @var array
     * @config
     */
    
private static $enabled_legacy_actions = array();

    public function 
init() {
        
// set reading lang
        
if(SiteTree::has_extension('Translatable') && !$this->getRequest()->isAjax()) {
            
Translatable::choose_site_locale(array_keys(Translatable::get_existing_content_languages('SiteTree')));
        }

        
parent::init();

        
Versioned::reading_stage("Stage");

        
Requirements::css(CMS_DIR '/css/screen.css');
        
Requirements::customCSS($this->generatePageIconsCss());

        
Requirements::combine_files(
            
'cmsmain.js',
            
array_merge(
                array(
                    
CMS_DIR '/javascript/CMSMain.js',
                    
CMS_DIR '/javascript/CMSMain.EditForm.js',
                    
CMS_DIR '/javascript/CMSMain.AddForm.js',
                    
CMS_DIR '/javascript/CMSPageHistoryController.js',
                    
CMS_DIR '/javascript/CMSMain.Tree.js',
                    
CMS_DIR '/javascript/SilverStripeNavigator.js',
                    
CMS_DIR '/javascript/SiteTreeURLSegmentField.js'
                
),
                
Requirements::add_i18n_javascript(CMS_DIR '/javascript/lang'truetrue)
            )
        );

        
CMSBatchActionHandler::register('publish''CMSBatchAction_Publish');
        
CMSBatchActionHandler::register('unpublish''CMSBatchAction_Unpublish');


        
// Check legacy actions
        
$legacy $this->config()->enabled_legacy_actions;

        
// Delete from live is unnecessary since we have unpublish which does the same thing
        
if(in_array('CMSBatchAction_DeleteFromLive'$legacy)) {
            
Deprecation::notice('4.0''Delete From Live is deprecated. Use Un-publish instead');
            
CMSBatchActionHandler::register('deletefromlive''CMSBatchAction_DeleteFromLive');
        }

        
// Delete action
        
if(in_array('CMSBatchAction_Delete'$legacy)) {
            
Deprecation::notice('4.0''Delete from Stage is deprecated. Use Archive instead.');
            
CMSBatchActionHandler::register('delete''CMSBatchAction_Delete');
        } else {
            
CMSBatchActionHandler::register('archive''CMSBatchAction_Archive');
            
CMSBatchActionHandler::register('restore''CMSBatchAction_Restore');
        }
    }

    public function 
index($request) {
        
// In case we're not showing a specific record, explicitly remove any session state,
        // to avoid it being highlighted in the tree, and causing an edit form to show.
        
if(!$request->param('Action')) $this->setCurrentPageId(null);

        return 
parent::index($request);
    }

    public function 
getResponseNegotiator() {
        
$negotiator parent::getResponseNegotiator();
        
$controller $this;
        
$negotiator->setCallback('ListViewForm', function() use(&$controller) {
            return 
$controller->ListViewForm()->forTemplate();
        });
        return 
$negotiator;
    }

    
/**
     * If this is set to true, the "switchView" context in the
     * template is shown, with links to the staging and publish site.
     *
     * @return boolean
     */
    
public function ShowSwitchView() {
        return 
true;
    }

    
/**
     * Overloads the LeftAndMain::ShowView. Allows to pass a page as a parameter, so we are able
     * to switch view also for archived versions.
     */
    
public function SwitchView($page null) {
        if(!
$page) {
            
$page $this->currentPage();
        }

        if(
$page) {
            
$nav SilverStripeNavigator::get_for_record($page);
            return 
$nav['items'];
        }
    }

    
//------------------------------------------------------------------------------------------//
    // Main controllers

    //------------------------------------------------------------------------------------------//
    // Main UI components

    /**
     * Override {@link LeftAndMain} Link to allow blank URL segment for CMSMain.
     *
     * @param string|null $action Action to link to.
     * @return string
     */
    
public function Link($action null) {
        
$link Controller::join_links(
            
$this->stat('url_base'true),
            
$this->stat('url_segment'true), // in case we want to change the segment
            
'/'// trailing slash needed if $action is null!
            
"$action"
        
);
        
$this->extend('updateLink'$link);
        return 
$link;
    }

    public function 
LinkPages() {
        return 
singleton('CMSPagesController')->Link();
    }

    public function 
LinkPagesWithSearch() {
        return 
$this->LinkWithSearch($this->LinkPages());
    }

    public function 
LinkTreeView() {
        return 
$this->LinkWithSearch(singleton('CMSMain')->Link('treeview'));
    }

    public function 
LinkListView() {
        return 
$this->LinkWithSearch(singleton('CMSMain')->Link('listview'));
    }

    public function 
LinkGalleryView() {
        return 
$this->LinkWithSearch(singleton('CMSMain')->Link('galleryview'));
    }

    public function 
LinkPageEdit($id null) {
        if(!
$id$id $this->currentPageID();
        return 
$this->LinkWithSearch(
            
Controller::join_links(singleton('CMSPageEditController')->Link('show'), $id)
        );
    }

    public function 
LinkPageSettings() {
        if(
$id $this->currentPageID()) {
            return 
$this->LinkWithSearch(
                
Controller::join_links(singleton('CMSPageSettingsController')->Link('show'), $id)
            );
        }
    }

    public function 
LinkPageHistory() {
        if(
$id $this->currentPageID()) {
            return 
$this->LinkWithSearch(
                
Controller::join_links(singleton('CMSPageHistoryController')->Link('show'), $id)
            );
        }
    }

    public function 
LinkWithSearch($link) {
        
// Whitelist to avoid side effects
        
$params = array(
            
'q' => (array)$this->getRequest()->getVar('q'),
            
'ParentID' => $this->getRequest()->getVar('ParentID')
        );
        
$link Controller::join_links(
            
$link,
            
array_filter(array_values($params)) ? '?' http_build_query($params) : null
        
);
        
$this->extend('updateLinkWithSearch'$link);
        return 
$link;
    }

    public function 
LinkPageAdd($extra null$placeholders null) {
        
$link singleton("CMSPageAddController")->Link();
        
$this->extend('updateLinkPageAdd'$link);

        if(
$extra) {
            
$link Controller::join_links ($link$extra);
        }

        if(
$placeholders) {
            
$link .= (strpos($link'?') === false "?$placeholders"&amp;$placeholders");
        }

        return 
$link;
    }

    
/**
     * @return string
     */
    
public function LinkPreview() {
        
$record $this->getRecord($this->currentPageID());
        
$baseLink Director::absoluteBaseURL();
        if (
$record && $record instanceof Page) {
            
// if we are an external redirector don't show a link
            
if ($record instanceof RedirectorPage && $record->RedirectionType == 'External') {
                
$baseLink false;
            }
            else {
                
$baseLink $record->Link('?stage=Stage');
            }
        }
        return 
$baseLink;
    }

    
/**
     * Return the entire site tree as a nested set of ULs
     */
    
public function SiteTreeAsUL() {
        
// Pre-cache sitetree version numbers for querying efficiency
        
Versioned::prepopulate_versionnumber_cache("SiteTree""Stage");
        
Versioned::prepopulate_versionnumber_cache("SiteTree""Live");
        
$html $this->getSiteTreeFor($this->stat('tree_class'));

        
$this->extend('updateSiteTreeAsUL'$html);

        return 
$html;
    }

    
/**
     * @return boolean
     */
    
public function TreeIsFiltered() {
        
$query $this->getRequest()->getVar('q');

        if (!
$query || (count($query) === && isset($query['FilterClass']) && $query['FilterClass'] === 'CMSSiteTreeFilter_Search')) {
            return 
false;
        }

        return 
true;
    }

    public function 
ExtraTreeTools() {
        
$html '';
        
$this->extend('updateExtraTreeTools'$html);
        return 
$html;
    }

    
/**
     * Returns a Form for page searching for use in templates.
     *
     * Can be modified from a decorator by a 'updateSearchForm' method
     *
     * @return Form
     */
    
public function SearchForm() {
        
// Create the fields
        
$content = new TextField('q[Term]'_t('CMSSearch.FILTERLABELTEXT''Search'));
        
$dateHeader = new HeaderField('q[Date]'_t('CMSSearch.PAGEFILTERDATEHEADING''Last edited'), 4);
        
$dateFrom = new DateField(
            
'q[LastEditedFrom]',
            
_t('CMSSearch.FILTERDATEFROM''From')
        );
        
$dateFrom->setConfig('showcalendar'true);
        
$dateTo = new DateField(
            
'q[LastEditedTo]',
            
_t('CMSSearch.FILTERDATETO''To')
        );
        
$dateTo->setConfig('showcalendar'true);
        
$pageFilter = new DropdownField(
            
'q[FilterClass]',
            
_t('CMSMain.PAGES''Page status'),
            
CMSSiteTreeFilter::get_all_filters()
        );
        
$pageClasses = new DropdownField(
            
'q[ClassName]',
            
_t('CMSMain.PAGETYPEOPT''Page type''Dropdown for limiting search to a page type'),
            
$this->getPageTypes()
        );
        
$pageClasses->setEmptyString(_t('CMSMain.PAGETYPEANYOPT','Any'));

        
// Group the Datefields
        
$dateGroup = new FieldGroup(
            
$dateHeader,
            
$dateFrom,
            
$dateTo
        
);
        
$dateGroup->setFieldHolderTemplate('FieldGroup_DefaultFieldHolder')->addExtraClass('stacked');

        
// Create the Field list
        
$fields = new FieldList(
            
$content,
            
$dateGroup,
            
$pageFilter,
            
$pageClasses
        
);

        
// Create the Search and Reset action
        
$actions = new FieldList(
            
FormAction::create('doSearch',  _t('CMSMain_left_ss.APPLY_FILTER''Search'))
                ->
addExtraClass('ss-ui-action-constructive'),
            
Object::create('ResetFormAction''clear'_t('CMSMain_left_ss.CLEAR_FILTER''Clear'))
        );

        
// Use <button> to allow full jQuery UI styling on the all of the Actions
        
foreach($actions->dataFields() as $action) {
            
$action->setUseButtonTag(true);
        }

        
// Create the form
        
$form Form::create($this'SearchForm'$fields$actions)
            ->
addExtraClass('cms-search-form')
            ->
setFormMethod('GET')
            ->
setFormAction($this->Link())
            ->
disableSecurityToken()
            ->
unsetValidator();

        
// Load the form with previously sent search data
        
$form->loadDataFrom($this->getRequest()->getVars());

        
// Allow decorators to modify the form
        
$this->extend('updateSearchForm'$form);

        return 
$form;
    }

    
/**
     * Returns a sorted array suitable for a dropdown with pagetypes and their translated name
     *
     * @return array
     */
    
protected function getPageTypes() {
        
$pageTypes = array();
        foreach(
SiteTree::page_type_classes() as $pageTypeClass) {
            
$pageTypes[$pageTypeClass] = _t($pageTypeClass.'.SINGULARNAME'$pageTypeClass);
        }
        
asort($pageTypes);
        return 
$pageTypes;
    }

    public function 
doSearch($data$form) {
        return 
$this->getsubtree($this->getRequest());
    }

    
/**
     * @param bool $unlinked
     * @return ArrayList
     */
    
public function Breadcrumbs($unlinked false) {
        
$items parent::Breadcrumbs($unlinked);

        if(
$items->count() > 1) {
            
// Specific to the SiteTree admin section, we never show the cms section and current
            // page in the same breadcrumbs block.
            
$items->shift();
        }

        return 
$items;
    }

    
/**
     * Create serialized JSON string with site tree hints data to be injected into
     * 'data-hints' attribute of root node of jsTree.
     *
     * @return string Serialized JSON
     */
    
public function SiteTreeHints() {
        
$json '';
        
$classes SiteTree::page_type_classes();

         
$cacheCanCreate = array();
         foreach(
$classes as $class$cacheCanCreate[$class] = singleton($class)->canCreate();

         
// Generate basic cache key. Too complex to encompass all variations
         
$cache SS_Cache::factory('CMSMain_SiteTreeHints');
         
$cacheKey md5(implode('_', array(Member::currentUserID(), implode(','$cacheCanCreate), implode(','$classes))));
         if(
$this->getRequest()->getVar('flush')) $cache->clean(Zend_Cache::CLEANING_MODE_ALL);
         
$json $cache->load($cacheKey);
         if(!
$json) {
            
$def['Root'] = array();
            
$def['Root']['disallowedChildren'] = array();

            
// Contains all possible classes to support UI controls listing them all,
            // such as the "add page here" context menu.
            
$def['All'] = array();

            
// Identify disallows and set globals
            
foreach($classes as $class) {
                
$obj singleton($class);
                if(
$obj instanceof HiddenClass) continue;

                
// Name item
                
$def['All'][$class] = array(
                    
'title' => $obj->i18n_singular_name()
                );

                
// Check if can be created at the root
                
$needsPerm $obj->stat('need_permission');
                if(
                    !
$obj->stat('can_be_root')
                    || (!
array_key_exists($class$cacheCanCreate) || !$cacheCanCreate[$class])
                    || (
$needsPerm && !$this->can($needsPerm))
                ) {
                    
$def['Root']['disallowedChildren'][] = $class;
                }

                
// Hint data specific to the class
                
$def[$class] = array();

                
$defaultChild $obj->defaultChild();
                if(
$defaultChild !== 'Page' && $defaultChild !== null) {
                    
$def[$class]['defaultChild'] = $defaultChild;
                }

                
$defaultParent $obj->defaultParent();
                if (
$defaultParent !== && $defaultParent !== null) {
                    
$def[$class]['defaultParent'] = $defaultParent;
                }
            }

            
$this->extend('updateSiteTreeHints'$def);

            
$json Convert::raw2json($def);
            
$cache->save($json$cacheKey);
        }
        return 
$json;
    }

    
/**
     * Populates an array of classes in the CMS
     * which allows the user to change the page type.
     *
     * @return SS_List
     */
    
public function PageTypes() {
        
$classes SiteTree::page_type_classes();

        
$result = new ArrayList();

        foreach(
$classes as $class) {
            
$instance singleton($class);

            if(
$instance instanceof HiddenClass) continue;

            
// skip this type if it is restricted
            
if($instance->stat('need_permission') && !$this->can(singleton($class)->stat('need_permission'))) continue;

            
$addAction $instance->i18n_singular_name();

            
// Get description (convert 'Page' to 'SiteTree' for correct localization lookups)
            
$description _t((($class == 'Page') ? 'SiteTree' $class) . '.DESCRIPTION');

            if(!
$description) {
                
$description $instance->uninherited('description');
            }

            if(
$class == 'Page' && !$description) {
                
$description singleton('SiteTree')->uninherited('description');
            }

            
$result->push(new ArrayData(array(
                
'ClassName' => $class,
                
'AddAction' => $addAction,
                
'Description' => $description,
                
// TODO Sprite support
                
'IconURL' => $instance->stat('icon'),
                
'Title' => singleton($class)->i18n_singular_name(),
            )));
        }

        
$result $result->sort('AddAction');

        return 
$result;
    }

    
/**
     * Get a database record to be managed by the CMS.
     *
     * @param int $id Record ID
     * @param int $versionID optional Version id of the given record
     * @return DataObject
     */
     
public function getRecord($id$versionID null) {
        
$treeClass $this->stat('tree_class');

        if(
$id instanceof $treeClass) {
            return 
$id;
        }
        else if(
$id && is_numeric($id)) {
            if(
$this->getRequest()->getVar('Version')) {
                
$versionID = (int) $this->getRequest()->getVar('Version');
            }

            if(
$versionID) {
                
$record Versioned::get_version($treeClass$id$versionID);
            } else {
                
$record DataObject::get_by_id($treeClass$id);
            }

            
// Then, try getting a record from the live site
            
if(!$record) {
                
// $record = Versioned::get_one_by_stage($treeClass, "Live", ""$treeClass"."ID" = $id");
                
Versioned::reading_stage('Live');
                
singleton($treeClass)->flushCache();

                
$record DataObject::get_by_id($treeClass$id);
                if(
$recordVersioned::set_reading_mode('');
            }

            
// Then, try getting a deleted record
            
if(!$record) {
                
$record Versioned::get_latest_version($treeClass$id);
            }

            
// Don't open a page from a different locale
            /** The record's Locale is saved in database in 2.4, and not related with Session,
             *  we should not check their locale matches the Translatable::get_current_locale,
             *     here as long as we all the HTTPRequest is init with right locale.
             *    This bit breaks the all FileIFrameField functions if the field is used in CMS
             *  and its relevent ajax calles, like loading the tree dropdown for TreeSelectorField.
             */
            /* if($record && SiteTree::has_extension('Translatable') && $record->Locale && $record->Locale != Translatable::get_current_locale()) {
                $record = null;
            }*/

            
return $record;

        } else if(
substr($id,0,3) == 'new') {
            return 
$this->getNewItem($id);
        }
    }

    
/**
     * @param int $id
     * @param FieldList $fields
     * @return CMSForm
     */
    
public function getEditForm($id null$fields null) {

        if(!
$id$id $this->currentPageID();
        
$form parent::getEditForm($id);

        
// TODO Duplicate record fetching (see parent implementation)
        
$record $this->getRecord($id);
        if(
$record && !$record->canView()) return Security::permissionFailure($this);

        if(!
$fields$fields $form->Fields();
        
$actions $form->Actions();

        if(
$record) {
            
$deletedFromStage $record->getIsDeletedFromStage();
            
$deleteFromLive = !$record->getExistsOnLive();

            
$fields->push($idField = new HiddenField("ID"false$id));
            
// Necessary for different subsites
            
$fields->push($liveLinkField = new HiddenField("AbsoluteLink"false$record->AbsoluteLink()));
            
$fields->push($liveLinkField = new HiddenField("LiveLink"));
            
$fields->push($stageLinkField = new HiddenField("StageLink"));
            
$fields->push(new HiddenField("TreeTitle"false$record->TreeTitle));

            if(
$record->ID && is_numeric$record->ID ) ) {
                
$liveLink $record->getAbsoluteLiveLink();
                if(
$liveLink$liveLinkField->setValue($liveLink);
                if(!
$deletedFromStage) {
                    
$stageLink Controller::join_links($record->AbsoluteLink(), '?stage=Stage');
                    if(
$stageLink$stageLinkField->setValue($stageLink);
                }
            }

            
// Added in-line to the form, but plucked into different view by LeftAndMain.Preview.js upon load
            
if(in_array('CMSPreviewable'class_implements($record)) && !$fields->fieldByName('SilverStripeNavigator')) {
                
$navField = new LiteralField('SilverStripeNavigator'$this->getSilverStripeNavigator());
                
$navField->setAllowHTML(true);
                
$fields->push($navField);
            }

            
// getAllCMSActions can be used to completely redefine the action list
            
if($record->hasMethod('getAllCMSActions')) {
                
$actions $record->getAllCMSActions();
            } else {
                
$actions $record->getCMSActions();

                
// Find and remove action menus that have no actions.
                
if ($actions && $actions->Count()) {
                    
$tabset $actions->fieldByName('ActionMenus');
                    if (
$tabset) {
                        foreach (
$tabset->getChildren() as $tab) {
                            if (!
$tab->getChildren()->count()) {
                                
$tabset->removeByName($tab->getName());
                            }
                        }
                    }
                }
            }

            
// Use <button> to allow full jQuery UI styling
            
$actionsFlattened $actions->dataFields();
            if(
$actionsFlattened) foreach($actionsFlattened as $action$action->setUseButtonTag(true);

            if(
$record->hasMethod('getCMSValidator')) {
                
$validator $record->getCMSValidator();
            } else {
                
$validator = new RequiredFields();
            }

            
$form CMSForm::create(
                
$this"EditForm"$fields$actions$validator
            
)->setHTMLID('Form_EditForm');
            
$form->setResponseNegotiator($this->getResponseNegotiator());
            
$form->loadDataFrom($record);
            
$form->disableDefaultAction();
            
$form->addExtraClass('cms-edit-form');
            
$form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));
            
// TODO Can't merge $FormAttributes in template at the moment
            
$form->addExtraClass('center ' $this->BaseCSSClasses());
            
// if($form->Fields()->hasTabset()) $form->Fields()->findOrMakeTab('Root')->setTemplate('CMSTabSet');
            
$form->setAttribute('data-pjax-fragment''CurrentForm');
            
// Set validation exemptions for specific actions
            
$form->setValidationExemptActions(array('restore''revert''deletefromlive''delete''unpublish''rollback'));

            
// Announce the capability so the frontend can decide whether to allow preview or not.
            
if(in_array('CMSPreviewable'class_implements($record))) {
                
$form->addExtraClass('cms-previewable');
            }

            if(!
$record->canEdit() || $deletedFromStage) {
                
$readonlyFields $form->Fields()->makeReadonly();
                
$form->setFields($readonlyFields);
            }

            
$this->extend('updateEditForm'$form);
            return 
$form;
        } else if(
$id) {
            
$form CMSForm::create$this"EditForm", new FieldList(
                new 
LabelField('PageDoesntExistLabel',_t('CMSMain.PAGENOTEXISTS',"This page doesn't exist"))), new FieldList()
            )->
setHTMLID('Form_EditForm');
            
$form->setResponseNegotiator($this->getResponseNegotiator());
            return 
$form;
        }
    }

    
/**
     * @param SS_HTTPRequest $request
     * @return string HTML
     */
    
public function treeview($request) {
        return 
$this->renderWith($this->getTemplatesWithSuffix('_TreeView'));
    }

    
/**
     * @param SS_HTTPRequest $request
     * @return string HTML
     */
    
public function listview($request) {
        return 
$this->renderWith($this->getTemplatesWithSuffix('_ListView'));
    }

    
/**
     * Callback to request the list of page types allowed under a given page instance.
     * Provides a slower but more precise response over SiteTreeHints
     *
     * @param SS_HTTPRequest $request
     * @return SS_HTTPResponse
     */
    
public function childfilter($request) {
        
// Check valid parent specified
        
$parentID $request->requestVar('ParentID');
        
$parent SiteTree::get()->byID($parentID);
        if(!
$parent || !$parent->exists()) return $this->httpError(404);

        
// Build hints specific to this class
        // Identify disallows and set globals
        
$classes SiteTree::page_type_classes();
        
$disallowedChildren = array();
        foreach(
$classes as $class) {
            
$obj singleton($class);
            if(
$obj instanceof HiddenClass) continue;

            if(!
$obj->canCreate(null, array('Parent' => $parent))) {
                
$disallowedChildren[] = $class;
            }
        }

        
$this->extend('updateChildFilter'$disallowedChildren$parentID);
        return 
$this
            
->getResponse()
            ->
addHeader('Content-Type''application/json; charset=utf-8')
            ->
setBody(Convert::raw2json($disallowedChildren));
    }

    
/**
     * Safely reconstruct a selected filter from a given set of query parameters
     *
     * @param array $params Query parameters to use
     * @return CMSSiteTreeFilter The filter class, or null if none present
     * @throws InvalidArgumentException if invalid filter class is passed.
     */
    
protected function getQueryFilter($params) {
        if(empty(
$params['FilterClass'])) return null;
        
$filterClass $params['FilterClass'];
        if(!
is_subclass_of($filterClass'CMSSiteTreeFilter')) {
            throw new 
InvalidArgumentException("Invalid filter class passed: {$filterClass}");
        }
        return 
$filterClass::create($params);
    }

    
/**
     * 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()}.
     *
     * @param array $params Search filter criteria
     * @param int $parentID Optional parent node to filter on (can't be combined with other search criteria)
     * @return SS_List
     * @throws InvalidArgumentException if invalid filter class is passed.
     */
    
public function getList($params = array(), $parentID 0) {
        if(
$filter $this->getQueryFilter($params)) {
            return 
$filter->getFilteredPages();
        } else {
            
$list DataList::create($this->stat('tree_class'));
            
$parentID is_numeric($parentID) ? $parentID 0;
            return 
$list->filter("ParentID"$parentID);
        }
    }

    public function 
ListViewForm() {
        
$params $this->getRequest()->requestVar('q');
        
$list $this->getList($params$parentID $this->getRequest()->requestVar('ParentID'));
        
$gridFieldConfig GridFieldConfig::create()->addComponents(
            new 
GridFieldSortableHeader(),
            new 
GridFieldDataColumns(),
            new 
GridFieldPaginator(self::config()->page_length)
        );
        if(
$parentID){
            
$gridFieldConfig->addComponent(
                
GridFieldLevelup::create($parentID)
                    ->
setLinkSpec('?ParentID=%d&view=list')
                    ->
setAttributes(array('data-pjax' => 'ListViewForm,Breadcrumbs'))
            );
        }
        
$gridField = new GridField('Page','Pages'$list$gridFieldConfig);
        
$columns $gridField->getConfig()->getComponentByType('GridFieldDataColumns');

        
// Don't allow navigating into children nodes on filtered lists
        
$fields = array(
            
'getTreeTitle' => _t('SiteTree.PAGETITLE''Page Title'),
            
'singular_name' => _t('SiteTree.PAGETYPE'),
            
'LastEdited' => _t('SiteTree.LASTUPDATED''Last Updated'),
        );
        
$gridField->getConfig()->getComponentByType('GridFieldSortableHeader')->setFieldSorting(array('getTreeTitle' => 'Title'));
        
$gridField->getState()->ParentID $parentID;

        if(!
$params) {
            
$fields array_merge(array('listChildrenLink' => ''), $fields);
        }

        
$columns->setDisplayFields($fields);
        
$columns->setFieldCasting(array(
            
'Created' => 'Datetime->Ago',
            
'LastEdited' => 'Datetime->FormatFromSettings',
            
'getTreeTitle' => 'HTMLText'
        
));

        
$controller $this;
        
$columns->setFieldFormatting(array(
            
'listChildrenLink' => function($value, &$item) use($controller) {
                
$num $item $item->numChildren() : null;
                if(
$num) {
                    return 
sprintf(
                        
'<a class="cms-panel-link list-children-link" data-pjax-target="ListViewForm,Breadcrumbs" href="%s">%s</a>',
                        
Controller::join_links(
                            
$controller->Link(),
                            
sprintf("?ParentID=%d&view=list", (int)$item->ID)
                        ),
                        
$num
                    
);
                }
            },
            
'getTreeTitle' => function($value, &$item) use($controller) {
                return 
sprintf(
                    
'<a class="action-detail" href="%s">%s</a>',
                    
Controller::join_links(
                        
singleton('CMSPageEditController')->Link('show'),
                        (int)
$item->ID
                    
),
                    
$item->TreeTitle // returns HTML, does its own escaping
                
);
            }
        ));

        
$listview CMSForm::create(
            
$this,
            
'ListViewForm',
            new 
FieldList($gridField),
            new 
FieldList()
        )->
setHTMLID('Form_ListViewForm');
        
$listview->setAttribute('data-pjax-fragment''ListViewForm');
        
$listview->setResponseNegotiator($this->getResponseNegotiator());

        
$this->extend('updateListView'$listview);

        
$listview->disableSecurityToken();
        return 
$listview;
    }

    public function 
currentPageID() {
        
$id parent::currentPageID();

        
$this->extend('updateCurrentPageID'$id);

        return 
$id;
    }

    
//------------------------------------------------------------------------------------------//
    // Data saving handlers

    /**
     * Save and Publish page handler
     */
    
public function save($data$form) {
        
$className $this->stat('tree_class');

        
// Existing or new record?
        
$id $data['ID'];
        if(
substr($id,0,3) != 'new') {
            
$record DataObject::get_by_id($className$id);
            if(
$record && !$record->canEdit()) return Security::permissionFailure($this);
            if(!
$record || !$record->ID) throw new SS_HTTPResponse_Exception("Bad record ID #$id"404);
        } else {
            if(!
singleton($this->stat('tree_class'))->canCreate()) return Security::permissionFailure($this);
            
$record $this->getNewItem($idfalse);
        }

        
// TODO Coupling to SiteTree
        
$record->HasBrokenLink 0;
        
$record->HasBrokenFile 0;

        if (!
$record->ObsoleteClassName$record->writeWithoutVersion();

        
// Update the class instance if necessary
        
if(isset($data['ClassName']) && $data['ClassName'] != $record->ClassName) {
            
$newClassName $record->ClassName;
            
// The records originally saved attribute was overwritten by $form->saveInto($record) before.
            // This is necessary for newClassInstance() to work as expected, and trigger change detection
            // on the ClassName attribute
            
$record->setClassName($data['ClassName']);
            
// Replace $record with a new instance
            
$record $record->newClassInstance($newClassName);
        }

        
// save form data into record
        
$form->saveInto($record);
        
$record->write();

        
// If the 'Save & Publish' button was clicked, also publish the page
        
if (isset($data['publish']) && $data['publish'] == 1) {
            
$record->doPublish();
        }

        return 
$this->getResponseNegotiator()->respond($this->getRequest());
    }

    
/**
     * @uses LeftAndMainExtension->augmentNewSiteTreeItem()
     */
    
public function getNewItem($id$setID true) {
        
$parentClass $this->stat('tree_class');
        list(
$dummy$className$parentID$suffix) = array_pad(explode('-',$id),4,null);

        if(!
is_subclass_of($className$parentClass) && strcasecmp($className$parentClass) != 0) {
            
$response Security::permissionFailure($this);
            if (!
$response) {
                
$response $this->getResponse();
            }
            throw new 
SS_HTTPResponse_Exception($response);
        }

        
$newItem = new $className();

        if( !
$suffix ) {
            
$sessionTag "NewItems." $parentID "." $className;
            if(
Session::get($sessionTag)) {
                
$suffix '-' Session::get($sessionTag);
                
Session::set($sessionTagSession::get($sessionTag) + 1);
            }
            else
                
Session::set($sessionTag1);

                
$id $id $suffix;
        }

        
$newItem->Title _t(
            
'CMSMain.NEWPAGE',
            
"New {pagetype}",'followed by a page type title',
            array(
'pagetype' => singleton($className)->i18n_singular_name())
        );
        
$newItem->ClassName $className;
        
$newItem->ParentID $parentID;

        
// DataObject::fieldExists only checks the current class, not the hierarchy
        // This allows the CMS to set the correct sort value
        
if($newItem->castingHelper('Sort')) {
            
$newItem->Sort DB::prepared_query('SELECT MAX("Sort") FROM "SiteTree" WHERE "ParentID" = ?', array($parentID))->value() + 1;
        }

        if(
$setID$newItem->ID $id;

        
# Some modules like subsites add extra fields that need to be set when the new item is created
        
$this->extend('augmentNewSiteTreeItem'$newItem);

        return 
$newItem;
    }

    
/**
     * Delete the page from live. This means a page in draft mode might still exist.
     *
     * @see delete()
     */
    
public function deletefromlive($data$form) {
        
Versioned::reading_stage('Live');
        
$record DataObject::get_by_id("SiteTree"$data['ID']);
        if(
$record && !($record->canDelete() && $record->canDeleteFromLive())) return Security::permissionFailure($this);

        
$descRemoved '';
        
$descendantsRemoved 0;
        
$recordTitle $record->Title;
        
$recordID $record->ID;

        
// before deleting the records, get the descendants of this tree
        
if($record) {
            
$descendantIDs $record->getDescendantIDList();

            
// then delete them from the live site too
            
$descendantsRemoved 0;
            foreach( 
$descendantIDs as $descID )
                if( 
$descendant DataObject::get_by_id('SiteTree'$descID) ) {
                    
$descendant->doDeleteFromLive();
                    
$descendantsRemoved++;
                }

            
// delete the record
            
$record->doDeleteFromLive();
        }

        
Versioned::reading_stage('Stage');

        if(isset(
$descendantsRemoved)) {
            
$descRemoved ' ' _t(
                
'CMSMain.DESCREMOVED',
                
'and {count} descendants',
                array(
'count' => $descendantsRemoved)
            );
        } else {
            
$descRemoved '';
        }

        
$this->getResponse()->addHeader(
            
'X-Status',
            
rawurlencode(
                
_t(
                    
'CMSMain.REMOVED',
                    
'Deleted '{title}'{description} from live site',
                    array(
'title' => $recordTitle'description' => $descRemoved)
                )
            )
        );

        
// Even if the record has been deleted from stage and live, it can be viewed in "archive mode"
        
return $this->getResponseNegotiator()->respond($this->getRequest());
    }

    
/**
     * Actually perform the publication step
     */
    
public function performPublish($record) {
        if(
$record && !$record->canPublish()) return Security::permissionFailure($this);

        
$record->doPublish();
    }

    
/**
      * Reverts a page by publishing it to live.
      * Use {@link restorepage()} if you want to restore a page
      * which was deleted from draft without publishing.
      *
      * @uses SiteTree->doRevertToLive()
     */
    
public function revert($data$form) {
        if(!isset(
$data['ID'])) return new SS_HTTPResponse("Please pass an ID in the form content"400);

        
$id = (int) $data['ID'];
        
$restoredPage Versioned::get_latest_version("SiteTree"$id);
        if(!
$restoredPage)     return new SS_HTTPResponse("SiteTree #$id not found"400);

        
$record Versioned::get_one_by_stage('SiteTree''Live', array(
            
'"SiteTree_Live"."ID"' => $id
        
));

        
// a user can restore a page without publication rights, as it just adds a new draft state
        // (this action should just be available when page has been "deleted from draft")
        
if($record && !$record->canEdit()) return Security::permissionFailure($this);
        if(!
$record || !$record->ID) throw new SS_HTTPResponse_Exception("Bad record ID #$id"404);

        
$record->doRevertToLive();

        
$this->getResponse()->addHeader(
            
'X-Status',
            
rawurlencode(_t(
                
'CMSMain.RESTORED',
                
"Restored '{title}' successfully",
                
'Param %s is a title',
                array(
'title' => $record->Title)
            ))
        );

        return 
$this->getResponseNegotiator()->respond($this->getRequest());
    }

    
/**
     * Delete the current page from draft stage.
     * @see deletefromlive()
     */
    
public function delete($data$form) {
        
Deprecation::notice('4.0''Delete from stage is deprecated. Use archive instead');
        
$id $data['ID'];
        
$record DataObject::get_by_id("SiteTree"$id);
        if(
$record && !$record->canDelete()) return Security::permissionFailure();
        if(!
$record || !$record->ID) throw new SS_HTTPResponse_Exception("Bad record ID #$id"404);

        
// Delete record
        
$record->delete();

        
$this->getResponse()->addHeader(
            
'X-Status',
            
rawurlencode(sprintf(_t('CMSMain.REMOVEDPAGEFROMDRAFT',"Removed '%s' from the draft site"), $record->Title))
        );

        
// Even if the record has been deleted from stage and live, it can be viewed in "archive mode"
        
return $this->getResponseNegotiator()->respond($this->getRequest());
    }

    
/**
     * Delete this page from both live and stage
     *
     * @param type $data
     * @param type $form
     */
    
public function archive($data$form) {
        
$id $data['ID'];
        
$record DataObject::get_by_id("SiteTree"$id);
        if(!
$record || !$record->exists()) {
            throw new 
SS_HTTPResponse_Exception("Bad record ID #$id"404);
        }
        if(!
$record->canArchive()) {
            return 
Security::permissionFailure();
        }

        
// Archive record
        
$record->doArchive();

        
$this->getResponse()->addHeader(
            
'X-Status',
            
rawurlencode(sprintf(_t('CMSMain.ARCHIVEDPAGE',"Archived page '%s'"), $record->Title))
        );

        
// Even if the record has been deleted from stage and live, it can be viewed in "archive mode"
        
return $this->getResponseNegotiator()->respond($this->getRequest());
    }

    public function 
publish($data$form) {
        
$data['publish'] = '1';

        return 
$this->save($data$form);
    }

    public function 
unpublish($data$form) {
        
$className $this->stat('tree_class');
        
$record DataObject::get_by_id($className$data['ID']);

        if(
$record && !$record->canDeleteFromLive()) return Security::permissionFailure($this);
        if(!
$record || !$record->ID) throw new SS_HTTPResponse_Exception("Bad record ID #" . (int)$data['ID'], 404);

        
$record->doUnpublish();

        
$this->getResponse()->addHeader(
            
'X-Status',
            
rawurlencode(_t('CMSMain.REMOVEDPAGE',"Removed '{title}' from the published site", array('title' => $record->Title)))
        );

        return 
$this->getResponseNegotiator()->respond($this->getRequest());
    }

    
/**
     * @return array
     */
    
public function rollback() {
        return 
$this->doRollback(array(
            
'ID' => $this->currentPageID(),
            
'Version' => $this->getRequest()->param('VersionID')
        ), 
null);
    }

    
/**
     * Rolls a site back to a given version ID
     *
     * @param array
     * @param Form
     *
     * @return html
     */
    
public function doRollback($data$form) {
        
$this->extend('onBeforeRollback'$data['ID']);

        
$id = (isset($data['ID'])) ? (int) $data['ID'] : null;
        
$version = (isset($data['Version'])) ? (int) $data['Version'] : null;

        
$record DataObject::get_by_id($this->stat('tree_class'), $id);
        if(
$record && !$record->canEdit()) return Security::permissionFailure($this);

        if(
$version) {
            
$record->doRollbackTo($version);
            
$message _t(
                
'CMSMain.ROLLEDBACKVERSIONv2',
                
"Rolled back to version #%d.",
                array(
'version' => $data['Version'])
            );
        } else {
            
$record->doRollbackTo('Live');
            
$message _t(
                
'CMSMain.ROLLEDBACKPUBv2',"Rolled back to published version."
            
);
        }

        
$this->getResponse()->addHeader('X-Status'rawurlencode($message));

        
// Can be used in different contexts: In normal page edit view, in which case the redirect won't have any effect.
        // Or in history view, in which case a revert causes the CMS to re-load the edit view.
        // The X-Pjax header forces a "full" content refresh on redirect.
        
$url Controller::join_links(singleton('CMSPageEditController')->Link('show'), $record->ID);
        
$this->getResponse()->addHeader('X-ControllerURL'$url);
        
$this->getRequest()->addHeader('X-Pjax''Content');
        
$this->getResponse()->addHeader('X-Pjax''Content');

        return 
$this->getResponseNegotiator()->respond($this->getRequest());
    }

    
/**
     * Batch Actions Handler
     */
    
public function batchactions() {
        return new 
CMSBatchActionHandler($this'batchactions');
    }

    public function 
BatchActionParameters() {
        
$batchActions CMSBatchActionHandler::config()->batch_actions;

        
$forms = array();
        foreach(
$batchActions as $urlSegment => $batchAction) {
            
$SNG_action singleton($batchAction);
            if (
$SNG_action->canView() && $fieldset $SNG_action->getParameterFields()) {
                
$formHtml '';
                foreach(
$fieldset as $field) {
                    
$formHtml .= $field->Field();
                }
                
$forms[$urlSegment] = $formHtml;
            }
        }
        
$pageHtml '';
        foreach(
$forms as $urlSegment => $html) {
            
$pageHtml .= "<div class="params" id="BatchActionParameters_$urlSegment">$html</div>nn";
        }
        return new 
LiteralField("BatchActionParameters"'<div id="BatchActionParameters" style="display:none">'.$pageHtml.'</div>');
    }
    
/**
     * Returns a list of batch actions
     */
    
public function BatchActionList() {
        return 
$this->batchactions()->batchActionList();
    }

    public function 
buildbrokenlinks($request) {
        
// Protect against CSRF on destructive action
        
if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);

        
increase_time_limit_to();
        
increase_memory_limit_to();

        if(
$this->urlParams['ID']) {
            
$newPageSet[] = DataObject::get_by_id("Page"$this->urlParams['ID']);
        } else {
            
$pages DataObject::get("Page");
            foreach(
$pages as $page$newPageSet[] = $page;
            
$pages null;
        }

        
$content = new HtmlEditorField('Content');
        
$download = new HtmlEditorField('Download');

        foreach(
$newPageSet as $i => $page) {
            
$page->HasBrokenLink 0;
            
$page->HasBrokenFile 0;

            
$content->setValue($page->Content);
            
$content->saveInto($page);

            
$download->setValue($page->Download);
            
$download->saveInto($page);

            echo 
"<li>$page->Title (link:$page->HasBrokenLink, file:$page->HasBrokenFile)";

            
$page->writeWithoutVersion();
            
$page->destroy();
            
$newPageSet[$i] = null;
        }
    }

    public function 
publishall($request) {
        if(!
Permission::check('ADMIN')) return Security::permissionFailure($this);

        
increase_time_limit_to();
        
increase_memory_limit_to();

        
$response "";

        if(isset(
$this->requestParams['confirm'])) {
            
// Protect against CSRF on destructive action
            
if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);

            
$start 0;
            
$pages DataObject::get("SiteTree""""""""$start,30");
            
$count 0;
            while(
$pages) {
                foreach(
$pages as $page) {
                    if(
$page && !$page->canPublish()) return Security::permissionFailure($this);

                    
$page->doPublish();
                    
$page->destroy();
                    unset(
$page);
                    
$count++;
                    
$response .= "<li>$count</li>";
                }
                if(
$pages->Count() > 29) {
                    
$start += 30;
                    
$pages DataObject::get("SiteTree""""""""$start,30");
                } else {
                    break;
                }
            }
            
$response .= _t('CMSMain.PUBPAGES',"Done: Published {count} pages", array('count' => $count));

        } else {
            
$token SecurityToken::inst();
            
$fields = new FieldList();
            
$token->updateFieldSet($fields);
            
$tokenField $fields->First();
            
$tokenHtml = ($tokenField) ? $tokenField->FieldHolder() : '';
            
$response .= '<h1>' _t('CMSMain.PUBALLFUN','"Publish All" functionality') . '</h1>
                <p>' 
_t('CMSMain.PUBALLFUN2''Pressing this button will do the equivalent of going to every page and pressing "publish".  It's
                intended to be used after there have been massive edits of the content
such as when the site was
                first built
.') . '</p>
                <
form method="post" action="publishall">
                    <
input type="submit" name="confirm" value="'
                    . _t('CMSMain.PUBALLCONFIRM',"
Please publish every page in the sitecopying content stage to live",'Confirmation button') .'" />'
                    . $tokenHtml .
                '
</form>';
        }

        return $response;
    }

    /**
     * Restore a completely deleted page from the SiteTree_versions table.
     */
    public function restore($data, $form) {
        if(!isset($data['
ID']) || !is_numeric($data['ID'])) {
            return new SS_HTTPResponse("Please pass an ID in the form content", 400);
        }

        $id = (int)$data['
ID'];
        $restoredPage = Versioned::get_latest_version("SiteTree", $id);
        if(!$restoredPage)     return new SS_HTTPResponse("SiteTree #$id not found", 400);

        $restoredPage = $restoredPage->doRestoreToStage();

        $this->getResponse()->addHeader(
            '
X-Status',
            rawurlencode(_t(
                '
CMSMain.RESTORED',
                "Restored '
{title}' successfully",
                array('
title' => $restoredPage->Title)
            ))
        );

        return $this->getResponseNegotiator()->respond($this->getRequest());
    }

    public function duplicate($request) {
        // Protect against CSRF on destructive action
        if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);

        if(($id = $this->urlParams['
ID']) && is_numeric($id)) {
            $page = DataObject::get_by_id("SiteTree", $id);
            if($page && (!$page->canEdit() || !$page->canCreate(null, array('
Parent' => $page->Parent())))) {
                return Security::permissionFailure($this);
            }
            if(!$page || !$page->ID) throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404);

            $newPage = $page->duplicate();

            // ParentID can be hard-set in the URL.  This is useful for pages with multiple parents
            if(isset($_GET['
parentID']) && is_numeric($_GET['parentID'])) {
                $newPage->ParentID = $_GET['
parentID'];
                $newPage->write();
            }

            $this->getResponse()->addHeader(
                '
X-Status',
                rawurlencode(_t(
                    '
CMSMain.DUPLICATED',
                    "Duplicated '
{title}' successfully",
                    array('
title' => $newPage->Title)
                ))
            );
            $url = Controller::join_links(singleton('
CMSPageEditController')->Link('show'), $newPage->ID);
            $this->getResponse()->addHeader('
X-ControllerURL', $url);
            $this->getRequest()->addHeader('
X-Pjax', 'Content');
            $this->getResponse()->addHeader('
X-Pjax', 'Content');

            return $this->getResponseNegotiator()->respond($this->getRequest());
        } else {
            return new SS_HTTPResponse("CMSMain::duplicate() Bad ID: '
$id'", 400);
        }
    }

    public function duplicatewithchildren($request) {
        // Protect against CSRF on destructive action
        if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
        increase_time_limit_to();
        if(($id = $this->urlParams['
ID']) && is_numeric($id)) {
            $page = DataObject::get_by_id("SiteTree", $id);
            if($page && (!$page->canEdit() || !$page->canCreate(null, array('
Parent' => $page->Parent())))) {
                return Security::permissionFailure($this);
            }
            if(!$page || !$page->ID) throw new SS_HTTPResponse_Exception("Bad record ID #$id", 404);

            $newPage = $page->duplicateWithChildren();

            $this->getResponse()->addHeader(
                '
X-Status',
                rawurlencode(_t(
                    '
CMSMain.DUPLICATEDWITHCHILDREN',
                    "Duplicated '
{title}' and children successfully",
                    array('
title' => $newPage->Title)
                ))
            );
            $url = Controller::join_links(singleton('
CMSPageEditController')->Link('show'), $newPage->ID);
            $this->getResponse()->addHeader('
X-ControllerURL', $url);
            $this->getRequest()->addHeader('
X-Pjax', 'Content');
            $this->getResponse()->addHeader('
X-Pjax', 'Content');

            return $this->getResponseNegotiator()->respond($this->getRequest());
        } else {
            return new SS_HTTPResponse("CMSMain::duplicatewithchildren() Bad ID: '
$id'", 400);
        }
    }

    public function providePermissions() {
        $title = _t("CMSPagesController.MENUTITLE", LeftAndMain::menu_title_for_class('
CMSPagesController'));
        return array(
            "CMS_ACCESS_CMSMain" => array(
                '
name' => _t('CMSMain.ACCESS', "Access to '{title}' section", array('title' => $title)),
                '
category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access'),
                '
help' => _t(
                    '
CMSMain.ACCESS_HELP',
                    '
Allow viewing of the section containing page tree and contentView and edit permissions can be handled through page specific dropdowns, as well as the separate "Content permissions".'
                ),
                '
sort' => -99 // below "CMS_ACCESS_LeftAndMain", but above everything else
            )
        );
    }

}
Онлайн: 2
Реклама