3461 lines
106 KiB
Text
3461 lines
106 KiB
Text
|
<?php namespace ProcessWire;
|
|||
|
|
|||
|
/**
|
|||
|
* ProcessWire Page Edit Process
|
|||
|
*
|
|||
|
* Provides the UI for editing a page
|
|||
|
*
|
|||
|
* For more details about how Process modules work, please see:
|
|||
|
* /wire/core/Process.php
|
|||
|
*
|
|||
|
* ProcessWire 3.x, Copyright 2022 by Ryan Cramer
|
|||
|
* https://processwire.com
|
|||
|
*
|
|||
|
* @property string $noticeUnknown
|
|||
|
* @property string $noticeLocked
|
|||
|
* @property string $noticeNoAccess
|
|||
|
* @property string $noticeIncomplete
|
|||
|
* @property string $viewAction One of 'panel', 'modal', 'new', 'this' (see getViewActions method)
|
|||
|
* @property bool $useBookmarks
|
|||
|
*
|
|||
|
* @method Page loadPage($id)
|
|||
|
* @method string execute()
|
|||
|
* @method string executeTemplate()
|
|||
|
* @method void executeSaveTemplate($template = null)
|
|||
|
* @method string executeBookmarks()
|
|||
|
* @method array getViewActions($actions = array(), $configMode = false)
|
|||
|
* @method array getSubmitActions()
|
|||
|
* @method bool processSubmitAction($value)
|
|||
|
* @method void processSaveRedirect($redirectUrl)
|
|||
|
* @method void deletedPage($page, $redirectUrl, $trashed = false)
|
|||
|
* @method InputfieldForm buildForm(InputfieldForm $form)
|
|||
|
* @method InputfieldWrapper buildFormContent()
|
|||
|
* @method InputfieldWrapper buildFormChildren()
|
|||
|
* @method InputfieldWrapper buildFormSettings()
|
|||
|
* @method InputfieldWrapper buildFormDelete()
|
|||
|
* @method Inputfield buildFormCreatedUser() hookable in 3.0.194+
|
|||
|
* @method void buildFormView($url)
|
|||
|
* @method InputfieldMarkup buildFormRoles()
|
|||
|
* @method void processInput(InputfieldWrapper $form, $level = 0, $formRoot = null)
|
|||
|
* @method void ajaxSave(Page $page)
|
|||
|
* @method bool ajaxSaveDone(Page $page, array $data)
|
|||
|
* @method bool ajaxEditable(Page $page, $fieldName = '')
|
|||
|
* @method array getTabs()
|
|||
|
*
|
|||
|
*/
|
|||
|
|
|||
|
class ProcessPageEdit extends Process implements WirePageEditor, ConfigurableModule {
|
|||
|
|
|||
|
/**
|
|||
|
* Module information
|
|||
|
*
|
|||
|
* @return array
|
|||
|
*
|
|||
|
*/
|
|||
|
public static function getModuleInfo() {
|
|||
|
return array(
|
|||
|
'title' => 'Page Edit',
|
|||
|
'summary' => 'Edit a Page',
|
|||
|
'version' => 112,
|
|||
|
'permanent' => true,
|
|||
|
'permission' => 'page-edit',
|
|||
|
'icon' => 'edit',
|
|||
|
'useNavJSON' => true
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Page edit form
|
|||
|
*
|
|||
|
* @var InputfieldForm
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $form;
|
|||
|
|
|||
|
/**
|
|||
|
* Page being edited
|
|||
|
*
|
|||
|
* @var Page
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $page;
|
|||
|
|
|||
|
/**
|
|||
|
* Single field to edit (if only 'fields' specified, this contains first field present in 'fields')
|
|||
|
*
|
|||
|
* @var null|Field
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $field = null;
|
|||
|
|
|||
|
/**
|
|||
|
* Array of fields to edit, indexed by field name
|
|||
|
*
|
|||
|
* @var array|Field[]
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $fields = array();
|
|||
|
|
|||
|
/**
|
|||
|
* Field name suffix, applicable only when field or fields (above) is also set, in specific situations like repeaters
|
|||
|
*
|
|||
|
* @var string
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $fnsx = '';
|
|||
|
|
|||
|
/**
|
|||
|
* Substituted master page (deprecated)
|
|||
|
*
|
|||
|
* @var null|Page
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $masterPage = null;
|
|||
|
|
|||
|
/**
|
|||
|
* Parent of page being edited
|
|||
|
*
|
|||
|
* @var Page
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $parent;
|
|||
|
|
|||
|
/**
|
|||
|
* User that is editing
|
|||
|
*
|
|||
|
* @var User
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $user;
|
|||
|
|
|||
|
/**
|
|||
|
* @var int ID of page being edited
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $id;
|
|||
|
|
|||
|
/**
|
|||
|
* URL to redirect to
|
|||
|
*
|
|||
|
* @var string
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $redirectUrl;
|
|||
|
|
|||
|
/**
|
|||
|
* @var string PHP class name of Page being edited
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $pageClass;
|
|||
|
|
|||
|
/**
|
|||
|
* Is the page in the trash?
|
|||
|
*
|
|||
|
* @var bool
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $isTrash;
|
|||
|
|
|||
|
/**
|
|||
|
* Cache used by getAllowedTemplates() method
|
|||
|
*
|
|||
|
* Contains Template objects indexed by template ID.
|
|||
|
*
|
|||
|
* @var array|Template[]
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $allowedTemplates = null; // cache
|
|||
|
|
|||
|
/**
|
|||
|
* Is this a POST request to save a page?
|
|||
|
*
|
|||
|
* @var bool
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $isPost = false;
|
|||
|
|
|||
|
/**
|
|||
|
* Show the "settings" tab?
|
|||
|
*
|
|||
|
* @var bool
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $useSettings = true;
|
|||
|
|
|||
|
/**
|
|||
|
* Show the "children" tab?
|
|||
|
*
|
|||
|
* @var bool
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $useChildren = true;
|
|||
|
|
|||
|
/**
|
|||
|
* Show the "view" tab/link?
|
|||
|
*
|
|||
|
* @var bool
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $useView = true;
|
|||
|
|
|||
|
/**
|
|||
|
* Identified tabs in the form indexed by tab ID and values are tab labels
|
|||
|
*
|
|||
|
* @var array
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $tabs = array();
|
|||
|
|
|||
|
/**
|
|||
|
* Predefined list of parents allowed for edited page (array of Page objects), set by setPredefinedParents() method
|
|||
|
*
|
|||
|
* @var array|PageArray
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $predefinedParents = array();
|
|||
|
|
|||
|
/**
|
|||
|
* Predefined list of templates allowed for edited page (array of Template objects), set by setPredefinedTemplates() method
|
|||
|
*
|
|||
|
* @var array|Template[]
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $predefinedTemplates = array();
|
|||
|
|
|||
|
/**
|
|||
|
* Primary editor process, if not $this
|
|||
|
*
|
|||
|
* @var null|WirePageEditor
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $editor = null;
|
|||
|
|
|||
|
/**
|
|||
|
* Tell the Page what Process is being used to edit it?
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $setEditor = true;
|
|||
|
|
|||
|
/**
|
|||
|
* Names of changed fields
|
|||
|
*
|
|||
|
* @var array
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $changes = array();
|
|||
|
|
|||
|
/**
|
|||
|
* @var Modules
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $modules;
|
|||
|
|
|||
|
/**
|
|||
|
* @var WireInput
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $input;
|
|||
|
|
|||
|
/**
|
|||
|
* @var Config
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $config;
|
|||
|
|
|||
|
/**
|
|||
|
* @var Sanitizer
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $sanitizer;
|
|||
|
|
|||
|
/**
|
|||
|
* @var Session
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $session;
|
|||
|
|
|||
|
/**
|
|||
|
* Sanitized contents of get[modal]
|
|||
|
*
|
|||
|
* @var int|string|bool|null
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $requestModal = null;
|
|||
|
|
|||
|
/**
|
|||
|
* Sanitized contents of get[context]
|
|||
|
*
|
|||
|
* @var string
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $requestContext = '';
|
|||
|
|
|||
|
/**
|
|||
|
* Sanitized contents of get[language]
|
|||
|
*
|
|||
|
* @var Language|null
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $requestLanguage = null;
|
|||
|
|
|||
|
/**
|
|||
|
* Is the LanguageSupportPageNames module installed?
|
|||
|
*
|
|||
|
* @var bool
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $hasLanguagePageNames = false;
|
|||
|
|
|||
|
/**
|
|||
|
* Contents of $config->pageEdit
|
|||
|
*
|
|||
|
* @var array
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $configSettings = array(
|
|||
|
'viewNew' => false,
|
|||
|
'confirm' => true,
|
|||
|
'ajaxChildren' => true,
|
|||
|
'ajaxParent' => true,
|
|||
|
'ajaxTemplate' => true,
|
|||
|
'ajaxCreatedUser' => true,
|
|||
|
'editCrumbs' => false,
|
|||
|
);
|
|||
|
|
|||
|
/**
|
|||
|
* Other core page classes
|
|||
|
*
|
|||
|
* @var array
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $otherCorePageClasses = array(
|
|||
|
'User', 'UserPage',
|
|||
|
'Role', 'RolePage',
|
|||
|
'Permission', 'PermissionPage',
|
|||
|
'Language', 'LanguagePage',
|
|||
|
);
|
|||
|
|
|||
|
/***********************************************************************************************************************
|
|||
|
* METHODS
|
|||
|
*
|
|||
|
*/
|
|||
|
|
|||
|
/**
|
|||
|
* Construct
|
|||
|
*
|
|||
|
*/
|
|||
|
public function __construct() {
|
|||
|
$this->set('useBookmarks', false);
|
|||
|
$this->set('viewAction', 'this');
|
|||
|
return parent::__construct();
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Wired to API
|
|||
|
*
|
|||
|
*/
|
|||
|
public function wired() {
|
|||
|
parent::wired();
|
|||
|
if($this->wire()->process instanceof WirePageEditor) {
|
|||
|
// keep existing process, which may be building on top of this one
|
|||
|
} else {
|
|||
|
$this->wire('process', $this);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Initialize the page editor by loading the requested page and any dependencies
|
|||
|
*
|
|||
|
* @throws WireException|Wire404Exception|WirePermissionException
|
|||
|
*
|
|||
|
*/
|
|||
|
public function init() {
|
|||
|
|
|||
|
$this->modules = $this->wire()->modules;
|
|||
|
$this->input = $this->wire()->input;
|
|||
|
$this->config = $this->wire()->config;
|
|||
|
$this->user = $this->wire()->user;
|
|||
|
$this->sanitizer = $this->wire()->sanitizer;
|
|||
|
$this->session = $this->wire()->session;
|
|||
|
|
|||
|
// predefined messages that maybe used in multiple places
|
|||
|
$this->set('noticeUnknown', $this->_("Unknown page")); // Init error: Unknown page
|
|||
|
$this->set('noticeLocked', $this->_("This page is locked for edits")); // Init error: Page is locked
|
|||
|
$this->set('noticeNoAccess', $this->_("You don't have access to edit")); // Init error: User doesn't have access
|
|||
|
$this->set('noticeIncomplete', $this->_("This page might have one or more incomplete fields (attempt to save or publish for more info)"));
|
|||
|
|
|||
|
$settings = $this->config->pageEdit;
|
|||
|
if(is_array($settings)) $this->configSettings = array_merge($this->configSettings, $settings);
|
|||
|
|
|||
|
if(in_array($this->input->urlSegment1, array('navJSON', 'bookmarks'))) return;
|
|||
|
|
|||
|
$getID = $this->input->get('id');
|
|||
|
if($getID === 'bookmark') {
|
|||
|
$this->session->redirect('./bookmarks/');
|
|||
|
return;
|
|||
|
}
|
|||
|
$getID = (int) $getID;
|
|||
|
$postID = (int) $this->input->post('id');
|
|||
|
$id = abs($postID ? $postID : $getID);
|
|||
|
|
|||
|
if(!$id) {
|
|||
|
$this->session->location('./bookmarks/');
|
|||
|
throw new Wire404Exception($this->noticeUnknown, Wire404Exception::codeSecondary); // Init error: no page provided
|
|||
|
}
|
|||
|
|
|||
|
$this->page = $this->loadPage($id);
|
|||
|
$this->id = $this->page->id;
|
|||
|
$this->pageClass = $this->page->className();
|
|||
|
$this->page->setOutputFormatting(false);
|
|||
|
$this->parent = $this->pages->get($this->page->parent_id);
|
|||
|
$this->isTrash = $this->page->isTrash();
|
|||
|
|
|||
|
// check if editing specific field or fieldset only
|
|||
|
if($this->page) {
|
|||
|
$field = $this->input->get('field');
|
|||
|
$fields = $this->input->get('fields');
|
|||
|
if($this->input->get('fnsx') !== null) $this->fnsx = $this->input->get->fieldName('fnsx');
|
|||
|
if($field && !$fields) $fields = $field;
|
|||
|
if($fields) {
|
|||
|
$fields = explode(',', $fields);
|
|||
|
foreach($fields as $fieldName) {
|
|||
|
$fieldName = $this->sanitizer->fieldName($fieldName);
|
|||
|
if(!$fieldName) throw new WireException("Invalid field name specified");
|
|||
|
$field = $this->page->template->fieldgroup->getField($fieldName, true); // get in context
|
|||
|
if(!$field) throw new WireException("Field '$fieldName' is not applicable to this page");
|
|||
|
$this->fields[$field->name] = $field;
|
|||
|
}
|
|||
|
$this->field = reset($this->fields);
|
|||
|
$this->useChildren = false;
|
|||
|
$this->useSettings = false;
|
|||
|
$this->useView = false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// determine if we're going to be dealing with a save/post request
|
|||
|
$this->isPost = ($postID > 0 && ($postID === $this->page->id))
|
|||
|
|| ($this->config->ajax && ($this->input->requestMethod('POST') || isset($_SERVER['HTTP_X_FIELDNAME'])));
|
|||
|
|
|||
|
if(!$this->isPost) {
|
|||
|
$this->setupHeadline();
|
|||
|
$this->setupBreadcrumbs();
|
|||
|
}
|
|||
|
|
|||
|
// optional context GET var
|
|||
|
$context = $this->input->get('context');
|
|||
|
if($context) $this->requestContext = $this->sanitizer->name($context);
|
|||
|
|
|||
|
// optional language GET var
|
|||
|
$languages = $this->wire()->languages;
|
|||
|
if($languages) {
|
|||
|
$this->hasLanguagePageNames = $languages->hasPageNames();
|
|||
|
if($this->hasLanguagePageNames) {
|
|||
|
$languageID = (int) $this->input->get('language');
|
|||
|
if($languageID > 0) {
|
|||
|
$language = $languages->get($languageID);
|
|||
|
if($language->id && $language->id != $this->user->language->id) $this->requestLanguage = $language;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// optional modal setting
|
|||
|
if($this->config->modal) {
|
|||
|
$this->requestModal = $this->sanitizer->name($this->config->modal);
|
|||
|
}
|
|||
|
|
|||
|
parent::init();
|
|||
|
|
|||
|
if(!$this->isPost) {
|
|||
|
$this->modules->get('JqueryWireTabs');
|
|||
|
/** @var JqueryUI $jQueryUI */
|
|||
|
$jQueryUI = $this->modules->get('JqueryUI');
|
|||
|
$jQueryUI->use('modal');
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Given a page ID, return the Page object
|
|||
|
*
|
|||
|
* @param int $id
|
|||
|
* @return Page
|
|||
|
* @throws WireException|WirePermissionException
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function ___loadPage($id) {
|
|||
|
|
|||
|
/** @var Page|NullPage $page */
|
|||
|
$page = $this->wire()->pages->get((int) $id);
|
|||
|
|
|||
|
if($page instanceof NullPage) {
|
|||
|
throw new WireException($this->noticeUnknown); // page doesn't exist
|
|||
|
}
|
|||
|
|
|||
|
$editable = $page->editable();
|
|||
|
|
|||
|
if($page instanceof User) {
|
|||
|
// special case when page is a User
|
|||
|
$userAdmin = $this->user->hasPermission('user-admin');
|
|||
|
|
|||
|
if($userAdmin && $this->wire()->process != 'ProcessUser') {
|
|||
|
// only allow user pages to be edited from the access section (at least for non-superusers)
|
|||
|
$this->session->redirect($this->config->urls->admin . 'access/users/edit/?id=' . $page->id);
|
|||
|
}
|
|||
|
|
|||
|
if(!$userAdmin && $page->id === $this->user->id && $this->config->ajax) {
|
|||
|
// user that lacks user-admin permission editing themself during ajax request
|
|||
|
$fieldName = $this->input->get->fieldName('field');
|
|||
|
$field = $fieldName ? $this->wire()->fields->get($fieldName) : null;
|
|||
|
if($field instanceof Field) {
|
|||
|
// respond to ajax request for field that is editable
|
|||
|
/** @var PagePermissions $pagePermissions */
|
|||
|
$pagePermissions = $this->modules->get('PagePermissions');
|
|||
|
$editable = $pagePermissions->userFieldEditable($field);
|
|||
|
// prevent a later potential redirect to user editor
|
|||
|
if($editable) $this->setEditor = false;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if(!$editable) {
|
|||
|
throw new WirePermissionException($this->noticeNoAccess);
|
|||
|
}
|
|||
|
|
|||
|
return $page;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Execute the Page Edit process by building the form and checking if it was submitted
|
|||
|
*
|
|||
|
* @return string
|
|||
|
* @throws WireException
|
|||
|
*
|
|||
|
*/
|
|||
|
public function ___execute() {
|
|||
|
|
|||
|
if(!$this->page) throw new WireException($this->_('No page found'));
|
|||
|
|
|||
|
if($this->setEditor) {
|
|||
|
// note that setting the editor can force a redirect to a ProcessPageType editor
|
|||
|
$this->page->setEditor($this->editor ? $this->editor : $this);
|
|||
|
}
|
|||
|
|
|||
|
if($this->config->ajax && (isset($_SERVER['HTTP_X_FIELDNAME']) || $this->input->requestMethod('POST'))) {
|
|||
|
$this->ajaxSave($this->page);
|
|||
|
return '';
|
|||
|
}
|
|||
|
|
|||
|
if($this->page->hasStatus(Page::statusTemp) && $this->page->parent->template->childNameFormat == 'title') {
|
|||
|
// make it set page name from page title
|
|||
|
$this->page->name = '';
|
|||
|
}
|
|||
|
|
|||
|
$adminTheme = $this->wire()->adminTheme;
|
|||
|
if($adminTheme) {
|
|||
|
$className = $this->className();
|
|||
|
$adminTheme->addBodyClass("$className-id-{$this->page->id}");
|
|||
|
$adminTheme->addBodyClass("$className-template-{$this->page->template->name}");
|
|||
|
}
|
|||
|
|
|||
|
$this->form = $this->modules->get('InputfieldForm');
|
|||
|
$this->form = $this->buildForm($this->form);
|
|||
|
$this->form->setTrackChanges();
|
|||
|
|
|||
|
if($this->isPost && $this->form->isSubmitted()) $this->processSave();
|
|||
|
|
|||
|
if($this->page->hasStatus(Page::statusLocked)) {
|
|||
|
if($this->user->hasPermission('page-lock', $this->page)) {
|
|||
|
$this->warning($this->noticeLocked); // Page locked message
|
|||
|
} else {
|
|||
|
$this->error($this->noticeLocked); // Page locked error
|
|||
|
}
|
|||
|
} else if(!$this->isPost && $this->page->hasStatus(Page::statusFlagged) && !$this->input->get('s')) {
|
|||
|
$this->warning($this->noticeIncomplete);
|
|||
|
}
|
|||
|
|
|||
|
return $this->renderEdit();
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/*********************************************************************************************************************
|
|||
|
* EDITOR FORM BUILDING
|
|||
|
*
|
|||
|
*/
|
|||
|
|
|||
|
/**
|
|||
|
* Render the Page Edit form
|
|||
|
*
|
|||
|
* @return string
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function renderEdit() {
|
|||
|
|
|||
|
$config = $this->config;
|
|||
|
$class = '';
|
|||
|
$numFields = count($this->fields);
|
|||
|
$out = "<p id='PageIDIndicator' class='$class'>{$this->page->id}</p>";
|
|||
|
|
|||
|
$description = $this->form->getSetting('description');
|
|||
|
if($description) {
|
|||
|
$out .= "<h2>" . $this->form->entityEncode($description, Inputfield::textFormatBasic) . "</h2>";
|
|||
|
$this->form->set('description', '');
|
|||
|
}
|
|||
|
|
|||
|
if(!$numFields) {
|
|||
|
/** @var JqueryWireTabs $tabs */
|
|||
|
$tabs = $this->modules->get('JqueryWireTabs');
|
|||
|
$this->form->value = $tabs->renderTabList($this->getTabs(), array('id' => 'PageEditTabs'));
|
|||
|
}
|
|||
|
|
|||
|
$out .= $this->form->render();
|
|||
|
|
|||
|
// buttons with dropdowns
|
|||
|
if(!$numFields) {
|
|||
|
$submitActions = $this->getSubmitActions();
|
|||
|
if(count($submitActions)) {
|
|||
|
$file = $config->debug ? 'dropdown.js' : 'dropdown.min.js';
|
|||
|
$config->scripts->add($config->urls('InputfieldSubmit') . $file);
|
|||
|
$input = "<input type='hidden' id='after-submit-action' name='_after_submit_action' value='' />";
|
|||
|
$out = str_replace('</form>', "$input</form>", $out);
|
|||
|
$out .= "<ul class='pw-button-dropdown' data-pw-dropdown-input='#after-submit-action' data-my='right top' data-at='right bottom+1'>";
|
|||
|
foreach($submitActions as $action) {
|
|||
|
$icon = empty($action['icon']) ? "" : "<i class='fa fa-fw fa-$action[icon]'></i>";
|
|||
|
$class = empty($action['class']) ? "after-submit-$action[value]" : $action['class'];
|
|||
|
$out .= "<li><a class='$class' data-pw-dropdown-value='$action[value]' href='#'>$icon $action[label]</a></li>";
|
|||
|
}
|
|||
|
$out .= "</ul>";
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if(!$numFields && !$this->requestModal && $this->page->viewable()) {
|
|||
|
// this supports code in the buildFormView() method
|
|||
|
$out .= "<ul id='_ProcessPageEditViewDropdown' class='pw-dropdown-menu pw-dropdown-menu-rounded' data-my='left top' data-at='left top-9'>";
|
|||
|
foreach($this->getViewActions() as $name => $action) {
|
|||
|
$out .= "<li class='page-view-action-$name'>$action</li>";
|
|||
|
}
|
|||
|
$out .= "</ul>";
|
|||
|
}
|
|||
|
|
|||
|
$func = 'initPageEditForm();'; // to prevent IDE from flagging as unknown function
|
|||
|
$out .= "<scr" . "ipt>$func</script>"; // ends up being slightly faster than ready() (or at least appears that way)
|
|||
|
|
|||
|
return $out;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get actions for submit button(s)
|
|||
|
*
|
|||
|
* Should return array where each item in the array is itself an array like this:
|
|||
|
* ~~~~~
|
|||
|
* [
|
|||
|
* 'value' => 'value of action, i.e. view, edit, add, etc.',
|
|||
|
* 'icon' => 'icon name excluding the “fa-” part',
|
|||
|
* 'label' => 'text label where %s is replaced with submit button label',
|
|||
|
* 'class' => 'optional class attribute',
|
|||
|
* ]
|
|||
|
* ~~~~~~
|
|||
|
* Array returned by this method is indexed by the 'value', though this is not required for hooks.
|
|||
|
*
|
|||
|
* #pw-hooker
|
|||
|
*
|
|||
|
* @return array
|
|||
|
* @throws WireException
|
|||
|
* @since 3.0.142
|
|||
|
* @see ___processSubmitAction()
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function ___getSubmitActions() {
|
|||
|
|
|||
|
if($this->requestModal) return array();
|
|||
|
|
|||
|
$viewable = $this->page->viewable();
|
|||
|
$process = $this->wire()->process;
|
|||
|
$actions = array();
|
|||
|
|
|||
|
$actions['exit'] = array(
|
|||
|
'value' => 'exit',
|
|||
|
'icon' => 'close',
|
|||
|
'label' => $this->_('%s + Exit'),
|
|||
|
'class' => '',
|
|||
|
);
|
|||
|
|
|||
|
if($viewable) $actions['view'] = array(
|
|||
|
'value' => 'view',
|
|||
|
'icon' => 'eye',
|
|||
|
'label' => $this->_('%s + View'),
|
|||
|
'class' => '',
|
|||
|
);
|
|||
|
|
|||
|
if("$process" === "$this" && $this->page->id > 1) {
|
|||
|
$parent = $this->page->parent();
|
|||
|
if($parent->addable()) $actions['add'] = array(
|
|||
|
'value' => 'add',
|
|||
|
'icon' => 'plus-circle',
|
|||
|
'label' => $this->_('%s + Add New'),
|
|||
|
'class' => '',
|
|||
|
);
|
|||
|
|
|||
|
if($parent->numChildren > 1) $actions['next'] = array(
|
|||
|
'value' => 'next',
|
|||
|
'icon' => 'edit',
|
|||
|
'label' => $this->_('%s + Next'),
|
|||
|
'class' => '',
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
return $actions;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get URL to view this page
|
|||
|
*
|
|||
|
* @param Language|int|string|null $language
|
|||
|
* @return string
|
|||
|
* @throws WireException
|
|||
|
* @since 3.0.142 Was protected in previous versions
|
|||
|
*
|
|||
|
*/
|
|||
|
public function getViewUrl($language = null) {
|
|||
|
$url = '';
|
|||
|
if(!$this->page) throw new WireException('No page yet');
|
|||
|
if($this->hasLanguagePageNames) {
|
|||
|
$languages = $this->wire()->languages;
|
|||
|
if($language) {
|
|||
|
if(is_string($language) || is_int($language)) $language = $languages->get($language);
|
|||
|
$userLanguage = $language;
|
|||
|
} else if($this->requestLanguage) {
|
|||
|
$userLanguage = $this->requestLanguage;
|
|||
|
} else {
|
|||
|
$userLanguage = $this->user->language;
|
|||
|
}
|
|||
|
if($userLanguage && $userLanguage->id) {
|
|||
|
$url = $this->page->localHttpUrl($userLanguage);
|
|||
|
}
|
|||
|
}
|
|||
|
if(!$url) $url = $this->page->httpUrl();
|
|||
|
return $url;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get actions for the "View" dropdown
|
|||
|
*
|
|||
|
* #pw-hooker
|
|||
|
*
|
|||
|
* @param array $actions Actions in case hook wants to populate them
|
|||
|
* @param bool $configMode Specify true if retrieving for configuration purposes rather than runtime purposes.
|
|||
|
* @return array of <a> tags or array of labels if $configMode == true
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function ___getViewActions($actions = array(), $configMode = false) {
|
|||
|
|
|||
|
$labels = array(
|
|||
|
'view' => $this->_x('Page View', 'panel-title'),
|
|||
|
'panel' => $this->_x('Panel', 'view-label'),
|
|||
|
'modal' => $this->_x('Modal Popup', 'view-label'),
|
|||
|
'new' => $this->_x('New Window/Tab', 'view-label'),
|
|||
|
'this' => $this->_x('Exit + View', 'view-label'),
|
|||
|
);
|
|||
|
|
|||
|
$icons = array(
|
|||
|
'panel' => 'columns',
|
|||
|
'modal' => 'picture-o',
|
|||
|
'new' => 'external-link-square',
|
|||
|
'this' => 'eye',
|
|||
|
);
|
|||
|
|
|||
|
if($configMode) {
|
|||
|
unset($labels['view']);
|
|||
|
return $labels;
|
|||
|
}
|
|||
|
|
|||
|
$url = $this->getViewUrl();
|
|||
|
if($this->page->hasStatus(Page::statusDraft) && strpos($url, '?') === false) $url .= '?draft=1';
|
|||
|
$languages = $this->hasLanguagePageNames ? $this->page->template->getLanguages() : null;
|
|||
|
|
|||
|
foreach($icons as $name => $icon) {
|
|||
|
$labels[$name] = "<i class='fa fa-fw fa-$icon'></i> " . $labels[$name];
|
|||
|
}
|
|||
|
|
|||
|
$class = '';
|
|||
|
$languageUrls = array();
|
|||
|
if($languages) {
|
|||
|
$class .= ' pw-has-items';
|
|||
|
foreach($languages as $language) {
|
|||
|
if(!$this->page->viewable($language)) continue;
|
|||
|
$localUrl = $this->page->localHttpUrl($language);
|
|||
|
if($this->page->hasStatus(Page::statusDraft) && strpos($localUrl, '?') === false) $localUrl .= '?draft=1';
|
|||
|
$languageUrls[$language->id] = $localUrl;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$actions = array_merge(array(
|
|||
|
"panel" => "<a class='pw-panel pw-panel-reload$class' href='$url' data-tab-text='$labels[view]' data-tab-icon='eye'>$labels[panel]</a>",
|
|||
|
"modal" => "<a class='pw-modal pw-modal-large$class' href='$url'>$labels[modal]</a>",
|
|||
|
"new" => "<a class='$class' target='_blank' href='$url'>$labels[new]</a>",
|
|||
|
"this" => "<a class='$class' target='_top' href='$url'>$labels[this]</a>",
|
|||
|
), $actions);
|
|||
|
|
|||
|
foreach($actions as $name => $action) {
|
|||
|
if(count($languageUrls) > 1) {
|
|||
|
$ul = "<ul class=''>";
|
|||
|
foreach($languages as $language) {
|
|||
|
/** @var Language $language */
|
|||
|
if(!isset($languageUrls[$language->id])) continue;
|
|||
|
$localUrl = $languageUrls[$language->id];
|
|||
|
$label = $language->get('title|name');
|
|||
|
$_action = str_replace(' pw-has-items', '', $action);
|
|||
|
$_action = str_replace("'$url'", "'$localUrl'", $_action);
|
|||
|
$_action = str_replace(">" . $labels[$name] . "<", ">$label<", $_action);
|
|||
|
$_action = str_replace("='$labels[view]'", "='$label'", $_action); // panel language
|
|||
|
$ul .= "<li>$_action</li>";
|
|||
|
}
|
|||
|
$ul .= "</ul>";
|
|||
|
$actions[$name] = str_replace('</a>', ' </a>', $action) . $ul;
|
|||
|
} else {
|
|||
|
$actions[$name] = str_replace(' pw-has-items', '', $action);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return $actions;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get URL (or form action attribute) for editing this page
|
|||
|
*
|
|||
|
* @param array $options
|
|||
|
* - `id` (int): Page ID to edit
|
|||
|
* - `modal` (int|string): Modal mode, when applicable
|
|||
|
* - `context` (string): Additional request context string, when applicable
|
|||
|
* - `language` (int|Language|string): Language for editor, if different from user’s language
|
|||
|
* - `field` (string): Only edit field with this name
|
|||
|
* - `fields` (string): CSV string of fields to edit, rather than all fields on apge
|
|||
|
* - `fnsx` (string): Field name suffix, applicable only when field or fields (above) is also set, in specific situations like repeaters
|
|||
|
* - `uploadOnlyMode (string|int): Upload only mode (internal use)
|
|||
|
* @return string
|
|||
|
*
|
|||
|
*/
|
|||
|
public function getEditUrl($options = array()) {
|
|||
|
$defaults = array(
|
|||
|
'id' => $this->page->id,
|
|||
|
'modal' => $this->requestModal,
|
|||
|
'context' => $this->requestContext,
|
|||
|
'language' => $this->requestLanguage,
|
|||
|
'field' => '',
|
|||
|
'fields' => '',
|
|||
|
'fnsx' => $this->fnsx,
|
|||
|
'uploadOnlyMode' => '',
|
|||
|
);
|
|||
|
if($this->field) {
|
|||
|
$numFields = count($this->fields);
|
|||
|
if($numFields === 1) {
|
|||
|
$defaults['field'] = $this->field->name;
|
|||
|
} else if($numFields > 1) {
|
|||
|
$defaults['fields'] = implode(',', array_keys($this->fields));
|
|||
|
}
|
|||
|
}
|
|||
|
$uploadOnlyMode = (int) $this->input->get('uploadOnlyMode');
|
|||
|
if($uploadOnlyMode && !$this->config->ajax) $defaults['uploadOnlyMode'] = $uploadOnlyMode;
|
|||
|
$options = array_merge($defaults, $options);
|
|||
|
$qs = array();
|
|||
|
foreach($options as $name => $value) {
|
|||
|
if(!empty($value)) $qs[] = "$name=$value";
|
|||
|
}
|
|||
|
return './?' . implode('&', $qs);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Build the form used for Page Edits
|
|||
|
*
|
|||
|
* @param InputfieldForm $form
|
|||
|
* @return InputfieldForm
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function ___buildForm(InputfieldForm $form) {
|
|||
|
|
|||
|
$form->attr('id+name', 'ProcessPageEdit');
|
|||
|
$form->attr('action', $this->getEditUrl(array('id' => $this->id)));
|
|||
|
$form->attr('method', 'post');
|
|||
|
$form->attr('enctype', 'multipart/form-data');
|
|||
|
$form->attr('class', 'ui-helper-clearfix template_' . $this->page->template . ' class_' . $this->page->className);
|
|||
|
$form->attr('autocomplete', 'off');
|
|||
|
$form->attr('data-uploading', $this->_('Are you sure? An upload is currently in progress and it may be lost if you proceed.'));
|
|||
|
|
|||
|
if($this->configSettings['confirm']) $form->addClass('InputfieldFormConfirm');
|
|||
|
|
|||
|
// for ProcessPageEditImageSelect support
|
|||
|
if($this->input->get('uploadOnlyMode') && !$this->config->ajax) {
|
|||
|
// for modal uploading with InputfieldFile or InputfieldImage
|
|||
|
if(count($this->fields) && $this->field->type instanceof FieldtypeImage) {
|
|||
|
$this->setRedirectUrl("../image/?id=$this->id");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$saveName = 'submit_save';
|
|||
|
$saveLabel = $this->_("Save"); // Button: save
|
|||
|
$submit2 = null; // second submit button, when applicable
|
|||
|
|
|||
|
if($this->field) {
|
|||
|
// focus in on a specific field or fields
|
|||
|
$form->addClass('ProcessPageEditSingleField');
|
|||
|
|
|||
|
|
|||
|
foreach($this->fields as $field) {
|
|||
|
$options = array(
|
|||
|
'contextStr' => $this->fnsx,
|
|||
|
'fieldName' => $field->name,
|
|||
|
'namespace' => '',
|
|||
|
'flat' => true,
|
|||
|
);
|
|||
|
foreach($this->page->getInputfields($options) as $inputfield) {
|
|||
|
/** @var Inputfield $inputfield */
|
|||
|
if(!$this->page->editable($field->name, false)) continue;
|
|||
|
$skipCollapsed = array(
|
|||
|
Inputfield::collapsedHidden,
|
|||
|
Inputfield::collapsedNoLocked,
|
|||
|
Inputfield::collapsedBlankLocked,
|
|||
|
Inputfield::collapsedYesLocked,
|
|||
|
Inputfield::collapsedTabLocked,
|
|||
|
);
|
|||
|
$collapsed = $inputfield->getSetting('collapsed');
|
|||
|
if($collapsed > 0 && !in_array($collapsed, $skipCollapsed)) {
|
|||
|
$inputfield->collapsed = Inputfield::collapsedNo;
|
|||
|
}
|
|||
|
$form->add($inputfield);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
} else {
|
|||
|
// all fields
|
|||
|
// determine what content fields should become tabs
|
|||
|
|
|||
|
$contentTab = $this->buildFormContent();
|
|||
|
$tabs = array();
|
|||
|
$tabWrap = null;
|
|||
|
$tabOpen = null;
|
|||
|
$tabViewable = null;
|
|||
|
$fieldsetTab = null;
|
|||
|
$collapsedTabTypes = array(
|
|||
|
Inputfield::collapsedTab => 1,
|
|||
|
Inputfield::collapsedTabAjax => 1,
|
|||
|
Inputfield::collapsedTabLocked => 1,
|
|||
|
);
|
|||
|
|
|||
|
// identify fields displayed as tabs and add fieldset open/close around them
|
|||
|
foreach($contentTab as $inputfield) {
|
|||
|
/** @var Inputfield $inputfield */
|
|||
|
if(!isset($collapsedTabTypes[$inputfield->collapsed])) continue;
|
|||
|
/** @var InputfieldFieldsetTabOpen $tab */
|
|||
|
if(!$fieldsetTab) {
|
|||
|
/** @var FieldtypeFieldsetTabOpen $fieldsetTab */
|
|||
|
$fieldsetTab = $this->modules->get('FieldtypeFieldsetTabOpen');
|
|||
|
$this->modules->get('FieldtypeFieldsetClose');
|
|||
|
}
|
|||
|
$tab = new InputfieldFieldsetTabOpen();
|
|||
|
$this->wire($tab);
|
|||
|
$tab->attr('name', '_tab_' . $inputfield->attr('name'));
|
|||
|
$tab->attr('id', '_tab_' . $inputfield->attr('id'));
|
|||
|
$tab->label = $inputfield->getSetting('tabLabel|label');
|
|||
|
if($inputfield->collapsed === Inputfield::collapsedTabAjax) {
|
|||
|
$tab->collapsed = Inputfield::collapsedYesAjax;
|
|||
|
$inputfield->collapsed = Inputfield::collapsedNo;
|
|||
|
if($this->isPost && !$contentTab->isProcessable($tab)) {
|
|||
|
$contentTab->remove($inputfield);
|
|||
|
continue;
|
|||
|
}
|
|||
|
}
|
|||
|
$contentTab->insertBefore($tab, $inputfield);
|
|||
|
$tabClose = new InputfieldFieldsetClose();
|
|||
|
$this->wire($tabClose);
|
|||
|
$tabClose->attr('id+name', $tab->attr('name') . '_END');
|
|||
|
$contentTab->insertAfter($tabClose, $inputfield);
|
|||
|
}
|
|||
|
|
|||
|
foreach($contentTab as $inputfield) {
|
|||
|
/** @var Inputfield $inputfield */
|
|||
|
if(!$tabOpen && $inputfield->className() === 'InputfieldFieldsetTabOpen') {
|
|||
|
// open new tab
|
|||
|
$showable = $this->isTrash ? 'editable' : 'viewable';
|
|||
|
$tabViewable = $this->page->$showable($inputfield->attr('name'));
|
|||
|
if($this->isPost) {
|
|||
|
// only remove non-visible tabs when in post/save mode, for proper processInput()
|
|||
|
if(!$tabViewable) $contentTab->remove($inputfield);
|
|||
|
// during post requests, this goes no further, as theres no need for visual tab manipulation
|
|||
|
continue;
|
|||
|
}
|
|||
|
$tabOpen = $inputfield;
|
|||
|
/** @var InputfieldWrapper $tabWrap */
|
|||
|
$tabWrap = $this->wire(new InputfieldWrapper());
|
|||
|
$tabWrap->attr('title', $tabOpen->getSetting('label'));
|
|||
|
$tabWrap->id = $tabOpen->attr('id');
|
|||
|
$tabWrap->collapsed = $tabOpen->getSetting('collapsed');
|
|||
|
// @todo support description in fieldset tab: works but needs styles for each admin theme, so commented out for now
|
|||
|
// $tabWrap->description = $inputfield->description;
|
|||
|
$tabWrap->notes = $inputfield->notes;
|
|||
|
$contentTab->remove($inputfield);
|
|||
|
if(!$tabViewable) continue;
|
|||
|
|
|||
|
if($inputfield->getSetting('modal')) {
|
|||
|
$href = $this->getEditUrl(array('field' => $inputfield->name, 'modal' => 1));
|
|||
|
$this->addTab($tabOpen->id, "<a class='pw-modal' " .
|
|||
|
"title='" . $this->sanitizer->entities($tabOpen->label) . "' " .
|
|||
|
"data-buttons='#ProcessPageEdit button[type=submit]' " .
|
|||
|
"data-autoclose='1' " .
|
|||
|
"href='$href'>" .
|
|||
|
$this->sanitizer->entities1($tabOpen->label) . "</a>",
|
|||
|
$tabWrap
|
|||
|
);
|
|||
|
/** @var JqueryUI $jqueryUI */
|
|||
|
$jqueryUI = $this->modules->get('JqueryUI');
|
|||
|
$jqueryUI->use('modal');
|
|||
|
$tabOpen = null;
|
|||
|
} else {
|
|||
|
$this->addTab($tabOpen->id, $this->sanitizer->entities1($tabOpen->label), $tabWrap);
|
|||
|
}
|
|||
|
|
|||
|
} else if($tabOpen && !$this->isPost) {
|
|||
|
/** @var Inputfield $tabOpen */
|
|||
|
// already have a tab open
|
|||
|
if($inputfield->attr('name') == $tabOpen->attr('name') . '_END') {
|
|||
|
// close tab
|
|||
|
if($tabViewable) $tabs[] = $tabWrap;
|
|||
|
$tabOpen = null;
|
|||
|
} else if($tabViewable) {
|
|||
|
// add to already open tab
|
|||
|
$tabWrap->add($inputfield);
|
|||
|
}
|
|||
|
$contentTab->remove($inputfield);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$form->append($contentTab);
|
|||
|
if(!$this->isPost) {
|
|||
|
foreach($tabs as $tab) $form->append($tab);
|
|||
|
}
|
|||
|
|
|||
|
if($this->page->addable() || $this->page->numChildren) $form->append($this->buildFormChildren());
|
|||
|
if(!$this->page->template->noSettings && $this->useSettings) $form->append($this->buildFormSettings());
|
|||
|
if($this->isTrash && !$this->isPost) {
|
|||
|
$this->message($this->_("This page is in the Trash"));
|
|||
|
$tabRestore = $this->buildFormRestore();
|
|||
|
if($tabRestore) $form->append($tabRestore);
|
|||
|
}
|
|||
|
$tabDelete = $this->buildFormDelete();
|
|||
|
if($tabDelete->children()->count()) $form->append($tabDelete);
|
|||
|
if($this->page->viewable() && !$this->requestModal) $this->buildFormView($this->getViewUrl());
|
|||
|
|
|||
|
if($this->page->hasStatus(Page::statusUnpublished)) {
|
|||
|
$pageClassName = wireClassName($this->page, false);
|
|||
|
$publishable = $this->page->publishable();
|
|||
|
if($publishable && (in_array($pageClassName, $this->otherCorePageClasses) || $this->page->template->noUnpublish)) {
|
|||
|
// Do not show a button allowing page to remain unpublished for User, Permission, Role, Language or
|
|||
|
// if the page's template indicates it cannot be unpublished
|
|||
|
} else {
|
|||
|
/** @var InputfieldSubmit $submit2 */
|
|||
|
$submit2 = $this->modules->get('InputfieldSubmit');
|
|||
|
$submit2->attr('name', 'submit_save');
|
|||
|
$submit2->attr('id', 'submit_save_unpublished');
|
|||
|
$submit2->showInHeader();
|
|||
|
$submit2->setSecondary();
|
|||
|
if($this->session->get('clientWidth') > 900) {
|
|||
|
$submit2->attr('value', $this->_('Save + Keep Unpublished')); // Button: save unpublished
|
|||
|
} else {
|
|||
|
$submit2->attr('value', $saveLabel); // Button: save unpublished
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if($publishable) {
|
|||
|
$saveName = 'submit_publish';
|
|||
|
$saveLabel = $this->_("Publish"); // Button: publish
|
|||
|
} else {
|
|||
|
$saveName = '';
|
|||
|
}
|
|||
|
} else {
|
|||
|
// use saveName and saveLabel defined at top of method
|
|||
|
}
|
|||
|
} // !$fieldName
|
|||
|
|
|||
|
if($saveName) {
|
|||
|
/** @var InputfieldSubmit $submit */
|
|||
|
$submit = $this->modules->get('InputfieldSubmit');
|
|||
|
$submit->attr('id+name', $saveName);
|
|||
|
$submit->attr('value', $saveLabel);
|
|||
|
$submit->showInHeader();
|
|||
|
$form->append($submit);
|
|||
|
}
|
|||
|
|
|||
|
if($submit2) $form->append($submit2);
|
|||
|
|
|||
|
/** @var InputfieldHidden $field */
|
|||
|
$field = $this->modules->get('InputfieldHidden');
|
|||
|
$field->attr('name', 'id');
|
|||
|
$field->attr('value', $this->page->id);
|
|||
|
$form->append($field);
|
|||
|
|
|||
|
return $form;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Build the 'content' tab on the Page Edit form
|
|||
|
*
|
|||
|
* @return InputfieldWrapper
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function ___buildFormContent() {
|
|||
|
|
|||
|
$fields = $this->page->getInputfields(array('flat' => !$this->isPost));
|
|||
|
$id = $this->className() . 'Content';
|
|||
|
$title = $this->page->template->getTabLabel('content');
|
|||
|
if(!$title) $title = $this->_('Content'); // Tab Label: Content
|
|||
|
|
|||
|
$fields->attr('id', $id);
|
|||
|
$fields->attr('title', $title);
|
|||
|
$this->addTab($id, $title, $fields);
|
|||
|
|
|||
|
if($this->page->template->nameContentTab) {
|
|||
|
$fields->prepend($this->buildFormPageName());
|
|||
|
}
|
|||
|
|
|||
|
return $fields;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Build the 'children' tab on the Page Edit form
|
|||
|
*
|
|||
|
* @return InputfieldWrapper
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function ___buildFormChildren() {
|
|||
|
|
|||
|
$page = $this->masterPage ? $this->masterPage : $this->page;
|
|||
|
$wrapper = $this->wire(new InputfieldWrapper()); /** @var InputfieldWrapper $wrapper */
|
|||
|
$id = $this->className() . 'Children';
|
|||
|
$wrapper->attr('id+name', $id);
|
|||
|
if(!empty($this->configSettings['ajaxChildren'])) $wrapper->collapsed = Inputfield::collapsedYesAjax;
|
|||
|
$defaultTitle = $this->_('Children'); // Tab Label: Children
|
|||
|
$title = $this->page->template->getTabLabel('children');
|
|||
|
if(!$title) $title = $defaultTitle;
|
|||
|
if($page->numChildren) {
|
|||
|
$wrapper->attr('title', "<em>$title</em>");
|
|||
|
} else {
|
|||
|
$wrapper->attr('title', $title);
|
|||
|
}
|
|||
|
$this->addTab($id, $title, $wrapper);
|
|||
|
$templateSortfield = $this->page->template->sortfield;
|
|||
|
|
|||
|
if(!$this->isPost) {
|
|||
|
|
|||
|
$pageListParent = $page ? $page : $this->parent;
|
|||
|
if($pageListParent->numChildren) {
|
|||
|
/** @var ProcessPageList $pageList */
|
|||
|
$pageList = $this->modules->get('ProcessPageList');
|
|||
|
$pageList->set('id', $pageListParent->id);
|
|||
|
$pageList->set('showRootPage', false);
|
|||
|
} else $pageList = null;
|
|||
|
|
|||
|
/** @var InputfieldMarkup $field */
|
|||
|
$field = $this->modules->get("InputfieldMarkup");
|
|||
|
$field->attr('id+name', 'ChildrenPageList');
|
|||
|
$field->label = $title == $defaultTitle ? $this->_("Children / Subpages") : $title; // Children field label
|
|||
|
if($pageList) {
|
|||
|
$field->value = $pageList->execute();
|
|||
|
} else {
|
|||
|
$field->description = $this->_("There are currently no children/subpages below this page.");
|
|||
|
}
|
|||
|
|
|||
|
if($templateSortfield && $templateSortfield != 'sort') {
|
|||
|
$field->notes = sprintf($this->_('Children are sorted by "%s", per the template setting.'), $templateSortfield);
|
|||
|
}
|
|||
|
|
|||
|
if($page->addable()) {
|
|||
|
/** @var InputfieldButton $button */
|
|||
|
$button = $this->modules->get("InputfieldButton");
|
|||
|
$button->attr('id+name', 'AddPageBtn');
|
|||
|
$button->attr('value', $this->_('Add New Page Here')); // Button: add new child page
|
|||
|
$button->icon = 'plus-circle';
|
|||
|
$button->attr('href', "../add/?parent_id={$page->id}" . ($this->requestModal ? "&modal=$this->requestModal" : ''));
|
|||
|
$field->append($button);
|
|||
|
}
|
|||
|
$wrapper->append($field);
|
|||
|
}
|
|||
|
|
|||
|
if(empty($this->page->template->sortfield) && $this->user->hasPermission('page-sort', $this->page)) {
|
|||
|
$sortfield = $this->page->sortfield && $this->page->sortfield != 'sort' ? $this->page->sortfield : '';
|
|||
|
$fieldset = self::buildFormSortfield($sortfield, $this);
|
|||
|
$fieldset->attr('id+name', 'ChildrenSortSettings');
|
|||
|
$fieldset->label = $this->_('Sort Settings'); // Children sort settings field label
|
|||
|
$fieldset->icon = 'sort';
|
|||
|
$fieldset->description = $this->_("If you want all current and future children to automatically sort by a specific field, select the field below and optionally check the 'reverse' checkbox to make the sort descending. Leave the sort field blank if you want to be able to drag-n-drop to your own order."); // Sort settings description text
|
|||
|
$wrapper->append($fieldset);
|
|||
|
}
|
|||
|
|
|||
|
return $wrapper;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Build the sortfield configuration fieldset
|
|||
|
*
|
|||
|
* NOTE: This is also used by ProcessTemplate, so it is self contained
|
|||
|
*
|
|||
|
* @param string $sortfield Current sortfield value
|
|||
|
* @param Process $caller The calling process
|
|||
|
* @return InputfieldFieldset
|
|||
|
*
|
|||
|
*/
|
|||
|
public static function buildFormSortfield($sortfield, Process $caller) {
|
|||
|
|
|||
|
$modules = $caller->wire()->modules;
|
|||
|
|
|||
|
/** @var InputfieldFieldset $fieldset */
|
|||
|
$fieldset = $modules->get("InputfieldFieldset");
|
|||
|
if(!$sortfield) $fieldset->collapsed = Inputfield::collapsedYes;
|
|||
|
|
|||
|
/** @var InputfieldSelect $field */
|
|||
|
$field = $modules->get('InputfieldSelect');
|
|||
|
$field->name = 'sortfield';
|
|||
|
$field->value = ltrim($sortfield, '-');
|
|||
|
$field->columnWidth = 60;
|
|||
|
$field->label = __('Children are sorted by', __FILE__); // Children sort field label
|
|||
|
|
|||
|
// if in ProcessTemplate, give a 'None' option that indicates the Page has control
|
|||
|
if($caller instanceof ProcessTemplate) $field->addOption('', __('None', __FILE__));
|
|||
|
|
|||
|
$field->addOption('sort', __('Manual drag-n-drop', __FILE__));
|
|||
|
|
|||
|
$options = array(
|
|||
|
'name' => 'name',
|
|||
|
'status' => 'status',
|
|||
|
'modified' => 'modified',
|
|||
|
'created' => 'created',
|
|||
|
'published' => 'published',
|
|||
|
);
|
|||
|
|
|||
|
$field->addOption(__('Native Fields', __FILE__), $options); // Optgroup label for sorting by fields native to ProcessWire
|
|||
|
|
|||
|
$customOptions = array();
|
|||
|
$fields = $caller->wire()->fields;
|
|||
|
$fieldsInfo = $fields->getAllValues(array('flags', 'type'), 'name');
|
|||
|
|
|||
|
foreach($fieldsInfo as $name => $f) {
|
|||
|
//if(!($f->flags & Field::flagAutojoin)) continue;
|
|||
|
if($f['flags'] & Field::flagSystem && $name != 'title' && $name != 'email') continue;
|
|||
|
if(wireInstanceOf($f['type'], 'FieldtypeFieldsetOpen')) continue;
|
|||
|
$customOptions[$name] = $name;
|
|||
|
}
|
|||
|
|
|||
|
ksort($customOptions);
|
|||
|
$field->addOption(__('Custom Fields', __FILE__), $customOptions); // Optgroup label for sorting by custom fields
|
|||
|
$fieldset->append($field);
|
|||
|
|
|||
|
/** @var InputfieldCheckbox $f */
|
|||
|
$f = $modules->get('InputfieldCheckbox');
|
|||
|
$f->value = 1;
|
|||
|
$f->attr('id+name', 'sortfield_reverse');
|
|||
|
$f->label = __('Reverse sort direction?', __FILE__); // Checkbox labe to reverse the sort direction
|
|||
|
$f->icon = 'rotate-left';
|
|||
|
if(substr($sortfield, 0, 1) == '-') $f->attr('checked', 'checked');
|
|||
|
$f->showIf = "sortfield!='', sortfield!=sort";
|
|||
|
$f->columnWidth = 40;
|
|||
|
|
|||
|
$fieldset->append($f);
|
|||
|
|
|||
|
return $fieldset;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Build the 'settings' tab on the Page Edit form
|
|||
|
*
|
|||
|
* @return InputfieldWrapper
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function ___buildFormSettings() {
|
|||
|
|
|||
|
$user = $this->wire()->user;
|
|||
|
$superuser = $user->isSuperuser();
|
|||
|
|
|||
|
/** @var InputfieldWrapper $wrapper */
|
|||
|
$wrapper = $this->wire(new InputfieldWrapper());
|
|||
|
$id = $this->className() . 'Settings';
|
|||
|
$title = $this->_('Settings'); // Tab Label: Settings
|
|||
|
$wrapper->attr('id', $id);
|
|||
|
$wrapper->attr('title', $title);
|
|||
|
$this->addTab($id, $title, $wrapper);
|
|||
|
|
|||
|
// name
|
|||
|
if(($this->page->id > 1 || $this->hasLanguagePageNames) && !$this->page->template->nameContentTab) {
|
|||
|
$wrapper->prepend($this->buildFormPageName());
|
|||
|
}
|
|||
|
|
|||
|
// template
|
|||
|
$wrapper->add($this->buildFormTemplate());
|
|||
|
|
|||
|
// parent
|
|||
|
if($this->page->id > 1 && $this->page->editable('parent', false)) {
|
|||
|
$wrapper->add($this->buildFormParent());
|
|||
|
}
|
|||
|
|
|||
|
// createdUser
|
|||
|
if($this->page->id && $superuser && $this->page->template->allowChangeUser) {
|
|||
|
$wrapper->add($this->buildFormCreatedUser());
|
|||
|
}
|
|||
|
|
|||
|
// status
|
|||
|
$wrapper->add($this->buildFormStatus());
|
|||
|
|
|||
|
// roles and references
|
|||
|
if(!$this->isPost) {
|
|||
|
// what users may access this page
|
|||
|
$wrapper->add($this->buildFormRoles());
|
|||
|
// what pages link tot his page
|
|||
|
$wrapper->add($this->buildFormReferences());
|
|||
|
}
|
|||
|
|
|||
|
// page path history (previous URLs)
|
|||
|
if($superuser || $user->hasPermission('page-edit-redirects', $this->page)) {
|
|||
|
$f = $this->buildFormPrevPaths();
|
|||
|
if($f) $wrapper->add($f);
|
|||
|
}
|
|||
|
|
|||
|
// information about created and modified user and time
|
|||
|
if(!$this->isPost) {
|
|||
|
$wrapper->add($this->buildFormInfo());
|
|||
|
}
|
|||
|
|
|||
|
return $wrapper;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Build the page name input
|
|||
|
*
|
|||
|
* @return InputfieldPageName
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function buildFormPageName() {
|
|||
|
|
|||
|
/** @var InputfieldPageName $field */
|
|||
|
$field = $this->modules->get('InputfieldPageName');
|
|||
|
$field->attr('name', '_pw_page_name');
|
|||
|
$field->attr('value', $this->page->name);
|
|||
|
$field->slashUrls = $this->page->template->slashUrls;
|
|||
|
$field->required = $this->page->id != 1 && !$this->page->hasStatus(Page::statusTemp);
|
|||
|
|
|||
|
$label = $this->page->template->getNameLabel();
|
|||
|
if($label) $field->label = $label;
|
|||
|
|
|||
|
if(!$this->page->editable('name', false)) {
|
|||
|
$field->attr('disabled', 'disabled');
|
|||
|
$field->required = false;
|
|||
|
}
|
|||
|
|
|||
|
if($this->hasLanguagePageNames) {
|
|||
|
// Using 'hasLanguages' as opposed to 'useLanguages' for different support from LanguageSupportPageNames
|
|||
|
$field->setQuietly('hasLanguages', true);
|
|||
|
}
|
|||
|
|
|||
|
$field->editPage = $this->page;
|
|||
|
if($this->page->parent) $field->parentPage = $this->page->parent;
|
|||
|
|
|||
|
return $field;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Build the template selection field
|
|||
|
*
|
|||
|
* @return InputfieldMarkup|InputfieldSelect
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function buildFormTemplate() {
|
|||
|
|
|||
|
if($this->page->editable('template', false)) {
|
|||
|
$languages = $this->wire()->languages;
|
|||
|
$language = $this->user->language; /** @var Language|null $language */
|
|||
|
$input = $this->wire()->input;
|
|||
|
$ajax = $this->configSettings['ajaxTemplate'];
|
|||
|
|
|||
|
/** @var InputfieldSelect $field */
|
|||
|
$field = $this->modules->get('InputfieldSelect');
|
|||
|
$field->attr('id+name', 'template');
|
|||
|
$field->attr('value', $this->page->template->id);
|
|||
|
$field->required = true;
|
|||
|
$field->collapsed = Inputfield::collapsedYesAjax;
|
|||
|
if(!$ajax || $input->get('renderInputfieldAjax') === 'template' || $input->post('template') !== null) {
|
|||
|
foreach($this->getAllowedTemplates() as $template) {
|
|||
|
/** @var Template $template */
|
|||
|
$label = '';
|
|||
|
if($languages && $language) $label = $template->get('label' . $language->id);
|
|||
|
if(!$label) $label = $template->label ? $template->label : $template->name;
|
|||
|
$field->addOption($template->id, $label);
|
|||
|
}
|
|||
|
}
|
|||
|
} else {
|
|||
|
/** @var InputfieldMarkup $field */
|
|||
|
$field = $this->modules->get('InputfieldMarkup');
|
|||
|
$field->attr('value', "<p>" . $this->wire()->sanitizer->entities1($this->page->template->getLabel()) . "</p>");
|
|||
|
}
|
|||
|
|
|||
|
$field->label = $this->_('Template') . ' (' . $this->page->template->getLabel() . ')'; // Settings: Template field label
|
|||
|
$field->icon = 'cubes';
|
|||
|
|
|||
|
return $field;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Build the parent selection Inputfield
|
|||
|
*
|
|||
|
* @return InputfieldPageListSelect|InputfieldSelect
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function buildFormParent() {
|
|||
|
|
|||
|
if(count($this->predefinedParents)) {
|
|||
|
/** @var InputfieldSelect $field */
|
|||
|
$field = $this->modules->get('InputfieldSelect');
|
|||
|
foreach($this->predefinedParents as $p) {
|
|||
|
$field->addOption($p->id, $p->path);
|
|||
|
}
|
|||
|
|
|||
|
} else {
|
|||
|
/** @var InputfieldPageListSelect $field */
|
|||
|
$field = $this->modules->get('InputfieldPageListSelect');
|
|||
|
$field->set('parent_id', 0);
|
|||
|
if(!empty($this->configSettings['ajaxParent'])) {
|
|||
|
$field->collapsed = Inputfield::collapsedYesAjax;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$field->required = true;
|
|||
|
$field->label = $this->_('Parent'); // Settings: Parent field label
|
|||
|
$field->icon = 'folder-open-o';
|
|||
|
$field->attr('id+name', 'parent_id');
|
|||
|
$field->attr('value', $this->page->parent_id);
|
|||
|
|
|||
|
return $field;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Build the created user selection
|
|||
|
*
|
|||
|
* Hookable as of 3.0.194
|
|||
|
*
|
|||
|
* #pw-hooker
|
|||
|
*
|
|||
|
* @return Inputfield
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function ___buildFormCreatedUser() {
|
|||
|
|
|||
|
$templates = $this->wire()->templates;
|
|||
|
$modules = $this->wire()->modules;
|
|||
|
$config = $this->wire()->config;
|
|||
|
$pages = $this->wire()->pages;
|
|||
|
|
|||
|
$selector = "parent_id=$config->usersPageID, include=all, limit=100";
|
|||
|
$usersPageIDs = $config->usersPageIDs;
|
|||
|
$createdUser = $this->page->createdUser;
|
|||
|
|
|||
|
if(count($usersPageIDs) < 2 && $pages->count($selector) < 100) {
|
|||
|
/** @var InputfieldPageListSelect $f */
|
|||
|
$f = $modules->get('InputfieldPageListSelect');
|
|||
|
$f->parent_id = $config->usersPageID;
|
|||
|
$f->showPath = false;
|
|||
|
$f->labelFieldName = 'name';
|
|||
|
|
|||
|
} else {
|
|||
|
$searchFields = array('name', 'email');
|
|||
|
$selector = "include=all, parent_id=" . implode('|', $usersPageIDs);
|
|||
|
if($createdUser->id != $config->guestUserPageID) $selector .= ", id!=$config->guestUserPageID";
|
|||
|
$userTemplate = null;
|
|||
|
|
|||
|
foreach($config->userTemplateIDs as $templateID) {
|
|||
|
$template = $templates->get((int) $templateID);
|
|||
|
if(!$userTemplate && $template->id === $config->userTemplateID) {
|
|||
|
$userTemplate = $template;
|
|||
|
}
|
|||
|
if($template && $template->hasField('title')) {
|
|||
|
$searchFields[] = 'title';
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/** @var InputfieldPageAutocomplete $f */
|
|||
|
$f = $modules->get('InputfieldPageAutocomplete');
|
|||
|
$f->labelFieldName = 'name';
|
|||
|
if($userTemplate && $userTemplate->pageLabelField) {
|
|||
|
$f->labelFieldFormat = $userTemplate->pageLabelField;
|
|||
|
} else {
|
|||
|
$f->labelFieldFormat = '{name} ({email}) [{roles.name}]';
|
|||
|
}
|
|||
|
$f->searchFields = implode(' ', $searchFields);
|
|||
|
$f->maxSelectedItems = 1;
|
|||
|
$f->findPagesSelector = $selector;
|
|||
|
$f->template_ids = $config->userTemplateIDs;
|
|||
|
$f->required = true;
|
|||
|
$f->useList = false;
|
|||
|
$f->addClass('InputfieldNoFocus', 'wrapClass');
|
|||
|
}
|
|||
|
|
|||
|
$f->val($createdUser);
|
|||
|
$f->label = $this->_('Created by User') . ($createdUser->id ? " ($createdUser->name)" : "");
|
|||
|
$f->icon = 'user-circle';
|
|||
|
$f->attr('id+name', 'created_users_id');
|
|||
|
if($this->configSettings['ajaxCreatedUser']) $f->collapsed = Inputfield::collapsedYesAjax;
|
|||
|
$f->required = true;
|
|||
|
|
|||
|
return $f;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Build the Settings > References fieldset on the Page Edit form
|
|||
|
*
|
|||
|
* @return InputfieldMarkup
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function buildFormReferences() {
|
|||
|
|
|||
|
/** @var InputfieldMarkup $field */
|
|||
|
$field = $this->modules->get('InputfieldMarkup');
|
|||
|
$field->attr('id', 'ProcessPageEditReferences');
|
|||
|
$field->label = $this->_('What pages link to this page?');
|
|||
|
$field->icon = 'link';
|
|||
|
$field->collapsed = Inputfield::collapsedYesAjax;
|
|||
|
|
|||
|
if($this->input->get('renderInputfieldAjax') != 'ProcessPageEditReferences') return $field;
|
|||
|
|
|||
|
$links = $this->page->links("include=all, limit=100");
|
|||
|
$references = $this->page->references("include=all, limit=100");
|
|||
|
|
|||
|
$numTotal = $references->getTotal() + $links->getTotal();
|
|||
|
$numShown = $references->count() + $links->count();
|
|||
|
$numNotShown = $numTotal - $numShown;
|
|||
|
$labelNotListable = $this->_('Not listable');
|
|||
|
|
|||
|
if($numTotal) {
|
|||
|
$field->description = sprintf(
|
|||
|
$this->_('Found %d other page(s) linking to this one in Page fields or href links.'),
|
|||
|
$numTotal
|
|||
|
);
|
|||
|
$out = "<ul>";
|
|||
|
$itemsByType = array(
|
|||
|
$this->_('(in page field)') => $references,
|
|||
|
$this->_('(in href link)') => $links
|
|||
|
);
|
|||
|
foreach($itemsByType as $label => $items) {
|
|||
|
$label = "<span class='detail'>$label</span>";
|
|||
|
foreach($items as $item) {
|
|||
|
/** @var Page $item */
|
|||
|
if($item->listable()) {
|
|||
|
$url = $item->editable() ? $item->editUrl() : $item->url();
|
|||
|
$out .= "<li><a href='$url' title='$item->url' target='_blank'>" . $item->get('title|path') . "</a> $label</li>";
|
|||
|
} else {
|
|||
|
$out .= "<li>$item->id $labelNotListable $label</li>";
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
$out .= "</ul>";
|
|||
|
if($numNotShown) {
|
|||
|
$out .= "<div class='notes'>" . sprintf($this->_('%d additional pages not shown.'), $numNotShown) . "</div>";
|
|||
|
}
|
|||
|
} else {
|
|||
|
$out = "<p>" . $this->_('Did not find any other pages pointing to this one in page fields or href links.') . "</p>";
|
|||
|
}
|
|||
|
|
|||
|
$field->value = $out;
|
|||
|
|
|||
|
return $field;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Build the “Settings > What URLs redirect to this page?” fieldset on the Page Edit form
|
|||
|
*
|
|||
|
* @return InputfieldMarkup|null
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function buildFormPrevPaths() {
|
|||
|
|
|||
|
$input = $this->input;
|
|||
|
$modules = $this->modules;
|
|||
|
$sanitizer = $this->sanitizer;
|
|||
|
$languages = $this->wire()->languages;
|
|||
|
$pages = $this->wire()->pages;
|
|||
|
|
|||
|
if($this->isPost && $input->post('_prevpath_add') === null) return null;
|
|||
|
if(!$modules->isInstalled('PagePathHistory')) return null;
|
|||
|
|
|||
|
/** @var InputfieldMarkup $field */
|
|||
|
$field = $modules->get('InputfieldMarkup');
|
|||
|
$field->attr('id', 'ProcessPageEditPrevPaths');
|
|||
|
$field->label = $this->_('What other URLs redirect to this page?');
|
|||
|
$field->icon = 'map-signs';
|
|||
|
|
|||
|
if(!$this->isPost) {
|
|||
|
$field->collapsed = Inputfield::collapsedYesAjax;
|
|||
|
if($input->get('renderInputfieldAjax') != 'ProcessPageEditPrevPaths') return $field;
|
|||
|
}
|
|||
|
|
|||
|
$field->description =
|
|||
|
$this->_('Whenever a page is moved or the name changes, we remember the previous location for redirects.') . ' ' .
|
|||
|
$this->_('Below is a list of URLs (paths) that automatically redirect to this page (using 301 permanent redirect).') . ' ' .
|
|||
|
$this->_('You may delete any paths/URLs or manually add new ones.');
|
|||
|
|
|||
|
/** @var PagePathHistory $history */
|
|||
|
$history = $modules->get('PagePathHistory');
|
|||
|
$data = $history->getPathHistory($this->page, array(
|
|||
|
'verbose' => true,
|
|||
|
'virtual' => true
|
|||
|
));
|
|||
|
|
|||
|
$multilang = $languages && $languages->hasPageNames();
|
|||
|
$slashUrls = $this->page->template->slashUrls;
|
|||
|
$deleteIDs = array();
|
|||
|
$rootUrl = $this->config->urls->root;
|
|||
|
|
|||
|
/** @var InputfieldCheckbox $delete */
|
|||
|
$delete = $modules->get('InputfieldCheckbox');
|
|||
|
$delete->label = wireIconMarkup('trash-o');
|
|||
|
$delete->attr('name', '_prevpath_delete[]');
|
|||
|
$delete->entityEncodeLabel = false;
|
|||
|
$delete->attr('title', $this->_x('Delete', 'prev-path-delete'));
|
|||
|
$delete->renderReady();
|
|||
|
|
|||
|
if($this->isPost && $this->form->isSubmitted('_prevpath_delete')) {
|
|||
|
$deleteIDs = array_flip($input->post->array('_prevpath_delete'));
|
|||
|
}
|
|||
|
|
|||
|
/** @var MarkupAdminDataTable $table */
|
|||
|
$table = $modules->get('MarkupAdminDataTable');
|
|||
|
$table->setEncodeEntities(false);
|
|||
|
$table->setSortable(false);
|
|||
|
|
|||
|
$header = array(
|
|||
|
$this->_x('URL', 'prev-path'),
|
|||
|
$this->_x('When', 'prev-path-date'),
|
|||
|
);
|
|||
|
|
|||
|
if(count($data)) {
|
|||
|
if($multilang) $header[] = $this->_x('Language', 'prev-path-language');
|
|||
|
$header[] = ' ';
|
|||
|
if(!$multilang) {
|
|||
|
$row = array(
|
|||
|
$sanitizer->entities($this->page->path),
|
|||
|
$this->_x('Current', 'prev-path-current'),
|
|||
|
' ',
|
|||
|
);
|
|||
|
$table->row($row);
|
|||
|
}
|
|||
|
} else {
|
|||
|
$table->row(array(
|
|||
|
$this->_('No redirect paths'),
|
|||
|
$this->_('Not yet')
|
|||
|
));
|
|||
|
}
|
|||
|
|
|||
|
$table->headerRow($header);
|
|||
|
|
|||
|
foreach($data as /*$n =>*/ $item) {
|
|||
|
|
|||
|
$id = md5($item['path'] . $item['date']);
|
|||
|
$path = $item['path'];
|
|||
|
|
|||
|
if($this->isPost && isset($deleteIDs[$id])) {
|
|||
|
if($history->deletePathHistory($this->page, $path)) {
|
|||
|
$this->message(sprintf($this->_('Deleted redirect for previous URL: %s'), $path));
|
|||
|
continue;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if($slashUrls) $path .= '/';
|
|||
|
|
|||
|
$url = $sanitizer->entities(rtrim($rootUrl, '/') . $path);
|
|||
|
$path = $sanitizer->entities($path);
|
|||
|
$row = array(
|
|||
|
"<a href='$url' target='_blank'>$path</a>",
|
|||
|
wireRelativeTimeStr($item['date']),
|
|||
|
);
|
|||
|
if($multilang && isset($item['language'])) {
|
|||
|
/** @var Language $language */
|
|||
|
$language = $item['language'];
|
|||
|
if($language && $language->id) {
|
|||
|
$langLabel = $language->get('title|name');
|
|||
|
if(!$language->isDefault() && !$this->page->get("status$language")) $langLabel = "<s>$langLabel</s>";
|
|||
|
$row[] = $langLabel;
|
|||
|
} else {
|
|||
|
$row[] = '?';
|
|||
|
}
|
|||
|
}
|
|||
|
if(empty($item['virtual'])) {
|
|||
|
$delete->attr('name', '_prevpath_delete[]');
|
|||
|
$delete->attr('value', $id);
|
|||
|
$row[] = "<div class='InputfieldCheckbox'>" . $delete->render() . "</div>";
|
|||
|
} else {
|
|||
|
$parentLabel = $this->_x('Parent', 'prev-path-parent');
|
|||
|
$parent = $pages->get((int) $item['virtual']);
|
|||
|
if($parent->id) $parentLabel = "<a target='_blank' title='$parent->path' href='$parent->editUrl'>$parentLabel</a>";
|
|||
|
$row[] = $parentLabel;
|
|||
|
}
|
|||
|
$table->row($row);
|
|||
|
}
|
|||
|
|
|||
|
/** @var InputfieldTextarea $add */
|
|||
|
$add = $modules->get('InputfieldTextarea');
|
|||
|
$add->attr('name', '_prevpath_add');
|
|||
|
$add->label = $this->_('Add new redirect URLs');
|
|||
|
$add->description =
|
|||
|
$this->_('Enter additional paths/URLs (one per line) that should redirect to this page.') . ' ' .
|
|||
|
$this->_('Enter the URL path only (i.e. “/hello/world/”), do NOT include scheme, domain, port, query string or fragments.') . ' ';
|
|||
|
if($rootUrl != '/') {
|
|||
|
$add->description .= sprintf(
|
|||
|
$this->_('Paths are relative to site root so do NOT include the %s subdirectory at the beginning.'),
|
|||
|
$rootUrl
|
|||
|
);
|
|||
|
}
|
|||
|
$add->collapsed = Inputfield::collapsedYes;
|
|||
|
$add->icon = 'plus';
|
|||
|
$add->addClass('InputfieldIsSecondary', 'wrapClass');
|
|||
|
if($multilang) {
|
|||
|
$add->notes = $this->_('To specify a language for the redirect, enter path/URL on line prefixed with language name:');
|
|||
|
foreach($languages->findNonDefault() as $language) {
|
|||
|
$add->notes .= "\n`$language->name:" .
|
|||
|
sprintf($this->_('/your/%s/url/'), $language->name) . "` " . // /your/[language-name]/url/
|
|||
|
sprintf($this->_('(for %s)'), $language->get('title|name')); // (for [language-title])
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if($this->isPost && $this->form->isSubmitted('_prevpath_add')) {
|
|||
|
$add->processInput($input->post);
|
|||
|
if($add->val()) {
|
|||
|
foreach(explode("\n", $add->val()) as $path) {
|
|||
|
if(strpos($path, ':')) {
|
|||
|
list($langName, $path) = explode(':', $path, 2);
|
|||
|
$language = $languages->get($sanitizer->pageName($langName));
|
|||
|
if(!$language || !$language->id) $language = null;
|
|||
|
} else {
|
|||
|
$language = null;
|
|||
|
}
|
|||
|
$path = $sanitizer->pagePathName($path);
|
|||
|
if(!strlen($path)) continue;
|
|||
|
if($history->addPathHistory($this->page, $path, $language)) {
|
|||
|
$this->message(sprintf(
|
|||
|
$this->_('Added redirect: %s'),
|
|||
|
$path
|
|||
|
));
|
|||
|
} else {
|
|||
|
$this->warning(sprintf(
|
|||
|
$this->_('Unable to add redirect %s because it appears to conflict with another path'),
|
|||
|
$path
|
|||
|
));
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
} else {
|
|||
|
$field->val($table->render());
|
|||
|
$field->add($add);
|
|||
|
}
|
|||
|
|
|||
|
return $field;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Build the Settings > Info fieldset on the Page Edit form
|
|||
|
*
|
|||
|
* @return InputfieldMarkup
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function buildFormInfo() {
|
|||
|
$page = $this->page;
|
|||
|
$dateFormat = $this->config->dateFormat;
|
|||
|
$unknown = '[?]';
|
|||
|
/** @var InputfieldMarkup $field */
|
|||
|
$field = $this->modules->get("InputfieldMarkup");
|
|||
|
$createdName = $page->createdUser ? $page->createdUser->name : '';
|
|||
|
$modifiedName = $page->modifiedUser ? $page->modifiedUser->name : '';
|
|||
|
if(empty($createdName)) $createdName = $unknown;
|
|||
|
if(empty($modifiedName)) $modifiedName = $unknown;
|
|||
|
if($this->user->isSuperuser()) {
|
|||
|
$url = $this->config->urls->admin . 'access/users/edit/?id=';
|
|||
|
if($createdName != $unknown && $page->createdUser instanceof User) $createdName = "<a href='$url{$page->createdUser->id}'>$createdName</a>";
|
|||
|
if($modifiedName != $unknown && $page->modifiedUser instanceof User) $modifiedName = "<a href='$url{$page->modifiedUser->id}'>$modifiedName</a>";
|
|||
|
}
|
|||
|
$lowestDate = strtotime('1974-10-10');
|
|||
|
$createdDate = $page->created > $lowestDate ? date($dateFormat, $page->created) . " " .
|
|||
|
"<span class='detail'>(" . wireRelativeTimeStr($page->created) . ")</span>" : $unknown;
|
|||
|
$modifiedDate = $page->modified > $lowestDate ? date($dateFormat, $page->modified) . " " .
|
|||
|
"<span class='detail'>(" . wireRelativeTimeStr($page->modified) . ")</span>" : $unknown;
|
|||
|
$publishedDate = $page->published > $lowestDate ? date($dateFormat, $page->published) . " " .
|
|||
|
"<span class='detail'>(" . wireRelativeTimeStr($page->published) . ")</span>" : $unknown;
|
|||
|
|
|||
|
$info = "\n<p>" .
|
|||
|
sprintf($this->_('Created by %1$s on %2$s'), $createdName, $createdDate) . "<br />" . // Settings: created user/date information line
|
|||
|
sprintf($this->_('Last modified by %1$s on %2$s'), $modifiedName, $modifiedDate) . "<br />" . // Settings: modified user/date information line
|
|||
|
sprintf($this->_('Published on %s'), $publishedDate) . // Settings: published information line
|
|||
|
"</p>";
|
|||
|
|
|||
|
$field->attr('id+name', 'ProcessPageEditInfo');
|
|||
|
$field->label = $this->_('Info'); // Settings: Info field label
|
|||
|
$field->icon = 'info-circle';
|
|||
|
if($this->config->advanced) $field->notes = "Object type: " . $page->className();
|
|||
|
$field->value = $info;
|
|||
|
|
|||
|
return $field;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Build the Settings > Status fieldset on the Page Edit form
|
|||
|
*
|
|||
|
* @return InputfieldCheckboxes
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function buildFormStatus() {
|
|||
|
|
|||
|
$status = (int) $this->page->status;
|
|||
|
$debug = $this->config->debug;
|
|||
|
$statuses = $this->getAllowedStatuses();
|
|||
|
|
|||
|
/** @var InputfieldCheckboxes $field */
|
|||
|
$field = $this->modules->get('InputfieldCheckboxes');
|
|||
|
$field->attr('name', 'status');
|
|||
|
$field->icon = 'sliders';
|
|||
|
|
|||
|
$value = array();
|
|||
|
|
|||
|
foreach($statuses as $s => $label) {
|
|||
|
if($s & $status) $value[] = $s;
|
|||
|
if(strpos($label, ': ')) $label = str_replace(': ', ': [span.detail]', $label) . '[/span]';
|
|||
|
$field->addOption($s, $label);
|
|||
|
}
|
|||
|
|
|||
|
$field->attr('value', $value);
|
|||
|
$field->label = $this->_('Status'); // Settings: Status field label
|
|||
|
|
|||
|
if($debug) $field->notes = $this->page->statusStr;
|
|||
|
|
|||
|
return $field;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Build the 'delete' tab on the Page Edit form
|
|||
|
*
|
|||
|
* @return InputfieldWrapper
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function ___buildFormDelete() {
|
|||
|
|
|||
|
/** @var InputfieldWrapper $wrapper */
|
|||
|
$wrapper = $this->wire(new InputfieldWrapper());
|
|||
|
$deleteable = $this->page->deleteable();
|
|||
|
$trashable = $deleteable || $this->page->trashable();
|
|||
|
|
|||
|
if(!$trashable) return $wrapper;
|
|||
|
|
|||
|
$id = $this->className() . 'Delete';
|
|||
|
$deleteLabel = $this->_('Delete'); // Tab Label: Delete
|
|||
|
$wrapper->attr('id', $id);
|
|||
|
$wrapper->attr('title', $deleteLabel);
|
|||
|
$this->addTab($id, $deleteLabel, $wrapper);
|
|||
|
|
|||
|
/** @var InputfieldCheckbox $field */
|
|||
|
$field = $this->modules->get('InputfieldCheckbox');
|
|||
|
$field->attr('id+name', 'delete_page');
|
|||
|
$field->attr('value', $this->page->id);
|
|||
|
|
|||
|
if($deleteable && ($this->isTrash || $this->page->template->noTrash)) {
|
|||
|
$deleteLabel = $this->_('Delete Permanently'); // Delete permanently checkbox label
|
|||
|
} else {
|
|||
|
$deleteLabel = $this->_('Move to Trash'); // Move to trash checkbox label
|
|||
|
}
|
|||
|
$field->icon = 'trash-o';
|
|||
|
$field->label = $deleteLabel;
|
|||
|
$field->description = $this->_('Check the box to confirm that you want to do this.'); // Delete page confirmation instruction
|
|||
|
$field->label2 = $this->_('Confirm');
|
|||
|
$wrapper->append($field);
|
|||
|
|
|||
|
if(count($wrapper->children())) {
|
|||
|
/** @var InputfieldButton $field */
|
|||
|
$field = $this->modules->get('InputfieldButton');
|
|||
|
$field->attr('id+name', 'submit_delete');
|
|||
|
$field->value = $deleteLabel;
|
|||
|
$wrapper->append($field);
|
|||
|
} else {
|
|||
|
$wrapper->description = $this->_('This page may not be deleted at this time'); // Page can't be deleted message
|
|||
|
}
|
|||
|
|
|||
|
return $wrapper;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Build the 'restore' tab shown for pages in the trash
|
|||
|
*
|
|||
|
* Returns boolean false if restore not possible.
|
|||
|
*
|
|||
|
* @return InputfieldWrapper|bool
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function buildFormRestore() {
|
|||
|
|
|||
|
if(!$this->page->isTrash()) return false;
|
|||
|
if(!$this->page->restorable()) return false;
|
|||
|
$info = $this->wire()->pages->trasher()->getRestoreInfo($this->page);
|
|||
|
if(!$info['restorable']) return false;
|
|||
|
|
|||
|
/** @var InputfieldWrapper $wrapper */
|
|||
|
$wrapper = $this->wire(new InputfieldWrapper());
|
|||
|
$id = $this->className() . 'Restore';
|
|||
|
$restoreLabel = $this->_('Restore'); // Tab Label: Restore
|
|||
|
$restoreLabel2 = $this->_('Move out of trash and restore to original location');
|
|||
|
$wrapper->attr('id', $id);
|
|||
|
$wrapper->attr('title', $restoreLabel);
|
|||
|
$this->addTab($id, $restoreLabel, $wrapper);
|
|||
|
/** @var Page $parent */
|
|||
|
$parent = $info['parent'];
|
|||
|
$newPath = $parent->path() . $info['name'] . '/';
|
|||
|
|
|||
|
/** @var InputfieldCheckbox $field */
|
|||
|
$field = $this->modules->get('InputfieldCheckbox');
|
|||
|
$field->attr('id+name', 'restore_page');
|
|||
|
$field->attr('value', $this->page->id);
|
|||
|
|
|||
|
$field->icon = 'trash-o';
|
|||
|
$field->label = $restoreLabel2;
|
|||
|
$field->description = $this->_('Check the box to confirm that you want to restore this page.'); // Restore page confirmation instruction
|
|||
|
$field->notes = sprintf($this->_('The page will be restored to: **%s**.'), $newPath);
|
|||
|
if($info['namePrevious']) $field->notes .= ' ' .
|
|||
|
sprintf($this->_('Original name will be adjusted from **%1$s** to **%2$s** to be unique.'), $info['namePrevious'], $info['name']);
|
|||
|
$field->label2 = $restoreLabel;
|
|||
|
$wrapper->append($field);
|
|||
|
|
|||
|
return $wrapper;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Build the 'view' tab on the Page Edit form
|
|||
|
*
|
|||
|
* @param string $url
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function ___buildFormView($url) {
|
|||
|
|
|||
|
$label = $this->_('View'); // Tab Label: View
|
|||
|
$id = $this->className() . 'View';
|
|||
|
|
|||
|
if((!empty($this->configSettings['viewNew'])) || $this->viewAction == 'new') {
|
|||
|
$target = '_blank';
|
|||
|
} else {
|
|||
|
$target = '_top';
|
|||
|
}
|
|||
|
|
|||
|
$a =
|
|||
|
"<a id='_ProcessPageEditView' target='$target' href='$url' data-action='$this->viewAction'>$label" .
|
|||
|
"<span id='_ProcessPageEditViewDropdownToggle' class='pw-dropdown-toggle' data-pw-dropdown='#_ProcessPageEditViewDropdown'>" .
|
|||
|
"<i class='fa fa-angle-down'></i></span></a>";
|
|||
|
|
|||
|
$this->addTab($id, $a);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Build the Settings > Roles fieldset on the Page Edit form
|
|||
|
*
|
|||
|
* @return InputfieldMarkup
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function ___buildFormRoles() {
|
|||
|
|
|||
|
/** @var InputfieldMarkup $field */
|
|||
|
$field = $this->modules->get("InputfieldMarkup");
|
|||
|
$field->label = $this->_('Who can access this page?'); // Roles information field label
|
|||
|
$field->icon = 'users';
|
|||
|
$field->attr('id+name', 'ProcessPageEditRoles');
|
|||
|
$field->collapsed = Inputfield::collapsedYesAjax;
|
|||
|
|
|||
|
/** @var MarkupAdminDataTable $table */
|
|||
|
$table = $this->modules->get("MarkupAdminDataTable");
|
|||
|
|
|||
|
$pageHasTemplateFile = $this->page->template->filenameExists();
|
|||
|
|
|||
|
if($this->input->get('renderInputfieldAjax') == 'ProcessPageEditRoles') {
|
|||
|
$roles = $this->page->getAccessRoles();
|
|||
|
$accessTemplate = $this->page->getAccessTemplate('edit');
|
|||
|
if($accessTemplate) {
|
|||
|
$editRoles = $accessTemplate->editRoles;
|
|||
|
$addRoles = $accessTemplate->addRoles;
|
|||
|
$createRoles = $accessTemplate->createRoles;
|
|||
|
} else {
|
|||
|
$editRoles = array();
|
|||
|
$addRoles = array();
|
|||
|
$createRoles = array();
|
|||
|
}
|
|||
|
|
|||
|
$table->headerRow(array(
|
|||
|
$this->_('Role'), // Roles table column header: Role
|
|||
|
$this->_('What they can do') // Roles table colum header: what they can do
|
|||
|
));
|
|||
|
$table->setEncodeEntities(false);
|
|||
|
$addLabel = 'add';
|
|||
|
|
|||
|
if(count($roles)) {
|
|||
|
|
|||
|
$hasPublishPermission = $this->wire()->permissions->has('page-publish');
|
|||
|
|
|||
|
foreach($roles as $role) {
|
|||
|
/** @var Role $role */
|
|||
|
|
|||
|
$permissions = array();
|
|||
|
$roleName = $role->name;
|
|||
|
if($roleName == 'guest') $roleName .= " " . $this->_('(everyone)'); // Identifies who guest is (everyone)
|
|||
|
$permissions["page-view"] = 'view' . ($pageHasTemplateFile ? '' : '¹');
|
|||
|
|
|||
|
$checkEditable = true;
|
|||
|
if($hasPublishPermission && !$this->page->hasStatus(Page::statusUnpublished)
|
|||
|
&& !$role->hasPermission('page-publish', $this->page)) {
|
|||
|
$checkEditable = false;
|
|||
|
}
|
|||
|
|
|||
|
$key = array_search($role->id, $addRoles);
|
|||
|
if($key !== false && $role->hasPermission('page-add', $this->page)) {
|
|||
|
$permissions["page-add"] = 'add';
|
|||
|
unset($addRoles[$key]);
|
|||
|
}
|
|||
|
|
|||
|
$editable = $role->hasPermission('page-edit', $this->page) && in_array($role->id, $editRoles);
|
|||
|
|
|||
|
if($checkEditable && $editable) {
|
|||
|
|
|||
|
foreach($role->permissions as $permission) {
|
|||
|
if(strpos($permission->name, 'page-') !== 0) continue;
|
|||
|
if(in_array($permission->name, array('page-view', 'page-publish', 'page-create', 'page-add'))) continue;
|
|||
|
if(!$role->hasPermission($permission, $this->page)) continue;
|
|||
|
$permissions[$permission->name] = str_replace('page-', '', $permission->name); // only page-context permissions
|
|||
|
}
|
|||
|
|
|||
|
if($hasPublishPermission && $role->hasPermission('page-publish', $this->page)) {
|
|||
|
$permissions["page-publish"] = 'publish';
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if(in_array($role->id, $createRoles) && $editable) {
|
|||
|
$permissions["page-create"] = 'create';
|
|||
|
}
|
|||
|
|
|||
|
$table->row(array($roleName, implode(', ', $permissions)));
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
if(count($addRoles)) {
|
|||
|
foreach($addRoles as $roleID) {
|
|||
|
$role = $this->wire('roles')->get($roleID);
|
|||
|
if(!$role->id) continue;
|
|||
|
if(!$role->hasPermission("page-add", $this->page)) continue;
|
|||
|
$table->row(array($role->name, $addLabel));
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$table->row(array('superuser', $this->_x('all', 'all permissions')));
|
|||
|
$field->value = $table->render();
|
|||
|
}
|
|||
|
|
|||
|
$accessParent = $this->page->getAccessParent();
|
|||
|
if($accessParent === $this->page) {
|
|||
|
$field->notes = sprintf($this->_('Access is defined with this page’s template: %s'), $accessParent->template); // Where access is defined: with this page's template
|
|||
|
} else {
|
|||
|
$field->notes = sprintf($this->_('Access is inherited from page "%1$s" and defined with template: %2$s'), $accessParent->path, $accessParent->template); // Where access is defined: inherited from a parent
|
|||
|
}
|
|||
|
if(!$pageHasTemplateFile) {
|
|||
|
$field->notes = trim("¹ " . $this->_('Viewable by its URL if page had a template file (it does not currently).') . "\n$field->notes");
|
|||
|
}
|
|||
|
|
|||
|
return $field;
|
|||
|
}
|
|||
|
|
|||
|
/***********************************************************************************************************************
|
|||
|
* FORM PROCESSING
|
|||
|
*
|
|||
|
*/
|
|||
|
|
|||
|
/**
|
|||
|
* Save a submitted Page Edit form
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function processSave() {
|
|||
|
|
|||
|
$input = $this->wire()->input;
|
|||
|
$pages = $this->wire()->pages;
|
|||
|
$page = $this->page;
|
|||
|
$user = $this->user;
|
|||
|
$form = $this->form;
|
|||
|
|
|||
|
if($page->hasStatus(Page::statusLocked)) {
|
|||
|
$inputStatus = $input->post('status');
|
|||
|
if(!$user->hasPermission('page-lock', $page) || (is_array($inputStatus) && in_array(Page::statusLocked, $inputStatus))) {
|
|||
|
$this->error($this->noticeLocked);
|
|||
|
$this->processSaveRedirect($this->redirectUrl);
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$formErrors = 0;
|
|||
|
|
|||
|
// remove temporary status that may have been assigned by ProcessPageAdd quick add mode
|
|||
|
if($page->hasStatus(Page::statusTemp)) $page->removeStatus(Page::statusTemp);
|
|||
|
|
|||
|
if($form->isSubmitted('submit_delete')) {
|
|||
|
|
|||
|
if(((int) $input->post('delete_page')) === $this->page->id) $this->deletePage();
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
$this->processInput($form);
|
|||
|
$changes = array_unique($page->getChanges());
|
|||
|
$numChanges = count($changes);
|
|||
|
if($numChanges) {
|
|||
|
$this->changes = $changes;
|
|||
|
$this->message(sprintf($this->_('Change: %s'), implode(', ', $changes)), Notice::debug); // Message shown for each changed field
|
|||
|
}
|
|||
|
|
|||
|
foreach($this->notices as $notice) {
|
|||
|
if($notice instanceof NoticeError) $formErrors++;
|
|||
|
}
|
|||
|
|
|||
|
// if any Inputfields threw errors during processing, give the page a 'flagged' status
|
|||
|
// so that it can later be identified the page may be missing something
|
|||
|
if($formErrors && count($form->getErrors())) {
|
|||
|
// add flagged status when form had errors
|
|||
|
$page->addStatus(Page::statusFlagged);
|
|||
|
} else if($page->hasStatus(Page::statusFlagged)) {
|
|||
|
// if no errors, remove incomplete status
|
|||
|
$page->removeStatus(Page::statusFlagged);
|
|||
|
$this->message($this->_('Removed flagged status because no errors reported during save'));
|
|||
|
}
|
|||
|
|
|||
|
$isUnpublished = $page->hasStatus(Page::statusUnpublished);
|
|||
|
|
|||
|
if($input->post('submit_publish') || $input->post('submit_save')) {
|
|||
|
|
|||
|
try {
|
|||
|
$options = array();
|
|||
|
$name = '';
|
|||
|
|
|||
|
if($page->isChanged('name')) {
|
|||
|
if(!strlen($page->name) && $page->namePrevious) {
|
|||
|
// blank page name when there was a previous name, set back the previous
|
|||
|
// example instance: when template.childNameFormat in use and template.noSettings active
|
|||
|
$page->name = $page->namePrevious;
|
|||
|
} else {
|
|||
|
$name = $page->name;
|
|||
|
}
|
|||
|
$options['adjustName'] = true;
|
|||
|
}
|
|||
|
|
|||
|
$numChanges = $numChanges > 0 ? ' (' . sprintf($this->_n('%d change', '%d changes', $numChanges) . ')', $numChanges) : '';
|
|||
|
if($input->post('submit_publish') && $isUnpublished && $this->page->publishable() && !$formErrors) {
|
|||
|
$page->removeStatus(Page::statusUnpublished);
|
|||
|
$message = sprintf($this->_('Published Page: %s'), '{path}') . $numChanges; // Message shown when page is published
|
|||
|
} else {
|
|||
|
$message = sprintf($this->_('Saved Page: %s'), '{path}') . $numChanges; // Message shown when page is saved
|
|||
|
if($isUnpublished && $formErrors && $input->post('submit_publish')) {
|
|||
|
$message .= ' - ' . $this->_('Cannot be published until errors are corrected');
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$restored = false;
|
|||
|
if($input->post('restore_page') && $page->isTrash() && $page->restorable()) {
|
|||
|
if($formErrors) {
|
|||
|
$this->warning($this->_('Page cannot be restored while errors are present'));
|
|||
|
} else if($pages->restore($page, false)) {
|
|||
|
$message = sprintf($this->_('Restored Page: %s'), '{path}') . $numChanges;
|
|||
|
$restored = true;
|
|||
|
} else {
|
|||
|
$this->warning($this->_('Error restoring page'));
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$pages->save($page, $options);
|
|||
|
if($restored) $pages->restored($page);
|
|||
|
$message = str_replace('{path}', $page->path, $message);
|
|||
|
$this->message($message);
|
|||
|
|
|||
|
if($name && $name != $page->name) {
|
|||
|
$this->warning(sprintf($this->_('Changed page URL name to "%s" because requested name was already taken.'), $this->page->name));
|
|||
|
}
|
|||
|
|
|||
|
} catch(\Exception $e) {
|
|||
|
$show = true;
|
|||
|
$message = $e->getMessage();
|
|||
|
foreach($this->errors('all') as $error) {
|
|||
|
if(strpos($error, $message) === false) continue;
|
|||
|
$show = false;
|
|||
|
break;
|
|||
|
}
|
|||
|
if($show) $this->error($message);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if($this->redirectUrl) {
|
|||
|
// non-default redirectUrl overrides after_submit_action
|
|||
|
} else if($formErrors) {
|
|||
|
// if there were errors to attend to, stay where we are
|
|||
|
} else {
|
|||
|
// after submit action
|
|||
|
$submitAction = $input->post('_after_submit_action');
|
|||
|
if($submitAction) $this->processSubmitAction($submitAction);
|
|||
|
}
|
|||
|
|
|||
|
$this->processSaveRedirect($this->getRedirectUrl());
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Process the given submit action value
|
|||
|
*
|
|||
|
* #pw-hooker
|
|||
|
*
|
|||
|
* @param string $value Value of selected action, i.e. 'exit', 'view', 'add', next', etc.
|
|||
|
* @return bool Returns true if value was acted upon or false if not
|
|||
|
* @since 3.0.142
|
|||
|
* @see ___getSubmitActions(), setRedirectUrl()
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function ___processSubmitAction($value) {
|
|||
|
|
|||
|
if($value == 'exit') {
|
|||
|
$this->setRedirectUrl('../');
|
|||
|
|
|||
|
} else if($value == 'view') {
|
|||
|
$this->setRedirectUrl($this->getViewUrl());
|
|||
|
|
|||
|
} else if($value == 'add') {
|
|||
|
$this->setRedirectUrl("../add/?parent_id={$this->page->parent_id}");
|
|||
|
|
|||
|
} else if($value == 'next') {
|
|||
|
$nextPage = $this->page->next("include=unpublished");
|
|||
|
if($nextPage->id) {
|
|||
|
if(!$nextPage->editable()) {
|
|||
|
$nextPage = $this->page->next("include=hidden");
|
|||
|
if($nextPage->id && !$nextPage->editable()) {
|
|||
|
$nextPage = $this->page->next();
|
|||
|
if($nextPage->id && !$nextPage->editable()) $nextPage = new NullPage();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
if($nextPage->id) {
|
|||
|
$this->setRedirectUrl($this->getEditUrl(array('id' => $nextPage->id)));
|
|||
|
} else {
|
|||
|
$this->warning($this->_('There is no editable next page to edit.'));
|
|||
|
}
|
|||
|
|
|||
|
} else {
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Perform an after save redirect
|
|||
|
*
|
|||
|
* @param string $redirectUrl
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function ___processSaveRedirect($redirectUrl = '') {
|
|||
|
if($redirectUrl) {
|
|||
|
$c = substr($redirectUrl, 0, 1);
|
|||
|
$admin = $c === '.' || $c === '?' || strpos($redirectUrl, $this->config->urls->admin) === 0;
|
|||
|
if($admin) {
|
|||
|
$redirectUrl .= (strpos($redirectUrl, '?') === false ? '?' : '&') . 's=1';
|
|||
|
}
|
|||
|
} else {
|
|||
|
$admin = true;
|
|||
|
$redirectUrl = $this->getEditUrl(array('s' => 1));
|
|||
|
}
|
|||
|
if($admin) {
|
|||
|
$redirectUrl .= "&c=" . count($this->changes);
|
|||
|
if(count($this->fields) && count($this->changes)) {
|
|||
|
$redirectUrl .= "&changes=" . implode(',', $this->changes);
|
|||
|
}
|
|||
|
}
|
|||
|
$this->setRedirectUrl($redirectUrl);
|
|||
|
$this->session->location($this->getRedirectUrl());
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Process the input from a submitted Page Edit form, delegating to other methods where appropriate
|
|||
|
*
|
|||
|
* @param InputfieldWrapper $form
|
|||
|
* @param int $level
|
|||
|
* @param Inputfield $formRoot
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function ___processInput(InputfieldWrapper $form, $level = 0, $formRoot = null) {
|
|||
|
|
|||
|
$input = $this->wire()->input;
|
|||
|
$languages = $this->wire()->languages;
|
|||
|
$page = $this->page;
|
|||
|
|
|||
|
static $skipFields = array(
|
|||
|
'sortfield_reverse',
|
|||
|
'submit_publish',
|
|||
|
'submit_save',
|
|||
|
'delete_page',
|
|||
|
);
|
|||
|
|
|||
|
if(!$level) {
|
|||
|
$form->processInput($input->post);
|
|||
|
$formRoot = $form;
|
|||
|
$page->setQuietly('_forceAddStatus', 0);
|
|||
|
}
|
|||
|
|
|||
|
$errorAction = (int) $page->template->errorAction;
|
|||
|
|
|||
|
foreach($form as $inputfield) {
|
|||
|
|
|||
|
/** @var Inputfield|InputfieldWrapper $inputfield */
|
|||
|
|
|||
|
$name = $inputfield->attr('name');
|
|||
|
if($name == '_pw_page_name') $name = 'name';
|
|||
|
if(in_array($name, $skipFields)) continue;
|
|||
|
|
|||
|
if(!$page->editable($name, false)) {
|
|||
|
$page->untrackChange($name); // just in case
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
if($name == 'sortfield' && $this->useChildren && $form->isProcessable($inputfield->parent->parent)) {
|
|||
|
$this->processInputSortfield($inputfield) ;
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
if($this->useSettings) {
|
|||
|
|
|||
|
if($name == 'template') {
|
|||
|
$this->processInputTemplate($inputfield);
|
|||
|
continue;
|
|||
|
|
|||
|
} else if($name == 'created_users_id') {
|
|||
|
$this->processInputUser($inputfield);
|
|||
|
continue;
|
|||
|
|
|||
|
} else if($name == 'parent_id' && count($this->predefinedParents)) {
|
|||
|
if(!$this->predefinedParents->has("id=$inputfield->value")) {
|
|||
|
$this->error("Parent $inputfield->value is not allowed for $page");
|
|||
|
continue;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if($name == 'status' && $this->processInputStatus($inputfield)) continue;
|
|||
|
}
|
|||
|
|
|||
|
if($this->processInputErrorAction($page, $inputfield, $name, $errorAction)) continue;
|
|||
|
|
|||
|
if($name && $inputfield->isChanged()) {
|
|||
|
if($languages && $inputfield->getSetting('useLanguages')) {
|
|||
|
$v = $page->get($name);
|
|||
|
if(is_object($v)) {
|
|||
|
$v = clone $v;
|
|||
|
$v->setFromInputfield($inputfield);
|
|||
|
$page->set($name, $v);
|
|||
|
} else {
|
|||
|
$page->set($name, $inputfield->val());
|
|||
|
}
|
|||
|
} else {
|
|||
|
$page->set($name, $inputfield->val());
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if($inputfield instanceof InputfieldWrapper && count($inputfield->getChildren())) {
|
|||
|
$this->processInput($inputfield, $level + 1, $formRoot);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if(!$level) {
|
|||
|
$forceAddStatus = $page->get('_forceAddStatus');
|
|||
|
if($forceAddStatus && !$page->hasStatus($forceAddStatus)) {
|
|||
|
$page->addStatus($forceAddStatus);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Process required error actions as configured with page’s template
|
|||
|
*
|
|||
|
* @param Page $page
|
|||
|
* @param Inputfield|InputfieldRepeater $inputfield Inputfield that has already had its processInput() method called.
|
|||
|
* @param string $name Name of field that we are checking.
|
|||
|
* @param null|int $errorAction Error action from $page->template->errorAction, or omit to auto-detect.
|
|||
|
* @return bool Returns true if field $name should be skipped over during processing, or false if not
|
|||
|
*
|
|||
|
*/
|
|||
|
public function processInputErrorAction(Page $page, Inputfield $inputfield, $name, $errorAction = null) {
|
|||
|
|
|||
|
if(empty($name)) return false;
|
|||
|
if($errorAction === null) $errorAction = (int) $page->template->get('errorAction');
|
|||
|
if(!$errorAction) return false;
|
|||
|
if($page->isUnpublished()) return false;
|
|||
|
|
|||
|
$isRequired = $inputfield->getSetting('required');
|
|||
|
$isRepeater = strpos($inputfield->className(), 'Repeater') > 0 && wireInstanceOf($inputfield, 'InputfieldRepeater', false);
|
|||
|
|
|||
|
if(!$isRepeater && !$isRequired) return false;
|
|||
|
if($inputfield->getSetting('requiredSkipped')) return false;
|
|||
|
|
|||
|
if($isRepeater) {
|
|||
|
if($inputfield->numRequiredEmpty() > 0) {
|
|||
|
// repeater has required fields that are empty
|
|||
|
} else if($isRequired && $inputfield->numPublished() < 1) {
|
|||
|
// repeater is required and has no published items
|
|||
|
} else {
|
|||
|
// repeater is okay for now
|
|||
|
return false;
|
|||
|
}
|
|||
|
} else if(!$inputfield->isEmpty()) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
if($errorAction === 1) {
|
|||
|
// restore existing value by skipping processing of empty when required
|
|||
|
$value = $inputfield->attr('value');
|
|||
|
if($value instanceof Wire) $value->resetTrackChanges();
|
|||
|
if($page->getField($name)) $page->remove($name); // force fresh copy to reload
|
|||
|
$previousValue = $page->get($name);
|
|||
|
$page->untrackChange($name);
|
|||
|
if($previousValue) {
|
|||
|
// we should have a previous value to restore
|
|||
|
if(WireArray::iterable($previousValue) && !count($previousValue)) {
|
|||
|
// previous value still empty
|
|||
|
} else {
|
|||
|
// previous value restored by simply not setting new value to $page
|
|||
|
$inputfield->error($this->_('Restored previous value'));
|
|||
|
return true;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
} else if($errorAction === 2 && $page->publishable() && $page->id > 1) {
|
|||
|
// unpublish page missing required value
|
|||
|
$page->setQuietly('_forceAddStatus', Page::statusUnpublished);
|
|||
|
$label = $inputfield->getSetting('label');
|
|||
|
if(empty($label)) $label = $inputfield->attr('name');
|
|||
|
$inputfield->error(sprintf($this->_('Page unpublished because field "%s" is required'), $label));
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Check to see if the page's created user has changed and make sure it's valid
|
|||
|
*
|
|||
|
* @param Inputfield $inputfield
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function processInputUser(Inputfield $inputfield) {
|
|||
|
$page = $this->page;
|
|||
|
if(!$this->user->isSuperuser() || !$page->id || !$page->template->allowChangeUser) return;
|
|||
|
$userID = $inputfield->val();
|
|||
|
if(is_array($userID)) $userID = reset($userID);
|
|||
|
if($userID instanceof PageArray) $userID = $userID->first();
|
|||
|
if($userID instanceof Page) $userID = $userID->id;
|
|||
|
$userID = (int) $userID;
|
|||
|
if(!$userID) return;
|
|||
|
if($userID == $this->page->created_users_id) return; // no change
|
|||
|
$user = $this->pages->get($userID);
|
|||
|
if(!$user->id) return;
|
|||
|
if(!in_array($user->template->id, $this->config->userTemplateIDs)) return; // invalid user template
|
|||
|
if(!in_array($user->parent_id, $this->config->usersPageIDs)) return; // invalid user parent
|
|||
|
$page->created_users_id = $user->id;
|
|||
|
$page->trackChange('created_users_id');
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Check to see if the page's template has changed and setup a redirect to a confirmation form if it has
|
|||
|
*
|
|||
|
* @param Inputfield $inputfield
|
|||
|
* @return bool
|
|||
|
* @throws WireException
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function processInputTemplate(Inputfield $inputfield) {
|
|||
|
if($this->page->template->noChangeTemplate) return true;
|
|||
|
$templateID = (int) $inputfield->attr('value');
|
|||
|
if(!$templateID) return true;
|
|||
|
$template = $this->wire()->templates->get((int) $inputfield->val());
|
|||
|
if(!$template) return true; // invalid template
|
|||
|
if($template->id == $this->page->template->id) return true; // no change
|
|||
|
if(!$this->isAllowedTemplate($template)) {
|
|||
|
throw new WireException(sprintf($this->_("Template '%s' is not allowed"), $template)); // Selected template is not allowed
|
|||
|
}
|
|||
|
|
|||
|
// template has changed, set a redirect URL which will confirm the change
|
|||
|
$this->setRedirectUrl("template?id={$this->page->id}&template=$template->id");
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Process the submitted 'status' field and account for the bitwise logic present
|
|||
|
*
|
|||
|
* @param Inputfield $inputfield
|
|||
|
* @return bool
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function processInputStatus(Inputfield $inputfield) {
|
|||
|
|
|||
|
$inputStatusFlags = $inputfield->val();
|
|||
|
if(!is_array($inputStatusFlags)) $inputStatusFlags = array();
|
|||
|
foreach($inputStatusFlags as $k => $v) $inputStatusFlags[$k] = (int) $v;
|
|||
|
|
|||
|
$allowedStatusFlags = array_keys($this->getAllowedStatuses());
|
|||
|
$statusLabels = array_flip(Page::getStatuses());
|
|||
|
$value = $this->page->status;
|
|||
|
|
|||
|
foreach($allowedStatusFlags as $flag) {
|
|||
|
if(in_array($flag, $inputStatusFlags, true)) {
|
|||
|
if($value & $flag) {
|
|||
|
// already has flag
|
|||
|
} else {
|
|||
|
$value = $value | $flag; // add status
|
|||
|
$this->message(sprintf($this->_('Added status: %s'), $statusLabels[$flag]), Notice::debug);
|
|||
|
}
|
|||
|
} else if($value & $flag) {
|
|||
|
$value = $value & ~$flag; // remove flag
|
|||
|
$this->message(sprintf($this->_('Removed status: %s'), $statusLabels[$flag]), Notice::debug);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$this->page->status = $value;
|
|||
|
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Process the Children > Sortfield input
|
|||
|
*
|
|||
|
* @param Inputfield $inputfield
|
|||
|
* @return bool
|
|||
|
*
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function processInputSortfield(Inputfield $inputfield) {
|
|||
|
if(!$this->user->hasPermission('page-sort', $this->page)) return true;
|
|||
|
$sortfield = $this->sanitizer->name($inputfield->val());
|
|||
|
if($sortfield != 'sort' && !empty($_POST['sortfield_reverse'])) $sortfield = '-' . $sortfield;
|
|||
|
if(empty($sortfield)) $sortfield = 'sort';
|
|||
|
$this->page->sortfield = $sortfield;
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Process a delete page request, moving the page to the trash if applicable
|
|||
|
*
|
|||
|
* @return bool
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function deletePage() {
|
|||
|
|
|||
|
$page = $this->page;
|
|||
|
|
|||
|
if(!$page->trashable(true)) {
|
|||
|
$this->error($this->_('This page is not deleteable'));
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
$redirectUrl = $this->wire()->config->urls->admin . "page/?open={$this->parent->id}";
|
|||
|
if($this->wire()->page->process != $this->className()) $redirectUrl = "../";
|
|||
|
$pagePath = $page->path();
|
|||
|
|
|||
|
if(($this->isTrash || $page->template->noTrash) && $page->deleteable()) {
|
|||
|
$this->wire()->session->message(sprintf($this->_('Deleted page: %s'), $pagePath)); // Page deleted message
|
|||
|
$this->pages->delete($page, true);
|
|||
|
$this->deletedPage($page, $redirectUrl, false);
|
|||
|
|
|||
|
} else if($this->pages->trash($page)) {
|
|||
|
$this->wire()->session->message(sprintf($this->_('Moved page to trash: %s'), $pagePath)); // Page moved to trash message
|
|||
|
$this->deletedPage($page, $redirectUrl, true);
|
|||
|
|
|||
|
} else {
|
|||
|
$this->error($this->_('Unable to move page to trash')); // Page can't be moved to the trash error
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Called after a page has been deleted or trashed, performs redirect
|
|||
|
*
|
|||
|
* @param Page $page Page that was deleted or trashed
|
|||
|
* @param string $redirectUrl URL that should be redirected to
|
|||
|
* @param bool $trashed True if page was trashed rather than deleted
|
|||
|
* @since 3.0.173
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function ___deletedPage($page, $redirectUrl, $trashed = false) {
|
|||
|
if($page || $trashed) {} // ignore
|
|||
|
$this->wire()->session->location($redirectUrl);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Save only the fields posted via ajax
|
|||
|
*
|
|||
|
* - Field name must be included in server header HTTP_X_FIELDNAME or directly in the POST vars.
|
|||
|
* - Note that fields that would be not present in POST vars (like a checkbox) are only supported
|
|||
|
* by the HTTP_X_FIELDNAME version.
|
|||
|
* - Works for custom fields only at present.
|
|||
|
*
|
|||
|
* @param Page $page
|
|||
|
* @throws WireException
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function ___ajaxSave(Page $page) {
|
|||
|
|
|||
|
if($this->config->demo) throw new WireException("Ajax save is disabled in demo mode");
|
|||
|
if($page->hasStatus(Page::statusLocked)) throw new WireException($this->noticeLocked);
|
|||
|
if(!$this->ajaxEditable($page)) throw new WirePermissionException($this->noticeNoAccess);
|
|||
|
|
|||
|
$this->wire()->session->CSRF->validate(); // throws exception when invalid
|
|||
|
|
|||
|
/** @var InputfieldWrapper $form */
|
|||
|
$form = $this->wire(new InputfieldWrapper());
|
|||
|
$form->useDependencies = false;
|
|||
|
$keys = array();
|
|||
|
$error = '';
|
|||
|
$message = '';
|
|||
|
|
|||
|
if(isset($_SERVER['HTTP_X_FIELDNAME'])) {
|
|||
|
$keys[] = $this->sanitizer->fieldName($_SERVER['HTTP_X_FIELDNAME']);
|
|||
|
|
|||
|
} else if(count($this->fields)) {
|
|||
|
$keys = array_keys($this->fields);
|
|||
|
|
|||
|
} else {
|
|||
|
foreach($this->input->post as $key => $unused) {
|
|||
|
if($key === 'id') continue;
|
|||
|
$keys[] = $this->sanitizer->fieldName($key);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
foreach($keys as $key) {
|
|||
|
|
|||
|
if(!$field = $page->template->fieldgroup->getFieldContext($key)) continue;
|
|||
|
if(!$this->ajaxEditable($page, $key)) continue;
|
|||
|
if(!$inputfield = $field->getInputfield($page)) continue;
|
|||
|
|
|||
|
$inputfield->showIf = ''; // cancel showIf dependencies since other fields may not be present
|
|||
|
$inputfield->name = $key;
|
|||
|
$inputfield->value = $page->get($key);
|
|||
|
$form->add($inputfield);
|
|||
|
}
|
|||
|
|
|||
|
$form->processInput($this->input->post);
|
|||
|
$page->setTrackChanges(true);
|
|||
|
$pageClass = $page->className();
|
|||
|
$numFields = 0;
|
|||
|
$lastFieldName = null;
|
|||
|
$savedNames = array();
|
|||
|
$saved = false; // was page saved?
|
|||
|
$languages = $this->wire()->languages;
|
|||
|
$changes = array();
|
|||
|
|
|||
|
foreach($form->children() as $inputfield) {
|
|||
|
/** @var Inputfield $inputfield */
|
|||
|
$name = $inputfield->attr('name');
|
|||
|
if($languages && $inputfield->getSetting('useLanguages')) {
|
|||
|
$v = $page->get($name);
|
|||
|
if(is_object($v)) {
|
|||
|
$v = clone $v;
|
|||
|
$v->setFromInputfield($inputfield);
|
|||
|
$page->set($name, $v);
|
|||
|
} else {
|
|||
|
$page->set($name, $inputfield->val());
|
|||
|
}
|
|||
|
} else {
|
|||
|
$page->set($name, $inputfield->val());
|
|||
|
}
|
|||
|
$numFields++;
|
|||
|
$lastFieldName = $inputfield->name;
|
|||
|
$savedNames[] = $lastFieldName;
|
|||
|
if($inputfield instanceof InputfieldFile) $page->trackChange($name);
|
|||
|
}
|
|||
|
|
|||
|
if($page->isChanged()) {
|
|||
|
$changes = $page->getChanges();
|
|||
|
if($numFields === 1) {
|
|||
|
if($page->save((string) $lastFieldName)) {
|
|||
|
$saved = true;
|
|||
|
$message = "Ajax saved $pageClass $page->id field: $lastFieldName";
|
|||
|
} else {
|
|||
|
$error = "Ajax error saving $pageClass $page->id field: $lastFieldName";
|
|||
|
}
|
|||
|
} else {
|
|||
|
if($page->save()) {
|
|||
|
$saved = true;
|
|||
|
$message = "Ajax saved $pageClass $page->id fields: " . implode(', ', $savedNames);
|
|||
|
} else {
|
|||
|
$error = "Ajax error saving $pageClass $page->id fields: " . implode(', ', $savedNames);
|
|||
|
}
|
|||
|
}
|
|||
|
} else {
|
|||
|
$message = "Ajax $pageClass $page->id not saved (no changes)";
|
|||
|
$savedNames = array();
|
|||
|
}
|
|||
|
|
|||
|
$data = array(
|
|||
|
'fields' => $savedNames,
|
|||
|
'changes' => $changes,
|
|||
|
'error' => strlen($error) > 0,
|
|||
|
'message' => ($error ? $error : $message),
|
|||
|
'saved' => $saved,
|
|||
|
);
|
|||
|
|
|||
|
if(!$this->ajaxSaveDone($page, $data)) {
|
|||
|
if($message) $this->message($message);
|
|||
|
if($error) $this->error($error);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Ajax save done - send output
|
|||
|
*
|
|||
|
* When a hook overrides this, it should hook after and set `$event->return = true;`
|
|||
|
* to indicate that it has handled the output.
|
|||
|
* ~~~~~
|
|||
|
* $wire->addHookAfter('ProcessPageEdit::ajaxSaveDone', function($event) {
|
|||
|
* if($event->return === true) return; // another hook already handled output
|
|||
|
* $page = $event->arguments(0); // Page
|
|||
|
* $data = $event->arguments(1); // array
|
|||
|
* $data['page'] = $page->id;
|
|||
|
* header('Content-Type', 'application/json');
|
|||
|
* echo json_encode($data);
|
|||
|
* $event->return = true; // tell ProcessPageEdit we handled output
|
|||
|
* });
|
|||
|
* ~~~~~
|
|||
|
*
|
|||
|
* #pw-hooker
|
|||
|
*
|
|||
|
* @param Page $page
|
|||
|
* @param array $data
|
|||
|
* @return bool Return true if hook has handled output, false if not (default)
|
|||
|
* @since 3.0.188
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function ___ajaxSaveDone(Page $page, array $data) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/***************************************************************************************************************
|
|||
|
* OTHER ACTIONS
|
|||
|
*
|
|||
|
*/
|
|||
|
|
|||
|
/**
|
|||
|
* Build template form
|
|||
|
*
|
|||
|
* @param Template $template Proposed template to change to
|
|||
|
* @return InputfieldForm
|
|||
|
* @throws WireException
|
|||
|
* @since 3.0.205
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function buildTemplateForm(Template $template) {
|
|||
|
|
|||
|
if($this->page->template->noChangeTemplate) {
|
|||
|
throw new WireException("Template changes not allowed by pages using template: $template");
|
|||
|
}
|
|||
|
|
|||
|
if(!$this->useSettings || !$this->user->hasPermission('page-template', $this->page)) {
|
|||
|
throw new WireException("You don't have permission to change the template on this page.");
|
|||
|
}
|
|||
|
|
|||
|
if(!$this->isAllowedTemplate($template->id)) {
|
|||
|
throw new WireException("That template is not allowed");
|
|||
|
}
|
|||
|
|
|||
|
$labelConfirm = $this->_('Confirm template change'); // Change template confirmation subhead
|
|||
|
$labelAction = sprintf($this->_('Change template from "%1$s" to "%2$s"'), $this->page->template, $template->name); // Change template A to B headline
|
|||
|
|
|||
|
$this->headline($labelConfirm);
|
|||
|
if($this->requestModal) $this->error("$labelConfirm – $labelAction"); // force modal open
|
|||
|
|
|||
|
/** @var InputfieldForm $form */
|
|||
|
$form = $this->modules->get("InputfieldForm");
|
|||
|
$form->attr('action', 'saveTemplate');
|
|||
|
$form->attr('method', 'post');
|
|||
|
$form->description = $labelAction;
|
|||
|
|
|||
|
/** @var InputfieldMarkup $f */
|
|||
|
$f = $this->modules->get("InputfieldMarkup");
|
|||
|
$f->icon = 'cubes';
|
|||
|
$f->label = $labelConfirm;
|
|||
|
$list = array();
|
|||
|
foreach($this->page->template->fieldgroup as $field) {
|
|||
|
/** @var Field $field */
|
|||
|
if(!$template->fieldgroup->has($field)) {
|
|||
|
$list[] = $this->sanitizer->entities($field->getLabel()) . " ($field->name)";
|
|||
|
}
|
|||
|
}
|
|||
|
if(count($list)) {
|
|||
|
$f->description = $this->_('Warning, changing the template will delete the following fields:'); // Headline that precedes list of fields that will be deleted as a result of template change
|
|||
|
$icon = wireIconMarkup('times-circle');
|
|||
|
$f->attr('value', "<p class='ui-state-error-text'>$icon " . implode("<br />$icon ", $list) . '</p>');
|
|||
|
$form->append($f);
|
|||
|
}
|
|||
|
|
|||
|
/** @var InputfieldCheckbox $f */
|
|||
|
$f = $this->modules->get("InputfieldCheckbox");
|
|||
|
$f->attr('name', 'template');
|
|||
|
$f->attr('value', $template->id);
|
|||
|
$f->label = $this->_('Are you sure?'); // Checkbox label to confirm they want to change template
|
|||
|
$f->label2 = $labelAction;
|
|||
|
$f->icon = 'warning';
|
|||
|
if(count($list)) $f->description = $this->_('Please confirm that you understand the above by clicking the checkbox below.'); // Checkbox description to confirm they want to change template
|
|||
|
$form->append($f);
|
|||
|
|
|||
|
/** @var InputfieldHidden $f */
|
|||
|
$f = $this->modules->get("InputfieldHidden");
|
|||
|
$f->attr('name', 'id');
|
|||
|
$f->attr('value', $this->page->id);
|
|||
|
$form->append($f);
|
|||
|
|
|||
|
/** @var InputfieldSubmit $f */
|
|||
|
$f = $this->modules->get("InputfieldSubmit");
|
|||
|
$f->attr('name', 'submit_change_template');
|
|||
|
$form->append($f);
|
|||
|
|
|||
|
return $form;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Execute a template change for a page, building an info + confirmation form (handler for /template/ action)
|
|||
|
*
|
|||
|
* @return string
|
|||
|
* @throws WireException
|
|||
|
*
|
|||
|
*/
|
|||
|
public function ___executeTemplate() {
|
|||
|
|
|||
|
$page = $this->page;
|
|||
|
$editUrl = "./?id=$page->id";
|
|||
|
$templateId = (int) $this->input->get('template');
|
|||
|
|
|||
|
try {
|
|||
|
if($templateId < 1) throw new WireException("Missing a 'template' GET variable");
|
|||
|
$template = $this->templates->get($templateId);
|
|||
|
if(!$template) throw new WireException("Unknown template");
|
|||
|
$form = $this->buildTemplateForm($template);
|
|||
|
} catch(\Exception $e) {
|
|||
|
$this->error($e->getMessage());
|
|||
|
$this->session->location($editUrl);
|
|||
|
return '';
|
|||
|
}
|
|||
|
|
|||
|
$this->breadcrumb($editUrl, $page->get("title|name"));
|
|||
|
|
|||
|
return $form->render();
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Save a template change for a page (handler for /saveTemplate/ action)
|
|||
|
*
|
|||
|
* @throws WireException
|
|||
|
*
|
|||
|
*/
|
|||
|
public function ___executeSaveTemplate() {
|
|||
|
|
|||
|
$page = $this->page;
|
|||
|
$editUrl = "./?id=$page->id";
|
|||
|
|
|||
|
$templateId = (int) $this->input->post('template');
|
|||
|
$template = $templateId > 0 ? $this->templates->get($templateId) : null;
|
|||
|
|
|||
|
if(!$template) {
|
|||
|
// checkbox not checked, template change aborted
|
|||
|
$this->session->location($editUrl);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
try {
|
|||
|
$form = $this->buildTemplateForm($template);
|
|||
|
} catch(\Exception $e) {
|
|||
|
$this->error($e->getMessage());
|
|||
|
$form = null;
|
|||
|
}
|
|||
|
|
|||
|
if(!$form || !$form->isSubmitted('submit_change_template')) {
|
|||
|
$this->session->location($editUrl);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
try {
|
|||
|
$page->template = $template;
|
|||
|
$page->save();
|
|||
|
$this->message(sprintf($this->_("Changed template to '%s'"), $template->name)); // Message: template was changed
|
|||
|
} catch(\Exception $e) {
|
|||
|
$this->error($e->getMessage());
|
|||
|
}
|
|||
|
|
|||
|
$this->session->location($editUrl);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Returns an array of templates that are allowed to be used here
|
|||
|
*
|
|||
|
* @return array|Template[] Array of Template objects
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function getAllowedTemplates() {
|
|||
|
|
|||
|
if(is_array($this->allowedTemplates)) return $this->allowedTemplates;
|
|||
|
|
|||
|
$templates = array();
|
|||
|
$user = $this->user;
|
|||
|
$isSuperuser = $user->isSuperuser();
|
|||
|
$page = $this->masterPage ? $this->masterPage : $this->page;
|
|||
|
$parent = $page->parent;
|
|||
|
$parentEditable = ($parent->id && $parent->editable());
|
|||
|
$config = $this->config;
|
|||
|
$superAdvanced = $isSuperuser && $config->advanced;
|
|||
|
|
|||
|
// current page template is assumed, otherwise we wouldn't be here
|
|||
|
$templates[$page->template->id] = $page->template;
|
|||
|
|
|||
|
// check if they even have permission to change it
|
|||
|
if(!$user->hasPermission('page-template', $page) || $page->template->noChangeTemplate) {
|
|||
|
$this->allowedTemplates = $templates;
|
|||
|
return $templates;
|
|||
|
}
|
|||
|
|
|||
|
$allTemplates = count($this->predefinedTemplates) ? $this->predefinedTemplates : $this->wire()->templates;
|
|||
|
|
|||
|
// note: this triggers load of all templates, fieldgroups and fields
|
|||
|
foreach($allTemplates as $template) {
|
|||
|
/** @var Template $template */
|
|||
|
|
|||
|
if(isset($templates[$template->id])) continue;
|
|||
|
|
|||
|
if($template->flags & Template::flagSystem) {
|
|||
|
// if($template->name == 'user' && $parent->id != $this->config->usersPageID) continue;
|
|||
|
if(in_array($template->id, $config->userTemplateIDs) && !in_array($parent->id, $config->usersPageIDs)) continue;
|
|||
|
if($template->name == 'role' && $parent->id != $config->rolesPageID) continue;
|
|||
|
if($template->name == 'permission' && $parent->id != $config->permissionsPageID) continue;
|
|||
|
if(strpos($template->name, 'repeater_') === 0 || strpos($template->name, 'fieldset_') === 0) continue;
|
|||
|
}
|
|||
|
|
|||
|
if(count($template->parentTemplates) && $parent->id && !in_array($parent->template->id, $template->parentTemplates)) {
|
|||
|
// this template specifies it can only be used with certain parents, and our parent's template isn't one of them
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
if($parent->id && count($parent->template->childTemplates)) {
|
|||
|
// the page's parent only allows certain templates for it's children
|
|||
|
// if this isn't one of them, then continue;
|
|||
|
if(!in_array($template->id, $parent->template->childTemplates)) continue;
|
|||
|
}
|
|||
|
|
|||
|
if(!$superAdvanced && ((int) $template->noParents) < 0 && $template->getNumPages() > 0) {
|
|||
|
// only one of these is allowed to exist (noParents=-1)
|
|||
|
continue;
|
|||
|
|
|||
|
} else if($template->noParents > 0) {
|
|||
|
// user can't change to a template that has been specified as no more instances allowed
|
|||
|
continue;
|
|||
|
|
|||
|
} else if($isSuperuser) {
|
|||
|
$templates[$template->id] = $template;
|
|||
|
|
|||
|
} else if((!$template->useRoles && $parentEditable) || $user->hasPermission('page-edit', $template)) {
|
|||
|
// determine if the template's assigned roles match up with the users's roles
|
|||
|
// and that at least one of those roles has page-edit permission
|
|||
|
if($user->hasPermission('page-create', $page)) {
|
|||
|
// user is allowed to create more pages of this type, so template may be used
|
|||
|
$templates[$template->id] = $template;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$this->allowedTemplates = $templates;
|
|||
|
|
|||
|
return $templates;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Is the given template or template ID allowed here?
|
|||
|
*
|
|||
|
* @param int|Template $id
|
|||
|
* @return bool
|
|||
|
*
|
|||
|
*/
|
|||
|
public function isAllowedTemplate($id) {
|
|||
|
|
|||
|
// if $id is a template, then convert it to it's numeric ID
|
|||
|
if($id instanceof Template) $id = $id->id;
|
|||
|
|
|||
|
$id = (int) $id;
|
|||
|
|
|||
|
// if the template is the same one already in place, of course it's allowed
|
|||
|
if($id == $this->page->template->id) return true;
|
|||
|
|
|||
|
// if we've made it this far, then get a list of templates that are allowed...
|
|||
|
$templates = $this->getAllowedTemplates();
|
|||
|
|
|||
|
// ...and determine if the supplied template is in that list
|
|||
|
return isset($templates[$id]);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Returns true if this page may be ajax saved (user has access), or false if not
|
|||
|
*
|
|||
|
* @param Page $page
|
|||
|
* @param string $fieldName Optional field name
|
|||
|
* @return bool
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function ___ajaxEditable(Page $page, $fieldName = '') {
|
|||
|
return $page->editable($fieldName);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Return instance of the Page being edited (required by WirePageEditor interface)
|
|||
|
*
|
|||
|
* For Inputfields/Fieldtypes to use if they want to retrieve the editing page rather than the viewing page
|
|||
|
*
|
|||
|
* @return Page
|
|||
|
*
|
|||
|
*/
|
|||
|
public function getPage() {
|
|||
|
return $this->page;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Set the page being edited
|
|||
|
*
|
|||
|
* @param Page $page
|
|||
|
*
|
|||
|
*/
|
|||
|
public function setPage(Page $page) {
|
|||
|
$this->page = $page;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Set the 'master' page
|
|||
|
*
|
|||
|
* @param Page $page
|
|||
|
* @deprecated
|
|||
|
*
|
|||
|
*/
|
|||
|
public function setMasterPage(Page $page) {
|
|||
|
$this->masterPage = $page;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get the 'master' page (if set)
|
|||
|
*
|
|||
|
* @return null|Page
|
|||
|
* @deprecated
|
|||
|
*
|
|||
|
*/
|
|||
|
public function getMasterPage() {
|
|||
|
return $this->masterPage;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Set whether or not 'settings' tab should show
|
|||
|
*
|
|||
|
* @param bool $useSettings
|
|||
|
*
|
|||
|
*/
|
|||
|
public function setUseSettings($useSettings) {
|
|||
|
$this->useSettings = (bool) $useSettings;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Set predefined allowed templates
|
|||
|
*
|
|||
|
* @param array|Template[] $templates
|
|||
|
*
|
|||
|
*/
|
|||
|
|
|||
|
public function setPredefinedTemplates($templates) {
|
|||
|
if(WireArray::iterable($templates)) $this->predefinedTemplates = $templates;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Set predefined allowed parents
|
|||
|
*
|
|||
|
* @param PageArray $parents
|
|||
|
*
|
|||
|
*/
|
|||
|
public function setPredefinedParents(PageArray $parents) {
|
|||
|
$this->predefinedParents = $parents;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Set the primary editor, if not ProcessPageEdit
|
|||
|
*
|
|||
|
* @param WirePageEditor $editor
|
|||
|
*
|
|||
|
*/
|
|||
|
public function setEditor(WirePageEditor $editor) {
|
|||
|
$this->editor = $editor;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Called on save requests, sets the next redirect URL for the next request
|
|||
|
*
|
|||
|
* @param string $url URL to redirect to
|
|||
|
* @since 3.0.142 Was protected in previous versions
|
|||
|
*
|
|||
|
*/
|
|||
|
public function setRedirectUrl($url) {
|
|||
|
$this->redirectUrl = $url;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get the current redirectUrl
|
|||
|
*
|
|||
|
* @param array $extras Any extra parts you want to add as array of strings like "key=value"
|
|||
|
* @return string
|
|||
|
* @since 3.0.142 Was protected in previous versions
|
|||
|
*
|
|||
|
*/
|
|||
|
public function getRedirectUrl(array $extras = array()) {
|
|||
|
$url = (string) $this->redirectUrl;
|
|||
|
if(!strlen($url)) $url = "./?id=$this->id";
|
|||
|
if($this->requestModal && strpos($url, 'modal=') === false) {
|
|||
|
$extras[] = "modal=$this->requestModal";
|
|||
|
}
|
|||
|
if(strpos($url, '&field=') === false && strpos($url, '&fields=') === false) {
|
|||
|
if(count($this->fields)) {
|
|||
|
$names = array();
|
|||
|
foreach($this->fields as $field) {
|
|||
|
$names[] = "$field";
|
|||
|
}
|
|||
|
$extras[] = "fields=" . implode(',', $names);
|
|||
|
} else if($this->field) {
|
|||
|
$extras[] = "field=$this->field";
|
|||
|
}
|
|||
|
}
|
|||
|
if(strpos($url, './') === 0 || (strpos($url, '/') !== 0 && strpos($url, '../') !== 0)) {
|
|||
|
if($this->requestLanguage && strpos($url, 'language=') === false) {
|
|||
|
$extras[] = "language=$this->requestLanguage";
|
|||
|
}
|
|||
|
if($this->requestContext && preg_match('/\bid=' . $this->id . '\b/', $url)) {
|
|||
|
$extras[] = "context=$this->requestContext";
|
|||
|
}
|
|||
|
}
|
|||
|
if(count($extras)) {
|
|||
|
$url .= strpos($url, '?') === false ? "?" : "&";
|
|||
|
$url .= implode('&', $extras);
|
|||
|
}
|
|||
|
return $url;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Add a tab with HTML id attribute and label
|
|||
|
*
|
|||
|
* Label may contain markup, and thus you should entity encode text labels as appropriate.
|
|||
|
*
|
|||
|
* @param string $id
|
|||
|
* @param string $label
|
|||
|
* @param InputfieldWrapper|null $wrapper
|
|||
|
*
|
|||
|
*/
|
|||
|
public function addTab($id, $label, $wrapper = null) {
|
|||
|
$this->tabs[$id] = $label;
|
|||
|
if($wrapper) $wrapper->addClass('WireTab');
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Remove the tab with the given id
|
|||
|
*
|
|||
|
* @param string $id
|
|||
|
*
|
|||
|
*/
|
|||
|
public function removeTab($id) {
|
|||
|
unset($this->tabs[$id]);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Returns associative array of tab ID => tab Label
|
|||
|
*
|
|||
|
* @return array
|
|||
|
*
|
|||
|
*/
|
|||
|
public function ___getTabs() {
|
|||
|
return $this->tabs;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get allowed page statuses
|
|||
|
*
|
|||
|
* @return array Array of [ statusFlagInteger => 'Status flag label' ]
|
|||
|
* @since 3.0.181
|
|||
|
*
|
|||
|
*/
|
|||
|
public function getAllowedStatuses() {
|
|||
|
|
|||
|
$page = $this->page;
|
|||
|
$config = $this->wire()->config;
|
|||
|
$statuses = array();
|
|||
|
$superuser = $this->user->isSuperuser();
|
|||
|
|
|||
|
if(!$this->page->template->noUnpublish && $this->page->publishable()) {
|
|||
|
$statuses[Page::statusUnpublished] = $this->_('Unpublished: Not visible on site'); // Settings: Unpublished status checkbox label
|
|||
|
}
|
|||
|
|
|||
|
if($this->user->hasPermission('page-hide', $this->page)) {
|
|||
|
$statuses[Page::statusHidden] = $this->_('Hidden: Excluded from lists and searches'); // Settings: Hidden status checkbox label
|
|||
|
}
|
|||
|
|
|||
|
if($this->user->hasPermission('page-lock', $this->page)) {
|
|||
|
$statuses[Page::statusLocked] = $this->_('Locked: Not editable'); // Settings: Locked status checkbox label
|
|||
|
}
|
|||
|
|
|||
|
if($superuser) {
|
|||
|
$uniqueNote = ($this->wire('languages') ? ' ' . $this->_('(in default language only)') : '');
|
|||
|
$statuses[Page::statusUnique] = sprintf($this->_('Unique: Require page name “%s” to be globally unique'), $this->page->name) . $uniqueNote;
|
|||
|
}
|
|||
|
|
|||
|
if($superuser && $config->advanced) {
|
|||
|
// additional statuses available to superuser in advanced mode
|
|||
|
$hasSystem = $page->hasStatus(Page::statusSystem) || $page->hasStatus(Page::statusSystemID) || $page->hasStatus(Page::statusSystemOverride);
|
|||
|
$statuses[Page::statusSystem] = "System: Non-deleteable and locked ID, name, template, parent (status not removeable without override)";
|
|||
|
$statuses[Page::statusSystemID] = "System ID: Non-deleteable and locked ID (status not removeable without override)";
|
|||
|
if($hasSystem) $statuses[Page::statusSystemOverride] = "System Override: Override (must be added temporarily in its own save before system status can be removed)";
|
|||
|
$statuses[Page::statusDraft] = "Draft: Page has a separate draft version";
|
|||
|
$statuses[Page::statusOn] = "On: Internal toggle when combined with other statuses (only for specific cases, otherwise ignored)";
|
|||
|
/*
|
|||
|
* Additional statuses that are possible but shouldn't be editable (uncomment temporarily if needed)
|
|||
|
*
|
|||
|
* $statuses[Page::statusTemp] = "Temp: Unpublished page more than 1 day old may be automatically deleted";
|
|||
|
* $statuses[Page::statusFlagged] = "Flagged: Page is flagged as incomplete, needing review, or having some issue";
|
|||
|
* $statuses[Page::statusTrash] = "Internal trash: Indicates that page is in the trash";
|
|||
|
* $statuses[Page::statusReserved] = "Internal-reserved: Status reserved for future use";
|
|||
|
* $statuses[Page::statusInternal] = "Internal-internal: Status for internal or future use";
|
|||
|
*
|
|||
|
*/
|
|||
|
}
|
|||
|
|
|||
|
return $statuses;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/**
|
|||
|
* Get PageBookmarks array
|
|||
|
*
|
|||
|
* @return PageBookmarks
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function getPageBookmarks() {
|
|||
|
static $bookmarks = null;
|
|||
|
if(is_null($bookmarks)) {
|
|||
|
require_once(dirname(__FILE__) . '/PageBookmarks.php');
|
|||
|
$bookmarks = $this->wire(new PageBookmarks($this));
|
|||
|
}
|
|||
|
return $bookmarks;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* navJSON action
|
|||
|
*
|
|||
|
* @param array $options
|
|||
|
* @return string
|
|||
|
* @throws Wire404Exception
|
|||
|
* @throws WireException
|
|||
|
*
|
|||
|
*/
|
|||
|
public function ___executeNavJSON(array $options = array()) {
|
|||
|
$bookmarks = $this->getPageBookmarks();
|
|||
|
$options['edit'] = $this->config->urls->admin . 'page/edit/?id={id}';
|
|||
|
$options['defaultIcon'] = 'pencil';
|
|||
|
$options = $bookmarks->initNavJSON($options);
|
|||
|
return parent::___executeNavJSON($options);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Bookmarks action
|
|||
|
*
|
|||
|
* @return string
|
|||
|
*
|
|||
|
*/
|
|||
|
public function ___executeBookmarks() {
|
|||
|
$bookmarks = $this->getPageBookmarks();
|
|||
|
return $bookmarks->editBookmarks();
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Set the headline used in the UI
|
|||
|
*
|
|||
|
*/
|
|||
|
public function setupHeadline() {
|
|||
|
|
|||
|
$titlePage = null;
|
|||
|
$page = $this->page;
|
|||
|
|
|||
|
if($page && $page->id) {
|
|||
|
$title = $page->get('title');
|
|||
|
if(is_object($title) && !strlen("$title") && wireInstanceOf($title, 'LanguagesPageFieldValue')) {
|
|||
|
/** @var LanguagesPageFieldValue $title */
|
|||
|
$title = $title->getNonEmptyValue($page->name);
|
|||
|
} else {
|
|||
|
$title = (string) $title;
|
|||
|
}
|
|||
|
if(empty($title)) {
|
|||
|
if($this->pages->names()->isUntitledPageName($page->name)) {
|
|||
|
$title = $page->template->getLabel();
|
|||
|
} else {
|
|||
|
$title = $page->get('name');
|
|||
|
}
|
|||
|
}
|
|||
|
if(empty($title)) $title = $page->name;
|
|||
|
} else if($this->parent && $this->parent->id) {
|
|||
|
$titlePage = $this->parent;
|
|||
|
$title = rtrim($this->parent->path, '/') . '/[...]';
|
|||
|
} else {
|
|||
|
$titlePage = new NullPage();
|
|||
|
$title = '[...]';
|
|||
|
}
|
|||
|
|
|||
|
$browserTitle = sprintf($this->_('Edit Page: %s'), $title);
|
|||
|
$headline = '';
|
|||
|
|
|||
|
if($this->field) {
|
|||
|
if(count($this->fields) == 1) {
|
|||
|
$headline = $this->field->getLabel();
|
|||
|
} else {
|
|||
|
$labels = array();
|
|||
|
foreach($this->fields as $field) {
|
|||
|
$labels[] = $field->getLabel();
|
|||
|
}
|
|||
|
$headline = implode(', ', $labels);
|
|||
|
}
|
|||
|
$browserTitle .= " ($headline)";
|
|||
|
|
|||
|
} else if($titlePage) {
|
|||
|
$headline = $titlePage->get('title|name');
|
|||
|
}
|
|||
|
|
|||
|
if(empty($headline)) $headline = $title;
|
|||
|
|
|||
|
$this->headline($headline);
|
|||
|
$this->browserTitle($browserTitle);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Setup the breadcrumbs used in the UI
|
|||
|
*
|
|||
|
*/
|
|||
|
public function setupBreadcrumbs() {
|
|||
|
if($this->input->urlSegment1) return;
|
|||
|
if($this->wire()->page->process != $this->className()) return;
|
|||
|
$this->wire()->breadcrumbs->shift(); // shift off the 'Admin' breadcrumb
|
|||
|
if($this->page && $this->page->id != 1) $this->wire()->breadcrumbs->shift(); // shift off the 'Pages' breadcrumb
|
|||
|
$page = $this->page ? $this->page : $this->parent;
|
|||
|
if($this->masterPage) $page = $this->masterPage;
|
|||
|
$lastID = (int) $this->session->get('ProcessPageList', 'lastID');
|
|||
|
$editCrumbs = !empty($this->configSettings['editCrumbs']);
|
|||
|
|
|||
|
$numParents = $page->parents->count();
|
|||
|
foreach($page->parents() as $cnt => $p) {
|
|||
|
$url = $editCrumbs && $p->editable() ? "./?id=$p->id" : "../?open=$p->id";
|
|||
|
if(!$editCrumbs && $cnt == $numParents-1 && $p->id == $lastID) $url = "../";
|
|||
|
$this->breadcrumb($url, $p->get("title|name"));
|
|||
|
}
|
|||
|
|
|||
|
if($this->page && $this->field) {
|
|||
|
$this->breadcrumb("./?id={$this->page->id}", $page->get("title|name"));
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Are we processing a submitted page edit form in this request?
|
|||
|
*
|
|||
|
* @return bool
|
|||
|
* @since 3.0.170
|
|||
|
*
|
|||
|
*/
|
|||
|
public function isSubmit() {
|
|||
|
return $this->isPost;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* URL to redirect to after non-authenticated user is logged-in, or false if module does not support
|
|||
|
*
|
|||
|
* @param Page $page
|
|||
|
* @return string
|
|||
|
* @sine 3.0.167
|
|||
|
*
|
|||
|
*/
|
|||
|
public static function getAfterLoginUrl(Page $page) {
|
|||
|
$sanitizer = $page->wire()->sanitizer;
|
|||
|
$input = $page->wire()->input;
|
|||
|
if($input->urlSegmentStr === 'bookmarks') return $page->url . 'bookmarks/';
|
|||
|
$qs = array(
|
|||
|
'id' => (int) $input->get('id'),
|
|||
|
'language' => (int) $input->get('language'),
|
|||
|
'template' => (int) $input->get('template'), // used by executeTemplate() only
|
|||
|
'uploadOnlyMode' => (int) $input->get('uploadOnlyMode'),
|
|||
|
'fnsx' => $input->get->fieldName('fnsx'),
|
|||
|
'context' => $input->get->name('context'),
|
|||
|
'field' => $input->get->fieldName('field'),
|
|||
|
'fields' => array(),
|
|||
|
);
|
|||
|
if($input->urlSegmentStr === 'template') {
|
|||
|
return "$page->url?id=$qs[id]&template=$qs[template]";
|
|||
|
}
|
|||
|
if($input->get('fields')) {
|
|||
|
foreach(explode(',', $input->get('fields')) as $value) {
|
|||
|
$qs['fields'] .= ($qs['fields'] ? ',' : '') . $sanitizer->fieldName($value);
|
|||
|
}
|
|||
|
}
|
|||
|
$a = array();
|
|||
|
foreach($qs as $name => $value) {
|
|||
|
if(!empty($value)) $a[] = "$name=$value";
|
|||
|
}
|
|||
|
return "$page->url?" . implode('&', $a);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Module config
|
|||
|
*
|
|||
|
* @param array $data
|
|||
|
* @return InputfieldWrapper
|
|||
|
* @throws WireException
|
|||
|
*
|
|||
|
*/
|
|||
|
public function getModuleConfigInputfields(array $data) {
|
|||
|
|
|||
|
$config = $this->wire()->config;
|
|||
|
$pages = $this->wire()->pages;
|
|||
|
$modules = $this->wire()->modules;
|
|||
|
$inputfields = new InputfieldWrapper();
|
|||
|
|
|||
|
$this->wire($inputfields);
|
|||
|
|
|||
|
/** @var InputfieldRadios $f */
|
|||
|
$f = $modules->get('InputfieldRadios');
|
|||
|
$f->name = 'viewAction';
|
|||
|
$f->label = $this->_('Default "view" location/action');
|
|||
|
$f->description = $this->_('The default type of action used when the "view" tab is clicked on in the page editor.');
|
|||
|
$f->icon = 'eye';
|
|||
|
|
|||
|
foreach($this->getViewActions(array(), true) as $name => $label) {
|
|||
|
$f->addOption($name, $label);
|
|||
|
}
|
|||
|
|
|||
|
$configData = $config->pageEdit;
|
|||
|
if(isset($data['viewAction'])) {
|
|||
|
$f->attr('value', $data['viewAction']);
|
|||
|
} else if(is_array($configData) && !empty($configData['viewNew'])) {
|
|||
|
$f->attr('value', 'new');
|
|||
|
} else {
|
|||
|
$f->attr('value', 'this');
|
|||
|
}
|
|||
|
|
|||
|
$inputfields->add($f);
|
|||
|
|
|||
|
$bookmarks = $this->getPageBookmarks();
|
|||
|
$bookmarks->addConfigInputfields($inputfields);
|
|||
|
$admin = $pages->get($config->adminRootPageID);
|
|||
|
$page = $pages->get($admin->path . 'page/edit/');
|
|||
|
$bookmarks->checkProcessPage($page);
|
|||
|
|
|||
|
return $inputfields;
|
|||
|
}
|
|||
|
|
|||
|
}
|