1515 lines
46 KiB
Text
1515 lines
46 KiB
Text
|
<?php namespace ProcessWire;
|
|||
|
|
|||
|
/**
|
|||
|
* ProcessWire Page Add Process
|
|||
|
*
|
|||
|
* Provides the UI for adding a page
|
|||
|
*
|
|||
|
* For more details about how Process modules work, please see:
|
|||
|
* /wire/core/Process.php
|
|||
|
*
|
|||
|
* ProcessWire 3.x, Copyright 2023 by Ryan Cramer
|
|||
|
* https://processwire.com
|
|||
|
*
|
|||
|
* @method string executeTemplate()
|
|||
|
* @method bool processQuickAdd(Page $parent, Template $template)
|
|||
|
* @method InputfieldForm buildForm()
|
|||
|
* @method bool processInput(InputfieldForm $form)
|
|||
|
* @method array getAllowedTemplates($parent = null)
|
|||
|
* @method string nameChangedWarning(Page $page, $namePrevious)
|
|||
|
*
|
|||
|
* @property bool|int $noAutoPublish Disable automatic publishing?
|
|||
|
* @property-write Template $template
|
|||
|
* @property-write int $parent_id
|
|||
|
*
|
|||
|
*/
|
|||
|
|
|||
|
class ProcessPageAdd extends Process implements ConfigurableModule, WirePageEditor {
|
|||
|
|
|||
|
/**
|
|||
|
* @var InputfieldForm
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $form;
|
|||
|
|
|||
|
/**
|
|||
|
* @var Page|null
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $parent = null;
|
|||
|
|
|||
|
/**
|
|||
|
* @var int
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $parent_id = 0;
|
|||
|
|
|||
|
/**
|
|||
|
* @var Page
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $page;
|
|||
|
|
|||
|
/**
|
|||
|
* @var Template|null
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $template = null;
|
|||
|
|
|||
|
/**
|
|||
|
* @var array|null
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $allowedTemplates = null; //cache
|
|||
|
|
|||
|
/**
|
|||
|
* @var array
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $predefinedTemplates = array();
|
|||
|
|
|||
|
/**
|
|||
|
* @var PageArray|array
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $predefinedParents = array();
|
|||
|
|
|||
|
/**
|
|||
|
* @var WirePageEditor|ProcessPageAdd
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $editor; // WirePageEditor
|
|||
|
|
|||
|
/**
|
|||
|
* Settings that may be specified with $config->pageAdd array
|
|||
|
*
|
|||
|
* @var array
|
|||
|
*
|
|||
|
*/
|
|||
|
protected $settings = array(
|
|||
|
'noSuggestTemplates' => '', // Disable suggestions: 1|true=disable all, or space-separated template names
|
|||
|
);
|
|||
|
|
|||
|
/**
|
|||
|
* @return array
|
|||
|
*
|
|||
|
*/
|
|||
|
public static function getModuleInfo() {
|
|||
|
return array(
|
|||
|
'title' => __('Page Add', __FILE__),
|
|||
|
'summary' => __('Add a new page', __FILE__),
|
|||
|
'version' => 109,
|
|||
|
'permanent' => true,
|
|||
|
'permission' => 'page-edit',
|
|||
|
'icon' => 'plus-circle',
|
|||
|
'useNavJSON' => true,
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Construct and populate default config
|
|||
|
*
|
|||
|
*/
|
|||
|
public function __construct() {
|
|||
|
$this->editor = $this;
|
|||
|
parent::__construct();
|
|||
|
$this->set('noAutoPublish', false);
|
|||
|
$this->set('shortcutSort', array());
|
|||
|
$settings = $this->wire()->config->pageAdd;
|
|||
|
if(is_array($settings)) $this->settings = array_merge($this->settings, $settings);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Module init
|
|||
|
*
|
|||
|
*/
|
|||
|
public function init() {
|
|||
|
$this->page = null;
|
|||
|
parent::init();
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Set property
|
|||
|
*
|
|||
|
* @param string $key
|
|||
|
* @param mixed $value
|
|||
|
* @return Process|ProcessPageAdd
|
|||
|
*
|
|||
|
*/
|
|||
|
public function set($key, $value) {
|
|||
|
if($key === 'parent_id') {
|
|||
|
$this->parent_id = (int) "$value";
|
|||
|
} else if($key === 'template') {
|
|||
|
if(is_string($value) || is_int($value)) $value = $this->wire()->templates->get($value);
|
|||
|
if($value instanceof Template) $this->template = $value;
|
|||
|
} else {
|
|||
|
return parent::set($key, $value);
|
|||
|
}
|
|||
|
return $this;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Return list of addable templates and links to add them
|
|||
|
*
|
|||
|
* Returns JSON by default, specify $options['getArray'] = true; to return an array.
|
|||
|
*
|
|||
|
* @param array $options
|
|||
|
* @return array|string
|
|||
|
*
|
|||
|
*/
|
|||
|
public function ___executeNavJSON(array $options = array()) {
|
|||
|
|
|||
|
$page = $this->wire()->page;
|
|||
|
$user = $this->wire()->user;
|
|||
|
$config = $this->wire()->config;
|
|||
|
$session = $this->wire()->session;
|
|||
|
$modules = $this->wire()->modules;
|
|||
|
$templates = $this->wire()->templates;
|
|||
|
$sanitizer = $this->wire()->sanitizer;
|
|||
|
|
|||
|
$data = $session->getFor($this, 'nav');
|
|||
|
|
|||
|
if(empty($data)) {
|
|||
|
|
|||
|
$data = array(
|
|||
|
'url' => $config->urls->admin . 'page/add/',
|
|||
|
'label' => $this->_((string) $page->get('title|name')),
|
|||
|
'icon' => 'plus-circle',
|
|||
|
'add' => null,
|
|||
|
'list' => array(),
|
|||
|
'modified' => time(),
|
|||
|
);
|
|||
|
|
|||
|
$items = array();
|
|||
|
|
|||
|
if(!$user->isGuest() && $user->hasPermission('page-edit')) {
|
|||
|
|
|||
|
foreach($templates as $template) {
|
|||
|
$parent = $template->getParentPage(true);
|
|||
|
if(!$parent) continue;
|
|||
|
if($parent->id) {
|
|||
|
// one parent possible
|
|||
|
if(!$this->isAllowedParent($parent, false, $template)) continue; // double check
|
|||
|
$qs = "?parent_id=$parent->id&template_id=$template->id";
|
|||
|
} else {
|
|||
|
// multiple parents possible
|
|||
|
$qs = "?template_id=$template->id";
|
|||
|
}
|
|||
|
$icon = $template->getIcon();
|
|||
|
if(!$icon) $icon = "plus-circle";
|
|||
|
$label = $template->getLabel();
|
|||
|
$key = strtolower($label);
|
|||
|
$label = $sanitizer->entities1($label);
|
|||
|
if(isset($items[$key])) $key .= $template->name;
|
|||
|
$items[$key] = array(
|
|||
|
'url' => $qs,
|
|||
|
'label' => $label,
|
|||
|
'icon' => $icon,
|
|||
|
'parent_id' => $parent->id, // for internal use only
|
|||
|
'template_id' => $template->id, // for internal use only
|
|||
|
);
|
|||
|
}
|
|||
|
ksort($items);
|
|||
|
|
|||
|
$configData = $modules->getConfig($this); // because admin theme calls with noInit option
|
|||
|
$shortcutSort = isset($configData['shortcutSort']) ? $configData['shortcutSort'] : array();
|
|||
|
if(!is_array($shortcutSort)) $shortcutSort = array();
|
|||
|
if(!empty($shortcutSort)) {
|
|||
|
$sorted = array();
|
|||
|
foreach($shortcutSort as $templateID) {
|
|||
|
foreach($items as $key => $item) {
|
|||
|
if($item['template_id'] == $templateID) {
|
|||
|
$sorted[$key] = $item;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
foreach($items as $key => $item) {
|
|||
|
if(!isset($sorted[$key])) $sorted[$key] = $item;
|
|||
|
}
|
|||
|
$items = $sorted;
|
|||
|
}
|
|||
|
}
|
|||
|
$data['list'] = array_values($items);
|
|||
|
$session->setFor($this, 'nav', $data);
|
|||
|
}
|
|||
|
|
|||
|
unset($data['modified']);
|
|||
|
|
|||
|
// get additional from PageBookmarks
|
|||
|
$bookmarks = $this->getPageBookmarks();
|
|||
|
$options2 = $bookmarks->initNavJSON(array('add' => 'ignore-me'));
|
|||
|
$lastItem = null;
|
|||
|
$listLength = count($data['list']);
|
|||
|
$n = 0;
|
|||
|
|
|||
|
foreach($options2['items'] as $p) {
|
|||
|
/** @var Page $p */
|
|||
|
if($p->id == 'bookmark' && !$user->isSuperuser()) continue;
|
|||
|
$item = array(
|
|||
|
'url' => ($p->id == 'bookmark' ? 'bookmarks/?role=0' : "?parent_id=$p->id"),
|
|||
|
'label' => $p->get('title|name') . ($p instanceof Page ? ' …' : ''),
|
|||
|
'icon' => $p->get('_icon') ? $p->get('_icon') : 'arrow-circle-right',
|
|||
|
'className' => $p->get('_class') . (!$n ? ' separator' : '')
|
|||
|
);
|
|||
|
if($p->id == 'bookmark') {
|
|||
|
$lastItem = $item;
|
|||
|
} else {
|
|||
|
$n++;
|
|||
|
$data['list'][] = $item;
|
|||
|
}
|
|||
|
}
|
|||
|
if($lastItem) $data['list'][] = $lastItem;
|
|||
|
|
|||
|
$session->setFor($this, 'numAddable', $listLength + $n);
|
|||
|
if(!empty($options['getArray'])) return $data;
|
|||
|
|
|||
|
if($config->ajax) header("Content-Type: application/json");
|
|||
|
|
|||
|
return json_encode($data);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Clear "add new" session caches
|
|||
|
*
|
|||
|
* @since 3.0.194
|
|||
|
*
|
|||
|
*/
|
|||
|
public function clearSessionCaches() {
|
|||
|
$session = $this->wire()->session;
|
|||
|
$session->removeFor($this, 'nav');
|
|||
|
$session->removeFor($this, 'numAddable');
|
|||
|
$this->message("Clearing 'Add New' page cache", Notice::debug);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Ask user to select template and parent
|
|||
|
*
|
|||
|
* @return string
|
|||
|
* @throws WireException
|
|||
|
*
|
|||
|
*/
|
|||
|
public function ___executeTemplate() {
|
|||
|
|
|||
|
$modules = $this->wire()->modules;
|
|||
|
$pages = $this->wire()->pages;
|
|||
|
$input = $this->wire()->input;
|
|||
|
|
|||
|
$templateID = (int) $this->input->get('template_id');
|
|||
|
if(!$templateID) throw new WireException("No template specified");
|
|||
|
|
|||
|
$template = $this->templates->get($templateID);
|
|||
|
if(!$template) throw new WireException("Unknown template");
|
|||
|
|
|||
|
$parentTemplates = new TemplatesArray();
|
|||
|
|
|||
|
foreach($template->parentTemplates as $templateID) {
|
|||
|
$t = $this->templates->get((int) $templateID);
|
|||
|
if($t) $parentTemplates->add($t);
|
|||
|
}
|
|||
|
|
|||
|
if(!count($parentTemplates)) throw new WireException("No parent templates defined for $template->name");
|
|||
|
|
|||
|
$parentTemplateIDs = $parentTemplates->implode('|', 'id');
|
|||
|
$parents = $pages->find("templates_id=$parentTemplateIDs, include=hidden, limit=500, sort=name");
|
|||
|
if(!count($parents)) throw new WireException("No usable parents match this template");
|
|||
|
|
|||
|
if(count($parents) == 1) {
|
|||
|
$url = "./?parent_id=" . $parents->first()->id;
|
|||
|
if($input->get('modal')) $url .= "&modal=1";
|
|||
|
$this->redirect($url);
|
|||
|
}
|
|||
|
|
|||
|
$templateLabel = $this->getTemplateLabel($template);
|
|||
|
|
|||
|
/** @var InputfieldForm $form */
|
|||
|
$form = $modules->get('InputfieldForm');
|
|||
|
$form->description = $this->getTemplateLabel($template);
|
|||
|
$form->method = 'get';
|
|||
|
$form->action = './';
|
|||
|
$form->attr('id', 'select_parent_form');
|
|||
|
|
|||
|
if($input->get('modal')) {
|
|||
|
/** @var InputfieldHidden $f */
|
|||
|
$f = $modules->get('InputfieldHidden');
|
|||
|
$f->attr('name', 'modal');
|
|||
|
$f->attr('value', 1);
|
|||
|
$form->add($f);
|
|||
|
}
|
|||
|
|
|||
|
/** @var InputfieldSelect $f */
|
|||
|
$f = $modules->get('InputfieldSelect');
|
|||
|
$f->attr('name', 'parent_id');
|
|||
|
$f->attr('id', 'select_parent_id');
|
|||
|
$f->label = sprintf($this->_('Where do you want to add the new %s?'), "“{$templateLabel}”");
|
|||
|
$f->description = sprintf($this->_('Please select a parent %s page below:'), ''); // Select parent label // you can omit the '%s' (no longer used)
|
|||
|
|
|||
|
$options = array();
|
|||
|
foreach($parents as $parent) {
|
|||
|
if(!$parent->addable()) continue;
|
|||
|
$key = $parent->parent->title ? $parent->parent->title . " - " . $parent->parent->path : $parent->parent->path;
|
|||
|
if(!isset($options[$key])) $options[$key] = array();
|
|||
|
$options[$key][$parent->id] = $parent->get('title|name');
|
|||
|
}
|
|||
|
|
|||
|
ksort($options);
|
|||
|
foreach($options as $optgroupLabel => $optgroup) {
|
|||
|
$f->addOption($optgroupLabel, $optgroup);
|
|||
|
}
|
|||
|
|
|||
|
$form->add($f);
|
|||
|
|
|||
|
/** @var InputfieldHidden $f */
|
|||
|
$f = $modules->get('InputfieldHidden');
|
|||
|
$f->attr('name', 'template_id');
|
|||
|
$f->attr('value', $template->id);
|
|||
|
$form->add($f);
|
|||
|
|
|||
|
/** @var InputfieldSubmit $f */
|
|||
|
$f = $modules->get('InputfieldSubmit');
|
|||
|
$f->attr('id', 'select_parent_submit');
|
|||
|
$form->add($f);
|
|||
|
|
|||
|
return $form->render();
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Render an HTML definition list template selection for when no parent/template is known
|
|||
|
*
|
|||
|
* @return string
|
|||
|
*
|
|||
|
*/
|
|||
|
public function renderChooseTemplate() {
|
|||
|
$pages = $this->wire()->pages;
|
|||
|
$templates = $this->wire()->templates;
|
|||
|
|
|||
|
/** @var array $data */
|
|||
|
$data = $this->executeNavJSON(array('getArray' => true));
|
|||
|
$out = '';
|
|||
|
$bookmarkItem = null;
|
|||
|
$rightIcon = "<span class='ui-priority-secondary'>" . wireIconMarkup('angle-right', 'fw') . "</span>";
|
|||
|
|
|||
|
foreach($data['list'] as $item) {
|
|||
|
if(strpos($item['url'], '?role=0') !== false) {
|
|||
|
$bookmarkItem = $item;
|
|||
|
continue;
|
|||
|
}
|
|||
|
if(!empty($item['parent_id'])) {
|
|||
|
$parents = $pages->find("id=$item[parent_id]");
|
|||
|
} else if(!empty($item['template_id'])) {
|
|||
|
$template = $templates->get($item['template_id']);
|
|||
|
$parentTemplates = implode('|', $template->parentTemplates);
|
|||
|
if(empty($parentTemplates)) continue;
|
|||
|
$parents = $pages->find("template=$parentTemplates, include=unpublished, limit=100, sort=-modified");
|
|||
|
} else {
|
|||
|
$parents = array();
|
|||
|
}
|
|||
|
$icon = wireIconMarkup($item['icon'], 'fw');
|
|||
|
$out .= "<dt><a class='label' href='./$item[url]'>$icon $item[label]</a></dt><dd>";
|
|||
|
|
|||
|
if(count($parents)) {
|
|||
|
$out .= "<ul>";
|
|||
|
foreach($parents as $parent) {
|
|||
|
$url = $item['url'];
|
|||
|
if(strpos($url, 'parent_id') === false) $url .= "&parent_id=$parent->id";
|
|||
|
$out .= "<li><a href='./$url'>";
|
|||
|
$parentParents = $parent->parents()->and($parent);
|
|||
|
foreach($parentParents as $p) {
|
|||
|
/** @var Page $p */
|
|||
|
if($p->id == 1 && $parentParents->count() > 1) continue;
|
|||
|
$out .= $p->title . $rightIcon;
|
|||
|
}
|
|||
|
$out .= "</a></li>";
|
|||
|
}
|
|||
|
$out .= "</ul>";
|
|||
|
}
|
|||
|
$out .= "</dd>";
|
|||
|
}
|
|||
|
if($out) {
|
|||
|
$out = "<dl class='nav'>$out</dl>";
|
|||
|
} else {
|
|||
|
// Text shown when no templates use family settings
|
|||
|
$out =
|
|||
|
"<h2>" .
|
|||
|
$this->_('There are currently no templates with defined parent/child relationships needed to show “Add New” shortcuts here.') . ' ' .
|
|||
|
$this->_('To configure this, edit any template (Setup > Templates) and click on the “Family” tab.') .
|
|||
|
"</h2>";
|
|||
|
}
|
|||
|
if($bookmarkItem) {
|
|||
|
/** @var InputfieldButton $button */
|
|||
|
$button = $this->wire()->modules->get('InputfieldButton');
|
|||
|
$button->href = $bookmarkItem['url'];
|
|||
|
$button->value = $bookmarkItem['label'];
|
|||
|
$button->showInHeader();
|
|||
|
$button->icon = $bookmarkItem['icon'];
|
|||
|
$out .= $button->render();
|
|||
|
}
|
|||
|
|
|||
|
return $out;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Method to handle AJAX call to check of a given page name exists for a parent
|
|||
|
*
|
|||
|
* Returns error or OK message in HTML
|
|||
|
*
|
|||
|
* @return string
|
|||
|
*
|
|||
|
*/
|
|||
|
public function executeExists() {
|
|||
|
|
|||
|
$pages = $this->wire()->pages;
|
|||
|
$input = $this->wire()->input;
|
|||
|
$sanitizer = $this->wire()->sanitizer;
|
|||
|
|
|||
|
$parentID = (int) $input->get('parent_id');
|
|||
|
if(!$parentID) return '';
|
|||
|
|
|||
|
$parent = $pages->get($parentID);
|
|||
|
if(!$parent->addable()) return '';
|
|||
|
|
|||
|
$name = $sanitizer->pageNameUTF8($input->get('name'));
|
|||
|
if(!strlen($name)) return '';
|
|||
|
|
|||
|
$parentID = count($this->predefinedParents) ? $this->predefinedParents : $parentID;
|
|||
|
|
|||
|
$test = new Page();
|
|||
|
$test->parent_id = $parentID;
|
|||
|
$test->name = $name;
|
|||
|
$reason = $pages->names()->pageNameHasConflict($test);
|
|||
|
|
|||
|
if($reason) {
|
|||
|
$out = "<span class='taken ui-state-error-text'>" . wireIconMarkup('exclamation-triangle') . " $reason</span>";
|
|||
|
} else {
|
|||
|
$out = "<span class='ui-priority-secondary'>" . wireIconMarkup('check-square-o') . ' ' . $this->_('Ok') . "</span>";
|
|||
|
}
|
|||
|
|
|||
|
return $out;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Main execution, first screen of adding a Page
|
|||
|
*
|
|||
|
* @return string
|
|||
|
* @throws Wire404Exception
|
|||
|
* @throws WireException
|
|||
|
*
|
|||
|
*/
|
|||
|
public function ___execute() {
|
|||
|
|
|||
|
$input = $this->wire()->input;
|
|||
|
$config = $this->wire()->config;
|
|||
|
|
|||
|
$this->headline($this->_('Add New')); // Headline
|
|||
|
|
|||
|
if(!$this->parent_id) {
|
|||
|
if($input->post('parent_id')) {
|
|||
|
$this->parent_id = (int) $input->post('parent_id');
|
|||
|
} else if($input->get('parent_id')) {
|
|||
|
$this->parent_id = (int) $input->get('parent_id');
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if($input->get('template_id') && !$this->parent_id) {
|
|||
|
return $this->executeTemplate();
|
|||
|
}
|
|||
|
|
|||
|
$template_id = (int) $input->post('template'); // note POST uses 'template' and GET uses 'template_id'
|
|||
|
if(!$template_id) $template_id = (int) $input->get('template_id');
|
|||
|
if($template_id) $this->template = $this->wire()->templates->get($template_id);
|
|||
|
|
|||
|
if(!$this->parent_id && count($this->predefinedParents)) {
|
|||
|
$this->parent_id = $this->predefinedParents->first()->id;
|
|||
|
}
|
|||
|
|
|||
|
if(!$this->parent_id) return $this->renderChooseTemplate();
|
|||
|
|
|||
|
$this->parent = $this->wire()->pages->get((int) $this->parent_id);
|
|||
|
if(!$this->parent->id) {
|
|||
|
throw new Wire404Exception("Unable to load parent page $this->parent_id", Wire404Exception::codeSecondary);
|
|||
|
}
|
|||
|
if(!$this->isAllowedParent($this->parent, true, $this->template)) {
|
|||
|
throw new WireException($this->errors('string'));
|
|||
|
}
|
|||
|
|
|||
|
// if page has a custom ProcessPageType for editing/adding then redirect to its add URL instead
|
|||
|
if($this->parent->template->name === 'admin' && $this->wire()->page->id != $this->parent->id) {
|
|||
|
$process = $this->parent->process;
|
|||
|
if($process && wireInstanceOf($process, 'ProcessPageType')) {
|
|||
|
$this->redirect($this->parent->url . 'add/');
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// the following does the same as the block of code above for a custom ProcessPageType
|
|||
|
// but is necessary for the case of when custom $config->usersPageIDs is in use since
|
|||
|
// the parent template may not be 'admin' and user pages may be outside of admin structure
|
|||
|
if(in_array($this->parent_id, $config->usersPageIDs) && $this->wire()->process != 'ProcessUser') {
|
|||
|
$url = $config->urls->admin . "access/users/add/?parent_id=$this->parent_id";
|
|||
|
if($template_id && in_array($template_id, $config->userTemplateIDs)) $url .= "&template_id=$template_id";
|
|||
|
$this->redirect($url);
|
|||
|
}
|
|||
|
|
|||
|
if(count($this->parent->template->childTemplates) == 1) {
|
|||
|
// only one type of template is allowed for the parent
|
|||
|
$childTemplates = $this->parent->template->childTemplates;
|
|||
|
$template = $this->wire()->templates->get(reset($childTemplates));
|
|||
|
if($this->template && $template->id != $this->template->id) {
|
|||
|
throw new WireException("Template $template is required for parent {$this->parent->path}");
|
|||
|
}
|
|||
|
$this->template = $template;
|
|||
|
if(!$this->isAllowedTemplate($this->template, $this->parent)) {
|
|||
|
throw new WireException("You don't have access to the template required to add pages here");
|
|||
|
}
|
|||
|
|
|||
|
} else if($this->template) {
|
|||
|
// initial request specifying a template id
|
|||
|
if(!$this->isAllowedTemplate($this->template, $this->parent)) {
|
|||
|
throw new WireException("Template {$this->template->name} is not allowed here ({$this->parent->path})");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if($this->template && $this->parent) {
|
|||
|
// determine whether quick-add can be used
|
|||
|
if(strlen($this->parent->template->childNameFormat) || $input->get('name_format')) {
|
|||
|
$this->processQuickAdd($this->parent, $this->template);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$this->form = $this->buildForm();
|
|||
|
$this->form->setTrackChanges();
|
|||
|
|
|||
|
if($input->post('submit_save') || $input->post('submit_publish') || $input->post('submit_publish_add')) {
|
|||
|
if($this->processInput($this->form)) {
|
|||
|
// redirect occurs within processInput
|
|||
|
} else {
|
|||
|
// errors occurred during process input, re-render form
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$this->setupBreadcrumbs();
|
|||
|
|
|||
|
return $this->form->render();
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Returns an array of templates that are allowed to be used here
|
|||
|
*
|
|||
|
* @param Page|null $parent
|
|||
|
* @return array
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function ___getAllowedTemplates($parent = null) {
|
|||
|
|
|||
|
$config = $this->wire()->config;
|
|||
|
|
|||
|
if(is_null($parent)) $parent = $this->parent;
|
|||
|
if(!$parent) return array();
|
|||
|
if(is_array($this->allowedTemplates)) return $this->allowedTemplates;
|
|||
|
|
|||
|
$user = $this->wire()->user;
|
|||
|
$templates = array();
|
|||
|
$allTemplates = count($this->predefinedTemplates) ? $this->predefinedTemplates : $this->wire()->templates;
|
|||
|
$allParents = $this->getAllowedParents();
|
|||
|
$usersPageIDs = $config->usersPageIDs;
|
|||
|
$userTemplateIDs = $config->userTemplateIDs;
|
|||
|
|
|||
|
if($parent->hasStatus(Page::statusUnpublished)) {
|
|||
|
$parentEditable = $parent->editable();
|
|||
|
} else {
|
|||
|
// temporarily put the parent in an unpublished status so that we can check it from
|
|||
|
// the proper context: when page-publish permission exists, a page not not editable
|
|||
|
// if a user doesn't have page-publish permission to it, even though it may still
|
|||
|
// be editable if it was unpublished.
|
|||
|
$parent->addStatus(Page::statusUnpublished);
|
|||
|
$parentEditable = $parent->editable();
|
|||
|
$parent->removeStatus(Page::statusUnpublished);
|
|||
|
}
|
|||
|
|
|||
|
foreach($allTemplates as $t) {
|
|||
|
|
|||
|
if($t->noParents == -1) {
|
|||
|
// only 1 of this type allowed
|
|||
|
if($t->getNumPages() > 0) continue;
|
|||
|
} else if($t->noParents) {
|
|||
|
continue;
|
|||
|
}
|
|||
|
if($t->useRoles && !$user->hasPermission('page-create', $t)) continue;
|
|||
|
if(!$t->useRoles && !$parentEditable) continue;
|
|||
|
if(!$t->useRoles && !$user->hasPermission('page-create', $parent)) continue;
|
|||
|
|
|||
|
if(count($allParents) == 1) {
|
|||
|
if(count($parent->template->childTemplates)) {
|
|||
|
// check that this template is allowed by the defined parent
|
|||
|
if(!in_array($t->id, $parent->template->childTemplates)) continue;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if(count($t->parentTemplates)) {
|
|||
|
// this template is only allowed for certain parents
|
|||
|
$allow = false;
|
|||
|
foreach($allParents as $_parent) {
|
|||
|
if(in_array($_parent->template->id, $t->parentTemplates)) {
|
|||
|
$allow = true;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
if(!$allow) continue;
|
|||
|
}
|
|||
|
|
|||
|
if(in_array($t->id, $userTemplateIDs)) {
|
|||
|
// this is a user template: allow any parents defined in $config->usersPageIDs
|
|||
|
$allow = false;
|
|||
|
foreach($allParents as $_parent) {
|
|||
|
if(in_array($_parent->id, $usersPageIDs)) {
|
|||
|
$allow = true;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
if(!$allow) continue;
|
|||
|
|
|||
|
} else if($t->name == 'role' && $parent->id != $config->rolesPageID) {
|
|||
|
// only allow role templates below rolesPageID
|
|||
|
continue;
|
|||
|
|
|||
|
} else if($t->name == 'permission' && $parent->id != $config->permissionsPageID) {
|
|||
|
// only allow permission templates below permissionsPageID
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
$templates[$t->id] = $t;
|
|||
|
}
|
|||
|
|
|||
|
if($this->template || count($this->predefinedTemplates)) {
|
|||
|
$predefinedTemplates = count($this->predefinedTemplates) ? $this->predefinedTemplates : array($this->template);
|
|||
|
foreach($predefinedTemplates as $t) {
|
|||
|
$isUserTemplate = in_array($t->id, $userTemplateIDs);
|
|||
|
if($isUserTemplate && !isset($templates[$t->id]) && $user->hasPermission('user-admin')) {
|
|||
|
// account for the unique situation of user-admin permission
|
|||
|
// where all user-based templates are allowed
|
|||
|
$templates[$t->id] = $t;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$this->allowedTemplates = $templates;
|
|||
|
|
|||
|
return $templates;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Is the given template or template ID allowed here?
|
|||
|
*
|
|||
|
* @param Template|int Template ID or object
|
|||
|
* @param Page $parent Optionally parent page to filter by
|
|||
|
* @return bool
|
|||
|
* @throws WireException of template argument can't be resolved
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function isAllowedTemplate($template, Page $parent = null) {
|
|||
|
if(!is_object($template)) $template = $this->wire()->templates->get($template);
|
|||
|
if(!$template) throw new WireException('Unknown template');
|
|||
|
$templates = $this->getAllowedTemplates($parent);
|
|||
|
$allowed = isset($templates[$template->id]);
|
|||
|
if($allowed && $parent) {
|
|||
|
if(count($parent->template->childTemplates) && !in_array($template->id, $parent->template->childTemplates)) {
|
|||
|
$allowed = false;
|
|||
|
} else if($parent->template->noChildren) {
|
|||
|
$allowed = false;
|
|||
|
} else if(count($template->parentTemplates) && !in_array($parent->template->id, $template->parentTemplates)) {
|
|||
|
$allowed = false;
|
|||
|
} else if($template->noParents == -1) {
|
|||
|
$allowed = $template->getNumPages() == 0;
|
|||
|
} else if($template->noParents) {
|
|||
|
$allowed = false;
|
|||
|
}
|
|||
|
}
|
|||
|
return $allowed;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Is the given parent page allowed?
|
|||
|
*
|
|||
|
* @param Page $parent
|
|||
|
* @param bool $showError
|
|||
|
* @param Template $template Optionally limit condition to a specific template
|
|||
|
* @return bool
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function isAllowedParent(Page $parent, $showError = false, Template $template = null) {
|
|||
|
|
|||
|
if($parent->template->noChildren) {
|
|||
|
if($showError) {
|
|||
|
$this->error($this->_('The parent template has specified that no children may be added here'));
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
if($template && count($template->parentTemplates)) {
|
|||
|
if(!in_array($parent->template->id, $template->parentTemplates)) {
|
|||
|
if($showError) {
|
|||
|
$this->error(sprintf(
|
|||
|
$this->_('The template "%1$s" does not allow parents of type "%2$s"'),
|
|||
|
$template->name,
|
|||
|
$parent->template->name
|
|||
|
));
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if($template && count($parent->template->childTemplates)) {
|
|||
|
if(!in_array($template->id, $parent->template->childTemplates)) {
|
|||
|
if($showError) {
|
|||
|
$this->error(sprintf(
|
|||
|
$this->_('The parent of type "%1$s" does not allow children of type "%2$s"'),
|
|||
|
$parent->template->name,
|
|||
|
$template->name
|
|||
|
));
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if(!$parent->addable()) {
|
|||
|
if($showError) {
|
|||
|
$this->error(sprintf(
|
|||
|
$this->_('You don’t have access to add pages to parent %s'),
|
|||
|
$parent->path
|
|||
|
));
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
if(count($this->predefinedParents)) {
|
|||
|
$allowed = false;
|
|||
|
foreach($this->predefinedParents as $p) {
|
|||
|
if($p->id == $parent->id) {
|
|||
|
$allowed = true;
|
|||
|
}
|
|||
|
}
|
|||
|
if(!$allowed) {
|
|||
|
if($showError) {
|
|||
|
$this->error(sprintf(
|
|||
|
$this->_('Specified parent is not allowed (%s)'),
|
|||
|
$parent->path
|
|||
|
));
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get allowed parents
|
|||
|
*
|
|||
|
* This will always be 1-parent, unless predefinedParents was populated.
|
|||
|
*
|
|||
|
* @param Template $template Optionally specify a template to filter parents by
|
|||
|
* @return PageArray
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function getAllowedParents(Template $template = null) {
|
|||
|
if(count($this->predefinedParents)) {
|
|||
|
$parents = $this->predefinedParents;
|
|||
|
} else {
|
|||
|
$parents = $this->wire()->pages->newPageArray();
|
|||
|
if($this->parent) $parents->add($this->parent);
|
|||
|
}
|
|||
|
foreach($parents as $parent) {
|
|||
|
if(!$parent->addable()) $parents->remove($parent);
|
|||
|
if($parent->template->noChildren) $parents->remove($parent);
|
|||
|
if($template && count($parent->template->childTemplates)) {
|
|||
|
// parent only allows certain templates for children
|
|||
|
// if a template was given in the arguments, check that it is allowed
|
|||
|
if(!in_array($template->id, $parent->template->childTemplates)) {
|
|||
|
$parents->remove($parent);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
if($template && count($template->parentTemplates)) {
|
|||
|
// given template only allows certain parents
|
|||
|
foreach($parents as $parent) {
|
|||
|
if(!in_array($parent->template->id, $template->parentTemplates)) {
|
|||
|
$parents->remove($parent);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
return $parents;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Build the form fields for adding a page
|
|||
|
*
|
|||
|
* @return InputfieldForm
|
|||
|
* @throws WireException
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function ___buildForm() {
|
|||
|
|
|||
|
$modules = $this->wire()->modules;
|
|||
|
$input = $this->wire()->input;
|
|||
|
$config = $this->wire()->config;
|
|||
|
$pages = $this->wire()->pages;
|
|||
|
$fields = $this->wire()->fields;
|
|||
|
$user = $this->wire()->user;
|
|||
|
|
|||
|
$lockedStates = array(Inputfield::collapsedNoLocked, Inputfield::collapsedYesLocked, Inputfield::collapsedBlankLocked);
|
|||
|
|
|||
|
/** @var InputfieldForm $form */
|
|||
|
$form = $modules->get('InputfieldForm');
|
|||
|
$form->attr('id', 'ProcessPageAdd');
|
|||
|
$form->addClass('InputfieldFormFocusFirst');
|
|||
|
$form->attr('action', './' . ($input->get('modal') ? "?modal=1" : ''));
|
|||
|
$form->attr('data-ajax-url', $config->urls->admin . 'page/add/');
|
|||
|
$form->attr('data-dup-note', $this->_('The name entered is already in use. If you do not modify it, the name will be made unique automatically after you save.'));
|
|||
|
$form->attr('method', 'post');
|
|||
|
|
|||
|
$page = $pages->newNullPage(); // for getInputfield
|
|||
|
if(is_null($this->template) || !$this->template->noGlobal) {
|
|||
|
foreach($fields as $field) {
|
|||
|
if($field->flags & Field::flagGlobal && ($field->type instanceof FieldtypePageTitle || $field->type instanceof FieldtypePageTitleLanguage)) {
|
|||
|
if($this->template) {
|
|||
|
$_field = $this->template->fieldgroup->getField($field->id, true); // get in context of fieldgroup
|
|||
|
if($_field) $field = $_field;
|
|||
|
}
|
|||
|
if(in_array($field->collapsed, $lockedStates)) continue;
|
|||
|
$inputfield = $field->getInputfield($page);
|
|||
|
if($inputfield) {
|
|||
|
if($this->template && $this->template->noLang) $inputfield->useLanguages = false;
|
|||
|
$inputfield->columnWidth = 100;
|
|||
|
$form->append($inputfield);
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
} else if($this->template) {
|
|||
|
/** @var Field $field */
|
|||
|
$field = $this->template->fieldgroup->getField('title', true);
|
|||
|
if($field) {
|
|||
|
if(in_array($field->collapsed, $lockedStates)) {
|
|||
|
// skip it
|
|||
|
} else {
|
|||
|
$inputfield = $field->getInputfield($page);
|
|||
|
$inputfield->columnWidth = 100;
|
|||
|
if($inputfield) $form->append($inputfield);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/** @var InputfieldPageName $field */
|
|||
|
$field = $modules->get('InputfieldPageName');
|
|||
|
$field->parentPage = $this->parent;
|
|||
|
$field->attr('name', '_pw_page_name');
|
|||
|
$field->required = true;
|
|||
|
|
|||
|
if($this->template) {
|
|||
|
$field->slashUrls = $this->template->slashUrls;
|
|||
|
$label = $this->template->getNameLabel();
|
|||
|
if($label) $field->label = $label;
|
|||
|
$languages = $this->template->getLanguages();
|
|||
|
} else {
|
|||
|
$languages = $this->wire()->languages;
|
|||
|
}
|
|||
|
|
|||
|
/** @var Languages $languages */
|
|||
|
if($languages && $this->parent && $this->parent_id > 0) {
|
|||
|
if($this->template) {
|
|||
|
// dummy edit page for examination by InputfieldPageName
|
|||
|
$editPage = $pages->newPage(array(
|
|||
|
'template' => $this->template,
|
|||
|
'parent' => $this->parent,
|
|||
|
));
|
|||
|
$field->editPage = $editPage;
|
|||
|
}
|
|||
|
}
|
|||
|
$form->append($field);
|
|||
|
|
|||
|
$defaultTemplateId = (int) $input->get('template_id');
|
|||
|
if(!$defaultTemplateId && $this->parent->numChildren > 0) {
|
|||
|
$sibling = $this->parent->child('sort=-created, include=hidden');
|
|||
|
if($sibling && $sibling->id) $defaultTemplateId = $sibling->template->id;
|
|||
|
}
|
|||
|
if(!$defaultTemplateId) $defaultTemplateId = $this->parent->template->id;
|
|||
|
$allowedTemplates = $this->getAllowedTemplates();
|
|||
|
if(!count($allowedTemplates)) throw new WireException($this->_('No templates allowed for adding new pages here.'));
|
|||
|
if($this->template && !isset($allowedTemplates[$this->template->id])) throw new WireException(sprintf($this->_('Template "%s" is not allowed here.'), $this->template->name));
|
|||
|
if(!isset($allowedTemplates[$defaultTemplateId])) $defaultTemplateId = 0;
|
|||
|
$numPublishable = 0;
|
|||
|
|
|||
|
if(count($allowedTemplates) < 2) {
|
|||
|
|
|||
|
// only 1 template can be used here, so store it in a hidden field (no need for selection)
|
|||
|
$template = $this->template ? $this->template : reset($allowedTemplates);
|
|||
|
/** @var InputfieldHidden $field */
|
|||
|
$field = $modules->get('InputfieldHidden');
|
|||
|
$field->attr('value', $template->id);
|
|||
|
if(count($template->fieldgroup) == 1 && $template->fieldgroup->hasField('title')) $numPublishable++;
|
|||
|
$field->attr('data-publish', $numPublishable);
|
|||
|
if($template->noLang) $field->attr('data-nolang', 1);
|
|||
|
|
|||
|
} else {
|
|||
|
// multiple templates are possible so give them a select
|
|||
|
|
|||
|
/** @var InputfieldSelect $field */
|
|||
|
$field = $modules->get('InputfieldSelect');
|
|||
|
|
|||
|
$noSuggest = $this->settings['noSuggestTemplates'];
|
|||
|
if(empty($noSuggest)) {
|
|||
|
$noSuggest = false;
|
|||
|
} else {
|
|||
|
// determine whether to show blank option at top
|
|||
|
$ptpl = $this->parent ? $this->parent->template->name : '';
|
|||
|
$noSuggestArray = is_array($noSuggest) ? $noSuggest : explode(' ', $noSuggest);
|
|||
|
if(((string) $noSuggest) === "1" || ($ptpl && in_array($ptpl, $noSuggestArray))) {
|
|||
|
$field->addOption('', '', array('data-publish' => false, 'data-nolang' => false));
|
|||
|
$noSuggest = true;
|
|||
|
} else {
|
|||
|
$noSuggest = false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
foreach($allowedTemplates as $template) {
|
|||
|
if(!count($this->predefinedTemplates) && $this->template && $template->id != $this->template->id) continue;
|
|||
|
$numFields = count($template->fieldgroup);
|
|||
|
if($numFields == 1 && $template->fieldgroup->hasField('title')) {
|
|||
|
$isPublishable = 1;
|
|||
|
$numPublishable++;
|
|||
|
} else {
|
|||
|
$isPublishable = 0;
|
|||
|
}
|
|||
|
|
|||
|
$field->addOption($template->id, $this->getTemplateLabel($template), array(
|
|||
|
'data-publish' => $isPublishable,
|
|||
|
'data-nolang' => (int) $template->noLang
|
|||
|
));
|
|||
|
}
|
|||
|
|
|||
|
if(!$noSuggest) $field->attr('value', $defaultTemplateId);
|
|||
|
}
|
|||
|
|
|||
|
$field->label = $this->_('Template'); // Template field label
|
|||
|
$field->attr('id+name', 'template');
|
|||
|
$field->icon = 'cubes';
|
|||
|
$field->required = true;
|
|||
|
$field instanceof InputfieldHidden ? $form->append($field) : $form->prepend($field);
|
|||
|
|
|||
|
if(count($this->predefinedParents) > 1) {
|
|||
|
/** @var InputfieldSelect $field */
|
|||
|
$field = $modules->get('InputfieldSelect');
|
|||
|
$field->attr('name', 'parent_id');
|
|||
|
$field->label = $this->_('Parent');
|
|||
|
$field->required = true;
|
|||
|
$field->icon = 'folder-open-o';
|
|||
|
$value = 0;
|
|||
|
foreach($this->predefinedParents as $parent) {
|
|||
|
$field->addOption($parent->id, $parent->path);
|
|||
|
if($parent->id == $this->parent_id) $value = $parent->id;
|
|||
|
}
|
|||
|
if($value) $field->attr('value', $value);
|
|||
|
$form->prepend($field);
|
|||
|
|
|||
|
} else {
|
|||
|
/** @var InputfieldHidden $field */
|
|||
|
$field = $modules->get('InputfieldHidden');
|
|||
|
$field->attr('name', 'parent_id');
|
|||
|
$value = count($this->predefinedParents) == 1 ? $this->predefinedParents->first()->id : $this->parent_id;
|
|||
|
$field->attr('value', $value);
|
|||
|
$form->append($field);
|
|||
|
}
|
|||
|
|
|||
|
/** @var InputfieldSubmit $field */
|
|||
|
$field = $modules->get('InputfieldSubmit');
|
|||
|
$field->attr('name', 'submit_save');
|
|||
|
$field->attr('value', $this->_('Save'));
|
|||
|
$field->showInHeader();
|
|||
|
$form->append($field);
|
|||
|
|
|||
|
if($numPublishable && !$this->noAutoPublish) {
|
|||
|
$allowPublish = true;
|
|||
|
if(!$user->isSuperuser()) {
|
|||
|
$publishPermission = $this->wire()->permissions->get('page-publish');
|
|||
|
if($publishPermission->id && !$user->hasPermission('page-publish')) $allowPublish = false;
|
|||
|
}
|
|||
|
if($allowPublish) {
|
|||
|
/** @var InputfieldSubmit $field */
|
|||
|
$field = $modules->get('InputfieldSubmit');
|
|||
|
$field->attr('id+name', 'submit_publish');
|
|||
|
$field->attr('value', $this->_('Save + Publish'));
|
|||
|
$field->setSecondary();
|
|||
|
$form->append($field);
|
|||
|
|
|||
|
if(!$input->get('modal')) {
|
|||
|
/** @var InputfieldSubmit $field */
|
|||
|
$field = $modules->get('InputfieldSubmit');
|
|||
|
$field->attr('id+name', 'submit_publish_add');
|
|||
|
$field->attr('value', $this->_('Save + Publish + Add Another'));
|
|||
|
$field->setSecondary();
|
|||
|
$form->append($field);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if(count($allowedTemplates) == 1) {
|
|||
|
$t = reset($allowedTemplates);
|
|||
|
$form->description = $this->getTemplateLabel($t);
|
|||
|
}
|
|||
|
|
|||
|
return $form;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Return the label for the given Template
|
|||
|
*
|
|||
|
* @param Template $template
|
|||
|
* @return string
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function getTemplateLabel(Template $template) {
|
|||
|
$label = '';
|
|||
|
$user = $this->wire()->user;
|
|||
|
$language = $this->wire()->languages && $user->language->id && !$user->language->isDefault() ? $user->language : null;
|
|||
|
if($language) $label = $template->get('label' . $language->id);
|
|||
|
if(!$label) $label = $template->label ? $template->label : $template->name;
|
|||
|
return $label;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Delete old 'quick add' pages that were never saved
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function deleteOldTempPages() {
|
|||
|
|
|||
|
$old = time() - 86400;
|
|||
|
$selector = "include=all, modified<$old, limit=10, status&" . Page::statusTemp . ", status<" . Page::statusTrash;
|
|||
|
$items = $this->wire()->pages->find($selector);
|
|||
|
|
|||
|
foreach($items as $item) {
|
|||
|
$this->message("Checking temporary item: $item->path", Notice::debug);
|
|||
|
if(!$item->hasStatus(Page::statusUnpublished)) continue;
|
|||
|
if(!$item->hasStatus(Page::statusTemp)) continue;
|
|||
|
if($item->modified > $old) continue;
|
|||
|
if($item->numChildren > 0) continue;
|
|||
|
|
|||
|
$msg = "Automatically trashed unused page: $item->path";
|
|||
|
$this->message($msg, Notice::debug);
|
|||
|
$this->wire()->log->message($msg);
|
|||
|
|
|||
|
try {
|
|||
|
if(!$item->title) $item->title = $this->_('Unused temp page') . ' - ' . $item->name;
|
|||
|
$this->wire()->pages->trash($item);
|
|||
|
} catch(\Exception $e) {
|
|||
|
$this->error($e->getMessage());
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Perform a 'quick add' of a page and redirect to edit the page
|
|||
|
*
|
|||
|
* @param Page $parent
|
|||
|
* @param Template $template
|
|||
|
* @return bool Returns false if not success. Redirects if success.
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function ___processQuickAdd(Page $parent, Template $template) {
|
|||
|
|
|||
|
$pages = $this->wire()->pages;
|
|||
|
$input = $this->wire()->input;
|
|||
|
$sanitizer = $this->wire()->sanitizer;
|
|||
|
|
|||
|
$this->deleteOldTempPages();
|
|||
|
// allow for nameFormat to come from a name_format GET variable
|
|||
|
$nameFormat = (string) $input->get('name_format');
|
|||
|
if(strlen($nameFormat)) {
|
|||
|
$nameFormat = $sanitizer->chars($sanitizer->text($nameFormat), '-_:./| [alpha][digit]', '-');
|
|||
|
} else {
|
|||
|
if(count($parent->template->childTemplates) > 1) return false;
|
|||
|
$nameFormat = '';
|
|||
|
}
|
|||
|
$nameFormatTemplate = $parent->template->childNameFormat;
|
|||
|
if(strlen($nameFormat)) {
|
|||
|
// temporarily assign to the template->childNameFormat property
|
|||
|
$parent->template->childNameFormat = $nameFormat;
|
|||
|
} else {
|
|||
|
// if not specified in get variable, next check parent template for setting
|
|||
|
$nameFormat = $nameFormatTemplate;
|
|||
|
}
|
|||
|
$page = $pages->newPage(array(
|
|||
|
'template' => $template,
|
|||
|
'parent' => $parent,
|
|||
|
));
|
|||
|
$pages->setupNew($page);
|
|||
|
if(!strlen($page->name)) return false;
|
|||
|
if(!$this->isAllowedTemplate($template)) return false;
|
|||
|
$page->addStatus(Page::statusUnpublished);
|
|||
|
$page->addStatus(Page::statusTemp); // ProcessPageEdit will remove this status the first time the page is saved
|
|||
|
|
|||
|
// if languages are in use, make the new page inherit the parent's language status (active vs. inactive)
|
|||
|
$languages = $template->getLanguages();
|
|||
|
if($languages) {
|
|||
|
foreach($languages as $language) {
|
|||
|
/** @var Language $language */
|
|||
|
if($language->isDefault()) continue;
|
|||
|
$languageStatus = $parent->get("status$language");
|
|||
|
if($languageStatus) $page->set("status$language", $languageStatus);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
try {
|
|||
|
$pages->save($page);
|
|||
|
$this->createdPageMessage($page);
|
|||
|
|
|||
|
} catch(\Exception $e) {
|
|||
|
$this->error($e->getMessage());
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
if(strlen($nameFormat) && $nameFormat != $nameFormatTemplate) {
|
|||
|
$parent->template->childNameFormat = $nameFormatTemplate; // restore original name format
|
|||
|
}
|
|||
|
|
|||
|
if($page->id) {
|
|||
|
// allow for classes descending Page to redirect to alternate editor if $this->editor is not the right kind
|
|||
|
$page->setEditor($this->editor);
|
|||
|
// redirect to edit the page
|
|||
|
$this->redirect("../edit/?id=$page->id&new=1" . ($input->get('modal') ? '&modal=1' : ''));
|
|||
|
return true;
|
|||
|
} else {
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Populate a session message indicating info about created page
|
|||
|
*
|
|||
|
* @param Page $page
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function createdPageMessage(Page $page) {
|
|||
|
$this->wire()->session->message(
|
|||
|
sprintf(
|
|||
|
$this->_('Created page %1$s using template: %2$s'),
|
|||
|
$page->parent->url . $page->name, $page->template->getLabel()
|
|||
|
)
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Hook called when the page's name changed during save
|
|||
|
*
|
|||
|
* @param Page $page
|
|||
|
* @param $namePrevious
|
|||
|
* @return string Warning message
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function ___nameChangedWarning(Page $page, $namePrevious) {
|
|||
|
return sprintf(
|
|||
|
$this->_('Warning, the name you selected "%1$s" was already in use and has been changed to "%2$s".'),
|
|||
|
$namePrevious, $page->name
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Save the submitted page add form
|
|||
|
*
|
|||
|
* @param InputfieldForm $form
|
|||
|
* @throws WireException
|
|||
|
* @return bool
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function ___processInput(InputfieldForm $form) {
|
|||
|
|
|||
|
$pages = $this->wire()->pages;
|
|||
|
$input = $this->wire()->input;
|
|||
|
|
|||
|
$template = $this->template;
|
|||
|
$this->page = $pages->newPage($template ? $template : array()); // must exist before processInput for language hooks
|
|||
|
$form->processInput($input->post);
|
|||
|
|
|||
|
/** @var InputfieldPageName $nameField */
|
|||
|
$nameField = $form->getChildByName('_pw_page_name');
|
|||
|
$name = $nameField->value;
|
|||
|
|
|||
|
if(!strlen($name)) {
|
|||
|
$nameField->error($this->_("Missing required field: name"));
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
if(is_null($template)) {
|
|||
|
/** @var InputfieldSelect $templateField */
|
|||
|
$templateField = $form->getChildByName('template');
|
|||
|
$templatesId = (int) $templateField->val();
|
|||
|
$template = $templatesId ? $this->templates->get($templatesId) : null;
|
|||
|
if(!$template) {
|
|||
|
$templateField->error($this->_('Template selection is required'));
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if(!$this->isAllowedTemplate($template, $this->parent)) {
|
|||
|
throw new WireException("You don't have access to add pages with template '$template'");
|
|||
|
} else {
|
|||
|
// $this->message("Template $template is allowed for {$this->parent->template}");
|
|||
|
}
|
|||
|
if(!$this->isAllowedParent($this->parent, true, $template)) {
|
|||
|
throw new WireException($this->errors('string'));
|
|||
|
} else {
|
|||
|
// $this->message("Parent {$this->parent->path} is allowed for $template");
|
|||
|
}
|
|||
|
|
|||
|
$this->page->template = $template;
|
|||
|
$this->page->parent = $this->parent;
|
|||
|
$this->page->name = $name;
|
|||
|
$this->page->sort = $this->parent->numChildren;
|
|||
|
|
|||
|
$publishAdd = $input->post('submit_publish_add');
|
|||
|
$publishNow = $this->page->publishable() && ($input->post('submit_publish') || $publishAdd);
|
|||
|
$languages = $template->getLanguages();
|
|||
|
|
|||
|
foreach($this->page->fieldgroup as $field) {
|
|||
|
/** @var Field $field */
|
|||
|
if(!$this->page->hasField($field)) continue;
|
|||
|
$f = $form->children->get($field->name);
|
|||
|
if($f) {
|
|||
|
/** @var Inputfield $f */
|
|||
|
if($languages && $f->getSetting('useLanguages')) {
|
|||
|
// account for language fields (most likely FieldtypePageTitleLanguage)
|
|||
|
$value = $this->page->get($field->name);
|
|||
|
if(is_object($value)) $value->setFromInputfield($f);
|
|||
|
} else {
|
|||
|
$value = $f->attr('value');
|
|||
|
}
|
|||
|
$this->page->set($field->name, $value);
|
|||
|
} else {
|
|||
|
$publishNow = false; // non-global fields means we won't publish yet
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if($publishNow && $this->noAutoPublish) $publishNow = false;
|
|||
|
|
|||
|
// if more fields are going to be present in this page's template, then don't make this page available until the user has
|
|||
|
// had the opportunity to edit those fields in ProcessPageEdit. But if they've already seen all the fields that will be here (global),
|
|||
|
// like just a title field, then go ahead and publish now.
|
|||
|
|
|||
|
if(!$publishNow) $this->page->addStatus(Page::statusUnpublished);
|
|||
|
|
|||
|
$pageName = $this->page->name;
|
|||
|
$this->page->setEditor($this->editor);
|
|||
|
try {
|
|||
|
$this->pages->save($this->page, array('adjustName' => true));
|
|||
|
} catch(\Exception $e) {
|
|||
|
$this->error($e->getMessage());
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
$this->createdPageMessage($this->page);
|
|||
|
|
|||
|
if($pages->names()->hasAdjustedName($this->page)) {
|
|||
|
$warning = $this->nameChangedWarning($this->page, $pageName);
|
|||
|
if($warning) $this->warning($warning);
|
|||
|
}
|
|||
|
|
|||
|
if($publishNow && $publishAdd) {
|
|||
|
$this->redirect("./?parent_id={$this->page->parent_id}&template_id={$this->page->template->id}");
|
|||
|
} else {
|
|||
|
$this->redirect("../edit/?id={$this->page->id}&new=1" . ($input->get('modal') ? "&modal=1" : ''));
|
|||
|
}
|
|||
|
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Redirect
|
|||
|
*
|
|||
|
* @param string $url
|
|||
|
* @param bool $permanent
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function redirect($url, $permanent = false) {
|
|||
|
if($permanent) $this->wire()->session->redirect($url);
|
|||
|
$this->wire()->session->location($url);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Setup the UI breadcrumb trail
|
|||
|
*
|
|||
|
*/
|
|||
|
public function setupBreadcrumbs() {
|
|||
|
$config = $this->wire()->config;
|
|||
|
if($this->wire()->page->process != $this->className()) return;
|
|||
|
$breadcrumbs = $this->wire(new Breadcrumbs());
|
|||
|
$breadcrumbs->add(new Breadcrumb($config->urls->admin . 'page/list/', "Pages"));
|
|||
|
foreach($this->parent->parents()->append($this->parent) as $p) {
|
|||
|
/** @var Page $p */
|
|||
|
$breadcrumbs->add(new Breadcrumb($config->urls->admin . "page/list/?open=" . $p->id, $p->get("title|name")));
|
|||
|
}
|
|||
|
$this->wire('breadcrumbs', $breadcrumbs);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get the Page that is being edited
|
|||
|
*
|
|||
|
* @return Page|null
|
|||
|
*
|
|||
|
*/
|
|||
|
public function getPage() {
|
|||
|
return $this->page ? $this->page : $this->wire()->pages->newNullPage();
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Set the WirePageEditor that is calling this Process
|
|||
|
*
|
|||
|
* @param WirePageEditor $editor
|
|||
|
*
|
|||
|
*/
|
|||
|
public function setEditor(WirePageEditor $editor) {
|
|||
|
$this->editor = $editor;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Predefine the allowed templates, separately from family/auto-detect
|
|||
|
*
|
|||
|
* @param array|WireArray $templates array of Template objects
|
|||
|
*
|
|||
|
*/
|
|||
|
public function setPredefinedTemplates($templates) {
|
|||
|
$this->predefinedTemplates = $templates;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Predefine the allowed parents, separately from family/auto-detect
|
|||
|
*
|
|||
|
* @param PageArray $parents
|
|||
|
*
|
|||
|
*/
|
|||
|
public function setPredefinedParents(PageArray $parents) {
|
|||
|
$this->predefinedParents = $parents;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get an instance of PageBookmarks
|
|||
|
*
|
|||
|
* @return PageBookmarks
|
|||
|
*
|
|||
|
*/
|
|||
|
protected function getPageBookmarks() {
|
|||
|
require_once($this->wire()->config->paths('ProcessPageEdit') . 'PageBookmarks.php');
|
|||
|
return $this->wire(new PageBookmarks($this));
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Execute the Page Bookmarks
|
|||
|
*
|
|||
|
* @return string
|
|||
|
* @throws WireException
|
|||
|
* @throws WirePermissionException
|
|||
|
*
|
|||
|
*/
|
|||
|
public function ___executeBookmarks() {
|
|||
|
|
|||
|
$input = $this->wire()->input;
|
|||
|
$user = $this->wire()->user;
|
|||
|
$modules = $this->wire()->modules;
|
|||
|
|
|||
|
if(is_array($input->post('shortcutSort')) && $user->isSuperuser()) {
|
|||
|
$data = $modules->getConfig($this);
|
|||
|
$data['shortcutSort'] = $input->post->intArray('shortcutSort');
|
|||
|
$modules->saveConfig($this, $data);
|
|||
|
}
|
|||
|
|
|||
|
$bookmarks = $this->getPageBookmarks();
|
|||
|
$form = $bookmarks->editBookmarksForm();
|
|||
|
|
|||
|
$roleID = $input->get('role'); // no integer sanitization is intentional
|
|||
|
|
|||
|
if(!is_null($roleID) && $roleID == 0 && $user->isSuperuser()) {
|
|||
|
$f = $this->getShortcutSortField();
|
|||
|
$form->insertBefore($f, $form->getChildByName('submit_save_bookmarks'));
|
|||
|
}
|
|||
|
|
|||
|
$f = $form->getChildByName('bookmarks');
|
|||
|
if($f->notes) $f->notes .= "\n\n";
|
|||
|
$f->notes .= $this->_('The pages you select above represent bookmarks to the parent pages where you want children added. Note that if a user does not have permission to add a page to a given parent page (whether due to access control or template family settings), the bookmark will not appear.'); // Notes for bookmarks
|
|||
|
|
|||
|
$this->wire()->session->remove($this, 'numAddable');
|
|||
|
|
|||
|
return $form->render();
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get Inputfield that lets you define shorcuts and sort order
|
|||
|
*
|
|||
|
* @return InputfieldAsmSelect|InputfieldHidden
|
|||
|
*
|
|||
|
*/
|
|||
|
public function getShortcutSortField() {
|
|||
|
|
|||
|
$this->wire()->session->remove($this, 'nav');
|
|||
|
|
|||
|
/** @var array $data */
|
|||
|
$data = $this->executeNavJSON(array('getArray' => true));
|
|||
|
$name = 'shortcutSort';
|
|||
|
|
|||
|
/** @var InputfieldAsmSelect $f */
|
|||
|
$f = $this->wire()->modules->get('InputfieldAsmSelect');
|
|||
|
$f->label = $this->_('Template shortcut sort order');
|
|||
|
$f->description = $this->_('To change the order of the "Add New" page-template shortcuts, drag and drop the options to the order you want them in.');
|
|||
|
$f->notes = $this->_('To add or remove templates from these shortcuts, see the Template editor Family tab.');
|
|||
|
$f->attr('name', $name);
|
|||
|
$f->icon = 'sort';
|
|||
|
$f->setAsmSelectOption('removeLabel', '');
|
|||
|
|
|||
|
$value = array();
|
|||
|
|
|||
|
foreach($data['list'] as $item) {
|
|||
|
if(empty($item['template_id'])) continue;
|
|||
|
$template = $this->wire()->templates->get($item['template_id']);
|
|||
|
if(!$template) continue;
|
|||
|
$f->addOption($template->id, $template->getLabel());
|
|||
|
$value[] = $template->id;
|
|||
|
}
|
|||
|
|
|||
|
if(!count($f->getOptions())) {
|
|||
|
/** @var InputfieldHidden $f */
|
|||
|
$f = $this->wire()->modules->get('InputfieldHidden');
|
|||
|
$f->attr('name', $name);
|
|||
|
return $f;
|
|||
|
}
|
|||
|
|
|||
|
$f->attr('value', $value);
|
|||
|
$f->collapsed = Inputfield::collapsedBlank;
|
|||
|
|
|||
|
return $f;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get module configuration inputs
|
|||
|
*
|
|||
|
* @param array $data
|
|||
|
* @return InputfieldWrapper
|
|||
|
*
|
|||
|
*/
|
|||
|
public function getModuleConfigInputfields(array $data) {
|
|||
|
|
|||
|
/** @var InputfieldWrapper $form */
|
|||
|
$form = $this->wire(new InputfieldWrapper());
|
|||
|
$form->add($this->getShortcutSortField());
|
|||
|
|
|||
|
/** @var InputfieldCheckbox $f */
|
|||
|
$f = $this->wire()->modules->get('InputfieldCheckbox');
|
|||
|
$f->label = $this->_('Disable automatic publishing');
|
|||
|
$f->description =
|
|||
|
$this->_('By default, pages with nothing but global fields (most commonly “title”) will be published automatically when added.') . ' ' .
|
|||
|
$this->_('This bypasses the unpublished state, which can be a desirable time saver in some instances.') . ' ' .
|
|||
|
$this->_('You may optionally cancel this behavior by checking the box below.');
|
|||
|
$f->attr('name', 'noAutoPublish');
|
|||
|
$f->attr('value', 1);
|
|||
|
if(!empty($data['noAutoPublish'])) $f->attr('checked', 'checked');
|
|||
|
$form->add($f);
|
|||
|
|
|||
|
return $form;
|
|||
|
}
|
|||
|
}
|