Blame | Last modification | View Log | Download
<?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 2017 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' => 108,'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;return 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' && $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');/** @var Session $session */$session = $this->wire('session');$data = $session->getFor($this, 'nav');if(!empty($data)) {// check that session cache data is still currentforeach($this->wire('templates') as $template) {if($template->modified > $data['modified']) {$data = array();$session->remove($this, 'nav');$session->remove($this, 'numAddable');$this->message("Clearing 'Add New' page cache", Notice::debug);break;}}}if(empty($data)) {$data = array('url' => $this->wire('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')) {$items = array();foreach($this->wire('templates') as $template) {$parent = $template->getParentPage(true);if(!$parent) continue;if($parent->id) {// one parent possibleif(!$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 = $this->wire('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 = $this->wire('modules')->getModuleConfigData($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(array_values($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($this->wire('config')->ajax) header("Content-Type: application/json");return json_encode($data);}/*** Ask user to select template and parent** @return string* @throws WireException**/public function ___executeTemplate() {$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 = $this->wire('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($this->wire('input')->get('modal')) $url .= "&modal=1";$this->wire('session')->redirect($url);}$templateLabel = $this->getTemplateLabel($template);$form = $this->wire('modules')->get('InputfieldForm');$form->description = $this->getTemplateLabel($template);$form->method = 'get';$form->action = './';$form->attr('id', 'select_parent_form');if($this->wire('input')->get('modal')) {$f = $this->wire('modules')->get('InputfieldHidden');$f->attr('name', 'modal');$f->attr('value', 1);$form->add($f);}$f = $this->wire('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);$f = $this->wire('modules')->get('InputfieldHidden');$f->attr('name', 'template_id');$f->attr('value', $template->id);$form->add($f);$f = $this->wire('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() {/** @var array $data */$data = $this->executeNavJSON(array('getArray' => true));$out = '';$bookmarkItem = null;foreach($data['list'] as $item) {if(strpos($item['url'], '?role=0') !== false) {$bookmarkItem = $item;continue;}if(!empty($item['parent_id'])) {$parents = $this->wire('pages')->find("id=$item[parent_id]");} else if(!empty($item['template_id'])) {$template = $this->wire('templates')->get($item['template_id']);$parentTemplates = implode('|', $template->parentTemplates);if(empty($parentTemplates)) continue;$parents = $this->wire('pages')->find("template=$parentTemplates, include=unpublished, limit=100, sort=-modified");} else {$parents = array();}$out .="<dt><a class='label' href='./$item[url]'>" ."<i class='fa fa-fw fa-$item[icon]'></i> $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) {if($p->id == 1 && $parentParents->count() > 1) continue;$out .= "$p->title<i class='ui-priority-secondary fa fa-fw fa-angle-right'></i>";}$out .= "</a></li>";}$out .= "</ul>";}$out .= "</dd>";}if($out) {$out = "<dl class='nav'>$out</dl>";} else {$out ="<h2>" .$this->_('There are currently no templates with defined parent/child relationships needed to show "Add New" shortcuts here. To configure this, edit any template (Setup > Templates) and click on the "Family" tab.') . // Text shown when no templates use family settings"</h2>";}if($bookmarkItem) {$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() {/** @var Pages $pages */$pages = $this->wire('pages');$parentID = (int) $this->wire('input')->get('parent_id');if(!$parentID) return '';$parent = $this->wire('pages')->get($parentID);if(!$parent->addable()) return '';$name = $this->wire('sanitizer')->pageNameUTF8($this->wire('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');$this->headline($this->_('Add New')); // Headlineif(!$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->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(count($this->parent->template->childTemplates) == 1) {// only one type of template is allowed for the parent$childTemplates = $this->parent->template->childTemplates;$template = $this->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 idif(!$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 usedif(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) {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 = $this->wire('config')->usersPageIDs;$userTemplateIDs = $this->wire('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 allowedif($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 parentif(!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 != $this->config->rolesPageID) {// only allow role templates below rolesPageIDcontinue;} else if($t->name == 'permission' && $parent->id != $this->config->permissionsPageID) {// only allow permission templates below permissionsPageIDcontinue;}$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("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("The template '$template' does not allow parents of type '$parent->template'");return false;}}if($template && count($parent->template->childTemplates)) {if(!in_array($template->id, $parent->template->childTemplates)) {if($showError) $this->error("The parent of type '$parent->template' does not allow children of type '$template'");return false;}}if(!$parent->addable()) {if($showError) $this->error("You don't have access to add pages to parent $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("Specified parent is not allowed ($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 allowedif(!in_array($template->id, $parent->template->childTemplates)) {$parents->remove($parent);}}}if($template && count($template->parentTemplates)) {// given template only allows certain parentsforeach($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() {/** @var InputfieldForm $form */$form = $this->modules->get('InputfieldForm');$form->attr('id', 'ProcessPageAdd');$form->addClass('InputfieldFormFocusFirst');$form->attr('action', './' . ($this->input->get('modal') ? "?modal=1" : ''));$form->attr('data-ajax-url', $this->wire('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 = $this->wire('pages')->newNullPage(); // for getInputfieldif(is_null($this->template) || !$this->template->noGlobal) {foreach($this->wire('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 fieldgroupif($_field) $field = $_field;}if(in_array($field->collapsed, array(Inputfield::collapsedNoLocked, Inputfield::collapsedYesLocked))) 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, array(Inputfield::collapsedNoLocked, Inputfield::collapsedYesLocked))) {// skip it} else {$inputfield = $field->getInputfield($page);$inputfield->columnWidth = 100;if($inputfield) $form->append($inputfield);}}}/** @var InputfieldPageName $field */$field = $this->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) {foreach($languages as $language) {if($language->isDefault()) continue;$field->checkboxChecked = $this->parent->get("status$language") ? true : false;}if($this->template) {// dummy edit page for examination by InputfieldPageName$editPage = $this->wire('pages')->newPage(array('template' => $this->template,'parent' => $this->parent,));$field->editPage = $editPage;}}$form->append($field);$defaultTemplateId = (int) $this->wire('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);$field = $this->modules->get('InputfieldHidden');$field->attr('id+name', 'template');$field->attr('value', $template->id);if(count($template->fieldgroup) == 1 && $template->fieldgroup->hasField('title')) $numPublishable++;$field->attr('data-publish', $numPublishable);} else {// multiple templates are possible so give them a select/** @var InputfieldSelect $field */$field = $this->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) {$field = $this->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 {$field = $this->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 = $this->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(!$this->wire('user')->isSuperuser()) {$publishPermission = $this->wire('permissions')->get('page-publish');if($publishPermission->id && !$this->wire('user')->hasPermission('page-publish')) $allowPublish = false;}if($allowPublish) {/** @var InputfieldSubmit $field */$field = $this->modules->get('InputfieldSubmit');$field->attr('id+name', 'submit_publish');$field->attr('value', $this->_('Save + Publish'));$field->setSecondary();$form->append($field);if(!$this->wire('input')->get('modal')) {$field = $this->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) {$this->deleteOldTempPages();// allow for nameFormat to come from a name_format GET variable$nameFormat = $this->wire('input')->get('name_format');if(strlen($nameFormat)) {$nameFormat = $this->sanitizer->chars($this->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 = $this->wire('pages')->newPage(array('template' => $template,'parent' => $parent,));$this->wire('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) {if($language->isDefault()) continue;$languageStatus = $parent->get("status$language");if($languageStatus) $page->set("status$language", $languageStatus);}try {$this->wire('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->session->redirect("../edit/?id=$page->id&new=1" . ($this->wire('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->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) {$template = $this->template;$this->page = $this->wire('pages')->newPage($template ? $template : array()); // must exist before processInput for language hooks$form->processInput($this->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 = $this->wire('input')->post('submit_publish_add');$publishNow = $this->page->publishable() && ($this->wire('input')->post('submit_publish') || $publishAdd);$languages = $template->getLanguages();foreach($this->page->fieldgroup as $field) {if(!$this->page->hasField($field)) continue;$f = $form->children->get($field->name);if($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($this->wire('pages')->names()->hasAdjustedName($this->page)) {$warning = $this->nameChangedWarning($this->page, $pageName);if($warning) $this->warning($warning);}if($publishNow && $publishAdd) {$this->session->redirect("./?parent_id={$this->page->parent_id}&template_id={$this->page->template->id}");} else {$this->session->redirect("../edit/?id={$this->page->id}&new=1" . ($this->input->get('modal') ? "&modal=1" : ''));}return true;}/*** Setup the UI breadcrumb trail**/public function setupBreadcrumbs() {if($this->wire('page')->process != $this->className()) return;$breadcrumbs = $this->wire(new Breadcrumbs());$breadcrumbs->add(new Breadcrumb($this->config->urls->admin . 'page/list/', "Pages"));foreach($this->parent->parents()->append($this->parent) as $p) {/** @var Page $p */$breadcrumbs->add(new Breadcrumb($this->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() {if(is_array($this->wire('input')->post('shortcutSort')) && $this->wire('user')->isSuperuser()) {$data = $this->wire('modules')->getModuleConfigData($this);$data['shortcutSort'] = $this->wire('input')->post->intArray('shortcutSort');$this->wire('modules')->saveModuleConfigData($this, $data);}$bookmarks = $this->getPageBookmarks();$form = $bookmarks->editBookmarksForm();$roleID = $this->wire('input')->get('role'); // no integer sanitization is intentionalif(!is_null($roleID) && $roleID == 0 && $this->wire('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**/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())) {$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) {$form = $this->wire(new InputfieldWrapper());$form->add($this->getShortcutSortField());$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, bypassing the usual unpublished state. Usually this is a desirable time saver. But you may cancel this behavior by checking the box below.'); // Description of automatic publishing$f->attr('name', 'noAutoPublish');$f->attr('value', 1);if(!empty($data['noAutoPublish'])) $f->attr('checked', 'checked');$form->add($f);return $form;}}