'Page Edit', 'summary' => 'Edit a Page', 'version' => 106, 'permanent' => true, 'permission' => 'page-edit', ); } /** * Initialize the page editor by loading the requested page and any dependencies * */ public function init() { if(isset($_POST['id'])) $this->id = (int) $_POST['id']; else if(isset($_GET['id'])) $this->id = (int) $_GET['id']; // predefined messages that maybe used in multiple places $this->noticeUnknown = $this->_("Unknown page"); // Init error: Unknown page $this->noticeLocked = $this->_("This page is locked for edits"); // Init error: Page is locked $this->noticeNoAccess = $this->_("You don't have access to edit"); // Init error: User doesn't have access if(!$this->id) throw new Wire404Exception($this->noticeUnknown); // Init error: no page provided $this->user = $this->fuel('user'); $this->page = $this->pages->get($this->id); if($this->page instanceof NullPage) throw new WireException($this->noticeUnknown); // Init error: page doesn't exist $this->pageClass = $this->page->className(); $this->page->setOutputFormatting(false); $this->parent = $this->pages->get($this->page->parent_id); $this->isTrash = $this->page->isTrash; if(!$this->page->editable()) throw new WirePermissionException($this->noticeNoAccess); // Init: user doesn't have access // determine if we're going to be dealing with a save/post request $this->isPost = ($this->input->post->id > 0 && (((int) $this->input->post->id) === $this->page->id)) || $this->config->ajax && (count($_POST) || isset($_SERVER['HTTP_X_FIELDNAME'])); // after confirming that the page is editable, if the current user is the one that created the page // then temporarily give both the page and the user the special "owner" role if($this->page->createdUserID == $this->user->id) { // TODO for new user system, old part commented out below but kept for reference: // $ownerRole = $this->roles->get(Role::ownerRoleID); // $this->page->addRole($ownerRole); // $this->user->addRole($ownerRole); } if(!$this->isPost) { $this->setupHeadline(); $this->setupBreadcrumbs(); } parent::init(); if(!$this->isPost) $this->modules->get('JqueryWireTabs'); $title = $this->page->title; if($title) $this->wire('processBrowserTitle', sprintf($this->_('Edit Page: %s'), $title)); } /** * Execute the Page Edit process by building the form and checking if it was submitted * */ public function ___execute() { if($this->config->ajax && (isset($_SERVER['HTTP_X_FIELDNAME']) || count($_POST))) return $this->ajaxSave($this->page); if($this->page->is(Page::statusTemp) && $this->page->parent->template->childNameFormat == 'title') { // make it set page name from page title $this->page->name = ''; } $this->form = $this->modules->get('InputfieldForm'); $this->form = $this->buildForm($this->form); $this->form->setTrackChanges(); if($this->isPost && count($_POST)) $this->processSave(); if($this->page->is(Page::statusLocked)) { if($this->user->hasPermission('page-lock', $this->page)) { $this->message($this->noticeLocked); // Page locked message } else { $this->error($this->noticeLocked); // Page locked error } } return $this->renderEdit(); } /** * Render the Page Edit form * */ protected function renderEdit() { $class = ''; $out = "

" . ($this->page->id ? $this->page->id : "New") . "

"; if($this->form->description) { $out .= "

" . $this->form->description . "

"; $this->form->description = ''; } $tabs = $this->wire('modules')->get('JqueryWireTabs')->renderTabList($this->tabs, array('id' => 'PageEditTabs')); $this->form->value = $tabs; $out .= $this->form->render(); $out .= ""; // ends up being slightly faster than ready() (or at least appears that way) return $out; } protected function checkPageName() { // make sure we don't have a name collision $n = 0; $name = ''; do { $name = $this->page->name . ($n ? "-$n" : ""); $p = $this->wire('pages')->get("include=all, id!={$this->page->id}, parent={$this->page->parent}, name=$name"); } while($p->id && ++$n); if($name != $this->page->name) { $this->message(sprintf($this->_('Changed page URL name to "%s" because requested name was already taken.'), $name)); $this->page->name = $name; } } /** * Save a submitted Page Edit form * */ protected function processSave() { if($this->page->is(Page::statusLocked)) { if(!$this->user->hasPermission('page-lock', $this->page) || (!empty($_POST['status']) && in_array(Page::statusLocked, $_POST['status']))) { $this->error($this->noticeLocked); $this->processSaveRedirect($this->redirectUrl); return; } } // remove temporary status that may have been assigned by ProcessPageAdd quick add mode if($this->page->is(Page::statusTemp)) $this->page->removeStatus(Page::statusTemp); if($this->input->post->submit_delete) { if($this->input->post->delete_page) $this->deletePage(); } else { $this->processInput($this->form); $debug = wire('config')->debug; $numChanges = 0; $changes = array_unique($this->page->getChanges()); $numChanges = count($changes); if($numChanges) $this->message(sprintf($this->_('Change: %s'), implode(', ', $changes)), Notice::debug); // Message shown for each changed field $formErrors = 0; foreach($this->notices as $notice) { if($notice instanceof NoticeError) $formErrors++; } $isUnpublished = $this->page->is(Page::statusUnpublished); if($this->input->post->submit_publish || $this->input->post->submit_save) { try { $numChanges = $numChanges > 0 ? ' (' . sprintf($this->_n('%d change', '%d changes', $numChanges) . ')', $numChanges) : ''; if($this->input->post->submit_publish && $isUnpublished && $this->page->publishable() && !$formErrors) { $this->page->removeStatus(Page::statusUnpublished); $message = sprintf($this->_('Published Page: %s'), $this->page->path) . $numChanges; // Message shown when page is published } else { $message = sprintf($this->_('Saved Page: %s'), $this->page->path) . $numChanges; // Message shown when page is saved if($isUnpublished && $formErrors && $this->input->post->submit_publish) $message .= ' - ' . $this->_('Cannot be published until errors are corrected'); } if($this->page->isChanged('name')) $this->checkPageName(); $this->page->save(); $this->message($message); } catch(Exception $e) { $this->error($e->getMessage()); } } } $this->processSaveRedirect($this->redirectUrl); } /** * Perform an after save redirect * */ protected function ___processSaveRedirect($redirectUrl) { if(!$redirectUrl) $redirectUrl = "./?id={$this->page->id}&s=1"; if($this->input->get->modal) $redirectUrl .= "&modal=1"; $this->redirectUrl = $redirectUrl; $this->session->redirect($redirectUrl); } /** * Build the form used for Page Edits * */ protected function ___buildForm(InputfieldForm $form) { $form->attr('id+name', 'ProcessPageEdit'); $form->attr('action', './?id=' . $this->id . ($this->input->get->modal ? "&modal=1" : '')); $form->attr('method', 'post'); $form->attr('enctype', 'multipart/form-data'); $form->attr('class', 'ui-helper-clearfix template_' . $this->page->template . ' class_' . $this->page->className); $form->attr('autocomplete', 'off'); // determine what content fields should become tabs $contentTab = $this->buildFormContent(); $tabs = array(); $tabWrap = null; $tabOpen = null; foreach($contentTab as $inputfield) { if($inputfield->className == 'InputfieldFieldsetTabOpen' && !$tabOpen) { // open new tab $tabOpen = $inputfield; $tabWrap = new InputfieldWrapper(); $tabWrap->attr('title', $tabOpen->label); $tabWrap->id = $tabOpen->id; $contentTab->remove($inputfield); $this->tabs[$tabOpen->id] = $this->wire('sanitizer')->entities1($tabOpen->label); } else if($tabOpen) { // already have a tab open if($inputfield->attr('name') == $tabOpen->attr('name') . '_END') { // close tab $tabs[] = $tabWrap; $tabOpen = null; } else { // add to already open tab $tabWrap->add($inputfield); } $contentTab->remove($inputfield); } } $form->append($contentTab); foreach($tabs as $tab) $form->append($tab); if($this->page->addable() || $this->page->numChildren) $form->append($this->buildFormChildren()); if(!$this->page->template->noSettings) $form->append($this->buildFormSettings()); if($this->isTrash) $this->message($this->_("This page is in the Trash")); if($this->page->deleteable()) $form->append($this->buildFormDelete()); //if($this->page->viewable() && !$this->input->get->modal) $form->append($this->buildFormView()); if($this->page->viewable() && !$this->input->get->modal) $this->buildFormView(); $field2 = null; $saveName = ''; if($this->page->is(Page::statusUnpublished)) { if(get_class($this->page) == 'Page') { //if(!$form->description) $form->description = $this->_("This page is currently unpublished"); $field2 = $this->modules->get('InputfieldSubmit'); $field2->attr('name', 'submit_save'); $field2->attr('id', 'submit_save_unpublished'); $field2->class .= ' ui-priority-secondary head_button_clone'; $field2->attr('value', $this->_('Save + Keep Unpublished')); // Button: save unpublished } // $saveName remains blank if page is not publishable if($this->page->publishable()) $saveName = 'submit_publish'; $saveLabel = $this->_("Publish"); // Button: publish } else { $saveName = 'submit_save'; $saveLabel = $this->_("Save"); // Button: save } if($saveName) { $field = $this->modules->get('InputfieldSubmit'); $field->attr('id+name', $saveName); if($saveName != 'submit_publish') $field->attr('class', $field->class . ' head_button_clone'); $field->attr('value', $saveLabel); $field->addClass('head_button_clone'); $form->append($field); } if($field2) $form->append($field2); if($this->input->get->modal && $this->page->className() == 'Page') { $field = $this->modules->get('InputfieldSubmit'); $field->attr('name', $saveName); $field->attr('id', 'submit_save_top'); $field->attr('value', $saveLabel); $form->append($field); } $field = $this->modules->get('InputfieldHidden'); $field->attr('name', 'id'); $field->attr('value', $this->page->id); $form->append($field); return $form; } /** * Build the 'content' tab on the Page Edit form * */ protected function ___buildFormContent() { $fields = $this->page->getInputfields(); $id = $this->className() . 'Content'; $title = $this->_('Content'); // Tab Label: Content $fields->attr('id', $id); $fields->attr('title', $title); $this->tabs[$id] = $title; if($this->page->template->nameContentTab) { // name $field = $this->modules->get('InputfieldPageName'); $field->attr('name', '_pw_page_name'); $field->attr('value', $this->page->name); $field->required = $this->page->id != 1; $field->slashUrls = $this->page->template->slashUrls; if(!$this->page->editable('name')) { $field->attr('disabled', 'disabled'); $field->required = false; } if($this->page->parent) $field->parentPage = $this->page->parent; $fields->prepend($field); } return $fields; } /** * Build the 'children' tab on the Page Edit form * */ protected function ___buildFormChildren() { $wrapper = new InputfieldWrapper(); $id = $this->className() . 'Children'; $wrapper->attr('id', $id); $title = $this->_('Children'); // Tab Label: Children if($this->page->numChildren) $title = "$title"; $wrapper->attr('title', $title); $this->tabs[$id] = $title; $templateSortfield = $this->page->template->sortfield; if(!$this->isPost) { $pageListParent = $this->page ? $this->page : $this->parent; if($pageListParent->numChildren) { $pageList = $this->modules->get('ProcessPageList'); $pageList->set('id', $pageListParent->id); $pageList->set('showRootPage', false); } else $pageList = null; $field = $this->modules->get("InputfieldMarkup"); $field->label = $this->_("Children / Subpages"); // Children field label if($pageList) $field->value = $pageList->execute(); else $field->description = $this->_("There are currently no children/subpages below this page."); if($templateSortfield && $templateSortfield != 'sort') { $field->notes = sprintf($this->_('Children are sorted by "%s", per the template setting.'), $templateSortfield); } if($this->page->addable()) { $button = $this->modules->get("InputfieldButton"); $button->attr('id+name', 'AddPageBtn'); $button->attr('value', $this->_('Add New Page Here')); // Button: add new child page $button->icon = 'plus-circle'; $button->attr('href', "../add/?parent_id={$this->page->id}" . ($this->input->get->modal ? "&modal=1" : '')); $field->append($button); } $wrapper->append($field); } if(empty($this->page->template->sortfield) && $this->user->hasPermission('page-sort', $this->page)) { $sortfield = $this->page->sortfield && $this->page->sortfield != 'sort' ? $this->page->sortfield : ''; $fieldset = self::buildFormSortfield($sortfield, $this); $fieldset->label = $this->_('Sort Settings'); // Children sort settings field label $fieldset->icon = 'sort'; $fieldset->description = $this->_("If you want all current and future children to automatically sort by a specific field, select the field below and optionally check the 'reverse' checkbox to make the sort descending. Leave the sort field blank if you want to be able to drag-n-drop to your own order."); // Sort settings description text $wrapper->append($fieldset); } return $wrapper; } /** * Build the sortfield configuration fieldset * * NOTE: This is also used by ProcessTemplate, so it is self contained * * @param string $sortfield Current sortfield value * @param Process $caller The calling process * @return InputfieldFieldset * */ public static function buildFormSortfield($sortfield, Process $caller) { $fieldset = wire('modules')->get("InputfieldFieldset"); if(!$sortfield) $fieldset->collapsed = Inputfield::collapsedYes; $field = wire('modules')->get('InputfieldSelect'); $field->name = 'sortfield'; $field->value = ltrim($sortfield, '-'); $field->columnWidth = 60; $field->label = __('Children are sorted by', __FILE__); // Children sort field label // if in ProcessTemplate, give a 'None' option that indicates the Page has control if($caller instanceof ProcessTemplate) $field->addOption('', __('None', __FILE__)); $field->addOption('sort', __('Manual drag-n-drop', __FILE__)); $options = array( 'name' => 'name', 'status' => 'status', 'modified' => 'modified', 'created' => 'created', ); $field->addOption(__('Native Fields', __FILE__), $options); // Optgroup label for sorting by fields native to ProcessWire $customOptions = array(); foreach(wire('fields') as $f) { //if(!($f->flags & Field::flagAutojoin)) continue; if($f->flags & Field::flagSystem && $f->name != 'title') continue; if($f->type instanceof FieldtypeFieldsetOpen) continue; $customOptions[$f->name] = $f->name; } ksort($customOptions); $field->addOption(__('Custom Fields', __FILE__), $customOptions); // Optgroup label for sorting by custom fields $fieldset->append($field); $f = wire('modules')->get('InputfieldCheckbox'); $f->value = 1; $f->attr('id+name', 'sortfield_reverse'); $f->label = __('Reverse sort direction?', __FILE__); // Checkbox labe to reverse the sort direction $f->icon = 'rotate-left'; if(substr($sortfield, 0, 1) == '-') $f->attr('checked', 'checked'); $f->showIf = "sortfield!='', sortfield!=sort"; $f->columnWidth = 40; $fieldset->append($f); return $fieldset; } /** * Build the 'settings' tab on the Page Edit form * */ protected function ___buildFormSettings() { $wrapper = new InputfieldWrapper(); $id = $this->className() . 'Settings'; $title = $this->_('Settings'); // Tab Label: Settings $wrapper->attr('id', $id); $wrapper->attr('title', $title); $this->tabs[$id] = $title; // name if(($this->page->id > 1 || wire('modules')->isInstalled('LanguageSupportPageNames')) && !$this->page->template->nameContentTab) { $field = $this->modules->get('InputfieldPageName'); $field->attr('value', $this->page->name); $field->attr('name', '_pw_page_name'); $field->slashUrls = $this->page->template->slashUrls; if($field->value && !$this->page->editable('name')) $field->attr('disabled', 'disabled'); $field->required = $this->page->id != 1; if($this->page->parent) $field->parentPage = $this->page->parent; $wrapper->prepend($field); } // template if($this->page->editable('template')) { $languages = wire('languages'); $language = wire('user')->language; $field = $this->modules->get('InputfieldSelect'); $field->attr('id+name', 'template'); $field->attr('value', $this->page->template->id); $field->required = true; foreach($this->getAllowedTemplates() as $template) { $label = ''; if($languages && $language) $label = $template->get('label' . $language->id); if(!$label) $label = $template->label ? $template->label : $template->name; $field->addOption($template->id, $label); } } else { $field = $this->modules->get('InputfieldMarkup'); $field->attr('value', "

" . $this->page->template->getLabel() . "

"); } $field->label = $this->_('Template'); // Settings: Template field label $wrapper->add($field); // parent if($this->page->id > 1 && $this->page->editable('parent')) { $field = $this->modules->get('InputfieldPageListSelect'); $field->label = $this->_('Parent'); // Settings: Parent field label $field->attr('id+name', 'parent_id'); $field->required = true; $field->attr('value', $this->page->parent_id); $field->parent_id = 0; $wrapper->add($field); } // createdUser if($this->page->id && $this->user->isSuperuser() && $this->page->template->allowChangeUser) { $field = $this->modules->get('InputfieldPageListSelect'); $field->label = $this->_('Created by User'); $field->attr('id+name', 'created_users_id'); $field->attr('value', $this->page->created_users_id); $field->parent_id = $this->config->usersPageID; $field->showPath = false; $field->required = true; $wrapper->add($field); } // status $wrapper->add($this->buildFormStatus()); // informational sections if(!$this->isPost) { $wrapper->add($this->buildFormRoles()); $wrapper->add($this->buildFormInfo()); } return $wrapper; } /** * Build the Settings > Info fieldset on the Page Edit form * */ protected function buildFormInfo() { $page = $this->page; $dateFormat = $this->config->dateFormat; $unknown = '[?]'; $field = $this->modules->get("InputfieldMarkup"); $createdName = $page->createdUser->name; $modifiedName = $page->modifiedUser->name; if(empty($createdName)) $createdName = $unknown; if(empty($modifiedName)) $modifiedName = $unknown; if($this->wire('user')->isSuperuser()) { $url = $this->wire('config')->urls->admin . 'access/users/edit/?id='; if($createdName != $unknown && $page->createdUser instanceof User) $createdName = "$createdName"; if($modifiedName != $unknown && $page->modifiedUser instanceof User) $modifiedName = "$modifiedName"; } $lowestDate = strtotime('1974-10-10'); $createdDate = $page->created > $lowestDate ? date($dateFormat, $page->created) . " (" . wireRelativeTimeStr($page->created) . ")" : $unknown; $modifiedDate = $page->modified > $lowestDate ? date($dateFormat, $page->modified) . " (" . wireRelativeTimeStr($page->modified) . ")" : $unknown; $info = "\n

" . sprintf($this->_('Created by %1$s on %2$s'), $createdName, $createdDate) . "
" . // Settings: created user/date information line sprintf($this->_('Last modified by %1$s on %2$s'), $modifiedName, $modifiedDate) . // Settings: modified user/date information line "

"; $field->attr('id+name', 'ProcessPageEditInfo'); $field->label = $this->_('Info'); // Settings: Info field label $field->value = $info; //$field->collapsed = Inputfield::collapsedYes; return $field; } /** * Build the Settings > Status fieldset on the Page Edit form * */ protected function buildFormStatus() { $status = (int) $this->page->status; $field = $this->modules->get('InputfieldCheckboxes'); $field->attr('name', 'status'); $statuses = array(); $statuses[Page::statusHidden] = $this->_('Hidden: Excluded from lists and searches'); // Settings: Hidden status checkbox label if($this->user->hasPermission('page-lock', $this->page)) $statuses[Page::statusLocked] = $this->_('Locked: Not editable'); // Settings: Locked status checkbox label if(!$this->page->template->noUnpublish && $this->page->publishable()) $statuses[Page::statusUnpublished] = $this->_('Unpublished: Not visible on site'); // Settings: Unpublished status checkbox label if($this->config->advanced && $this->user->isSuperuser()) { $statuses[Page::statusSystemID] = "System: Non-deleteable and locked ID (status not removeable via API)"; $statuses[Page::statusSystem] = "System: Non-deleteable and locked ID, name, template, parent (status not removeable via API)"; } $value = array(); foreach($statuses as $s => $label) { if($s & $status) $value[] = $s; $field->addOption($s, $label); } $field->attr('value', $value); $field->label = $this->_('Status'); // Settings: Status field label return $field; } /** * Build the 'delete' tab on the Page Edit form * */ protected function ___buildFormDelete() { $isTrash = $this->page->isTrash(); $isRestorable = $isTrash && $this->page->parent_id == $this->config->trashPageID; $wrapper = new InputfieldWrapper(); $id = $this->className() . 'Delete'; $deleteLabel = $this->_('Delete'); // Tab Label: Delete $wrapper->attr('id', $id); $wrapper->attr('title', $deleteLabel); $this->tabs[$id] = $deleteLabel; if($this->page->deleteable()) { $field = $this->modules->get('InputfieldCheckbox'); $field->attr('id+name', 'delete_page'); $field->attr('value', $this->page->id); if($this->isTrash || $this->page->template->noTrash) { $deleteLabel = $this->_('Delete Permanently'); // Delete permanently checkbox label } else { $deleteLabel = $this->_('Move to Trash'); // Move to trash checkbox label } $field->icon = 'trash-o'; $field->label = $deleteLabel; $field->description = $this->_('Check the box to confirm that you want to do this.'); // Delete page confirmation instruction $field->label2 = $this->_('Confirm'); $wrapper->append($field); } if(count($wrapper->children())) { $field = $this->modules->get('InputfieldButton'); $field->attr('id+name', 'submit_delete'); $field->value = $deleteLabel; $wrapper->append($field); } else { $wrapper->description = $this->_('This page may not be deleted at this time'); // Page can't be deleted message } return $wrapper; } /** * Build the 'view' tab on the Page Edit form * */ protected function buildFormView() { $label = $this->_('View'); // Tab Label: View $id = $this->className() . 'View'; $a = "$label"; $this->tabs[$id] = $a; } /** * Build the Settings > Roles fieldset on the Page Edit form * */ protected function ___buildFormRoles() { $field = $this->modules->get("InputfieldMarkup"); $field->label = $this->_('Who can access this page?'); // Roles information field label $roles = $this->page->getAccessRoles(); $editRoles = $this->page->getAccessTemplate()->editRoles; $addRoles = $this->page->getAccessTemplate()->addRoles; $createRoles = $this->page->getAccessTemplate()->createRoles; $table = $this->modules->get("MarkupAdminDataTable"); $table->headerRow(array( $this->_('Role'), // Roles table column header: Role $this->_('What they can do') // Roles table colum header: what they can do )); $table->setEncodeEntities(false); if(count($roles)) { $publishPermission = $this->fuel('permissions')->get('page-publish'); foreach($roles as $role) { $permissions = ''; $roleName = $role->name; if($roleName == 'guest') $roleName .= " " . $this->_('(everyone)'); // Identifies who guest is (everyone) $permissions .= "page-view\n"; $checkEditable = true; if($publishPermission->id && !$this->page->is(Page::statusUnpublished) && !$role->hasPermission('page-publish', $this->page)) { $checkEditable = false; } if(false !== ($key = array_search($role->id, $addRoles))) { if($role->hasPermission('page-add')) { $permissions .= "page-add\n"; unset($addRoles[$key]); } } $editable = $role->hasPermission('page-edit') && in_array($role->id, $editRoles); if($checkEditable && $editable) { foreach($role->permissions as $permission) { if(strpos($permission->name, 'page-') !== 0) continue; if(in_array($permission->name, array('page-view', 'page-publish', 'page-create', 'page-add'))) continue; $permissions .= "{$permission->name}\n"; // only page-context permissions } if($publishPermission->id && $role->hasPermission('page-publish', $this->page)) { $permissions .= "page-publish\n"; } } if(in_array($role->id, $createRoles) && $editable) { $permissions .= "page-create\n"; } $table->row(array($roleName, nl2br($permissions))); } } if(count($addRoles)) { foreach($addRoles as $roleID) { $role = $this->fuel('roles')->get($roleID); if($role->id) $table->row(array($role->name, "page-add")); } } $table->row(array('superuser', '*')); $field->value = $table->render(); $accessParent = $this->page->getAccessParent(); if($accessParent === $this->page) { $field->notes = sprintf($this->_('Access is defined with this page\'s template: %s'), $accessParent->template); // Where access is defined: with this page's template } else { $field->notes = sprintf($this->_('Access is inherited from page "%1$s" and defined with template: %2$s'), $accessParent->path, $accessParent->template); // Where access is defined: inherited from a parent } return $field; } /** * Process the input from a submitted Page Edit form, delegating to other methods where appropriate * */ protected function ___processInput(Inputfield $form, $level = 0) { static $skipFields = array( 'sortfield_reverse', 'submit_publish', 'submit_save', 'delete_page', ); if(!$level) $form->processInput($this->input->post); $languages = $this->fuel('languages'); foreach($form as $inputfield) { $name = $inputfield->attr('name'); if($name == '_pw_page_name') $name = 'name'; if(in_array($name, $skipFields)) continue; if(!$this->page->editable($name)) continue; if($name == 'sortfield' && $this->useChildren) { $this->processInputSortfield($inputfield) ; continue; } if($this->useSettings) { if($name == 'template') { $this->processInputTemplate($inputfield); continue; } else if($name == 'created_users_id') { $this->processInputUser($inputfield); continue; } if($name == 'status' && $this->processInputStatus($inputfield)) continue; } if($name && $inputfield->isChanged()) { if($languages && $inputfield->useLanguages) { $v = $this->page->get($name); if(is_object($v)) { $v->setFromInputfield($inputfield); $this->page->set($name, $v); $this->page->trackChange($name); } else { $this->page->set($name, $inputfield->value); } } else { $this->page->set($name, $inputfield->value); } } if($inputfield instanceof InputfieldWrapper && count($inputfield->getChildren())) $this->processInput($inputfield, $level + 1); } } /** * Check to see if the page's created user has changed and make sure it's valid * */ protected function processInputUser(Inputfield $inputfield) { if(!$this->user->isSuperuser() || !$this->page->id || !$this->page->template->allowChangeUser) return; $userID = (int) $inputfield->attr('value'); if(!$userID) return; if($userID == $this->page->created_users_id) return; // no change $user = $this->pages->get($userID); if($user->template->id != $this->config->userTemplateID) return; // invalid user template if($user->parent_id != $this->config->usersPageID) return; // invalid user parent $this->page->created_users_id = $userID; $this->page->trackChange('created_users_id'); } /** * Check to see if the page's template has changed and setup a redirect to a confirmation form if it has * */ protected function processInputTemplate(Inputfield $inputfield) { if(!$inputfield->value) return true; if($this->page->template->noChangeTemplate) return true; if((!$template = $this->templates->get((int) $inputfield->value)) || ($template->id == $this->page->template->id)) return true; if(!$this->isAllowedTemplate($template)) throw new WireException(sprintf($this->_("Template '%s' is not allowed"), $template)); // Selected template is not allowed // template has changed, set a redirect URL which will confirm the change $this->redirectUrl = "template?id={$this->page->id}&template={$template->id}"; return true; } /** * Process the submitted 'status' field and account for the bitwise logic present * */ protected function processInputStatus(Inputfield $inputfield) { $status = $inputfield->value; $value = $this->page->status; if(!is_array($status)) $status = array(); $statusFlags = array(Page::statusHidden); if($this->page->publishable()) $statusFlags[] = Page::statusUnpublished; if($this->user->hasPermission('page-lock', $this->page)) $statusFlags[] = Page::statusLocked; if($this->config->advanced && $this->user->isSuperuser()) { $statusFlags[] = Page::statusSystemID; $statusFlags[] = Page::statusSystem; } foreach($statusFlags as $flag) { if(in_array($flag, $status)) { if(!($value & $flag)) $value = $value | $flag; } else if($value & $flag) { $value = $value & ~$flag; } } $this->page->status = $value; return true; } /** * Process the Children > Sortfield input * */ protected function processInputSortfield(Inputfield $inputfield) { if(!$this->user->hasPermission('page-sort', $this->page)) return true; $sortfield = $this->sanitizer->name($inputfield->value); if($sortfield != 'sort' && !empty($_POST['sortfield_reverse'])) $sortfield = '-' . $sortfield; if(empty($sortfield)) $sortfield = 'sort'; $this->page->sortfield = $sortfield; return true; } /** * Process a delete page request, moving the page to the trash if applicable * */ protected function deletePage() { if(!$this->page->deleteable()) { $this->error($this->_('This page is not deleteable')); return false; } $afterDeleteRedirect = $this->fuel('config')->urls->admin . "page/?open={$this->parent->id}"; if($this->fuel('page')->process != $this->className()) $afterDeleteRedirect = "../"; if($this->isTrash || $this->page->template->noTrash) { $this->session->message(sprintf($this->_('Deleted page: %s'), $this->page->url)); // Page deleted message $this->pages->delete($this->page, true); $this->session->redirect($afterDeleteRedirect); } else if($this->pages->trash($this->page)) { $this->session->message(sprintf($this->_('Moved page to trash: %s'), $this->page->url)); // Page moved to trash message $this->session->redirect($afterDeleteRedirect); } else { $this->error($this->_('Unable to move page to trash')); // Page can't be moved to the trash error return false; } } /** * Set the headline used in the UI * */ public function setupHeadline() { $page = $this->page ? $this->page : $this->parent; $this->setFuel('processHeadline', $page->get("title|name")); } /** * Setup the breadcrumbs used in the UI * */ public function setupBreadcrumbs() { if($this->fuel('page')->process != $this->className()) return; $breadcrumbs = new Breadcrumbs(); $breadcrumbs->add(new Breadcrumb($this->config->urls->admin . 'page/list/', "Pages")); $page = $this->page ? $this->page : $this->parent; //$lastPageList = $this->wire('session')->get('lastPageList'); //if(!is_array($lastPageList)) $lastPageList = array(); $pageListConfig = $this->modules->getModuleConfigData('ProcessPageList'); $limit = $pageListConfig['limit']; foreach($page->parents() as $cnt => $p) { $parent = $p->parent; // make breadcrumb to go pagelist with page clicked, unless pagination is active in that PageList if($limit && $parent->id && $parent->numChildren > $limit && $p->editable()) { $url = "./?id={$p->id}"; } else { // if($p->id == $page->parent_id && isset($lastPageList[$p->id])) continue; $url = "../?open=" . $p->id; } $breadcrumbs->add(new Breadcrumb($url, $p->get("title|name"))); } /* @todo add lastPageList support if(isset($lastPageList[$page->parent_id])) { $info = $lastPageList[$page->parent_id]; $url = $info['url'] . "?open=$page->id"; if($info['n'] > 1) $url .= "&n=$info[n]"; $breadcrumbs->add(new Breadcrumb($url, $page->parent->get("title|name"))); } */ $this->setFuel('breadcrumbs', $breadcrumbs); } /** * Execute a template change for a page, building an info + confirmation form * */ public function ___executeTemplate() { if(!$this->useSettings || !$this->user->hasPermission('page-template', $this->page)) throw new WireException("You don't have permission to change the template on this page."); if(!isset($_GET['template'])) throw new WireException("This method requires a 'template' get var"); $template = $this->templates->get((int) $_GET['template']); if(!$template) throw new WireException("Unknown template"); if(!$this->isAllowedTemplate($template)) throw new WireException("That template is not allowed"); $form = $this->modules->get("InputfieldForm"); $form->attr('action', 'saveTemplate'); $form->attr('method', 'post'); $form->description = sprintf($this->_('Change template from "%1$s" to "%2$s"'), $this->page->template, $template); // Change template A to B headline $f = $this->modules->get("InputfieldMarkup"); $f->label = $this->_('Confirm template change'); // Change template confirmation subhead $list = ''; foreach($this->page->template->fieldgroup as $field) { if(!$template->fieldgroup->has($field)) $list .= "
  • $field
  • "; } if(!$list) $this->executeSaveTemplate($template); $f->description = $this->_('Warning, changing the template will delete the following fields:'); // Headline that precedes list of fields that will be deleted as a result of template change $f->attr('value', ""); $form->append($f); $f = $this->modules->get("InputfieldCheckbox"); $f->attr('name', 'template'); $f->attr('value', $template->id); $f->label = $this->_('Are you sure?'); // Checkbox label to confirm they want to change template $f->description = $this->_('Please confirm that you understand the above by clicking the checkbox below.'); // Checkbox description to confirm they want to change template $form->append($f); $f = $this->modules->get("InputfieldHidden"); $f->attr('name', 'id'); $f->attr('value', $this->page->id); $form->append($f); $f = $this->modules->get("InputfieldSubmit"); $form->append($f); $this->fuel('breadcrumbs')->add(new Breadcrumb("./?id={$this->page->id}", $this->page->get("title|name"))); return $form->render(); } /** * Save a template change for a page * */ public function ___executeSaveTemplate($template = null) { if(!$this->useSettings || !$this->user->hasPermission('page-template', $this->page)) throw new WireException($this->_("You don't have permission to change the template on this page.")); // Error: user doesn't have permission to change template if(!$this->page->template->noChangeTemplate) { if(!is_null($template) || (isset($_POST['template']) && ($template = $this->templates->get((int) $_POST['template'])))) { try { if(!$this->isAllowedTemplate($template)) throw new WireException($this->_('That template is not allowed')); // Error: selected template is not allowed $this->page->template = $template; $this->page->save(); $this->message(sprintf($this->_("Changed template to '%s'"), $template)); // Message: template was changed } catch(Exception $e) { $this->error($e->getMessage()); } } } $this->session->redirect("./?id={$this->page->id}"); } /** * Returns an array of templates that are allowed to be used here * */ protected function getAllowedTemplates() { if(is_array($this->allowedTemplates)) return $this->allowedTemplates; $templates = array(); $user = $this->fuel('user'); $isSuperuser = $user->isSuperuser(); $page = $this->page; $parent = $this->page->parent; $parentEditable = ($parent->id && $parent->editable()); // current page template is assumed, otherwise we wouldn't be here $templates[$page->template->id] = $page->template; // check if they even have permission to change it if(!$user->hasPermission('page-template', $page) || $page->template->noChangeTemplate) { $this->allowedTemplates = $templates; return $templates; } foreach(wire('templates') as $template) { if(isset($templates[$template->id])) continue; if($template->flags & Template::flagSystem) { if($template->name == 'user' && $parent->id != $this->config->usersPageID) continue; if($template->name == 'role' && $parent->id != $this->config->rolesPageID) continue; if($template->name == 'permission' && $parent->id != $this->config->permissionsPageID) continue; } if(count($template->parentTemplates) && $parent->id && !in_array($parent->template->id, $template->parentTemplates)) { // this template specifies it can only be used with certain parents, and our parent's template isn't one of them continue; } if($parent->id && count($parent->template->childTemplates)) { // the page's parent only allows certain templates for it's children // if this isn't one of them, then continue; if(!in_array($template->id, $parent->template->childTemplates)) continue; } if($isSuperuser) { $templates[$template->id] = $template; } else if($template->noParents) { // user can't change to a template that has been specified as no more instances allowed // except for superuser... we'll let them do it continue; } else if((!$template->useRoles && $parentEditable) || $user->hasPermission('page-edit', $template)) { // determine if the template's assigned roles match up with the users's roles // and that at least one of those roles has page-edit permission if($user->hasPermission('page-create', $page)) { // user is allowed to create more pages of this type, so template may be used $templates[$template->id] = $template; } } } $this->allowedTemplates = $templates; return $templates; } /** * Is the given template or template ID allowed here? * */ protected function isAllowedTemplate($id) { // if $id is a template, then convert it to it's numeric ID if(is_object($id) && $id instanceof Template) $id = $id->id; $id = (int) $id; // if the template is the same one already in place, of course it's allowed if($id == $this->page->template->id) return true; // if we've made it this far, then get a list of templates that are allowed... $templates = $this->getAllowedTemplates(); // ...and determine if the supplied template is in that list return isset($templates[$id]); } /** * Save only the fields posted * * Field name must be included in server header HTTP_X_FIELDNAME or directly in the POST vars. * * Note that fields that would be not present in POST vars (like a checkbox) are only supported by the HTTP_X_FIELDNAME version. * * Works for custom fields only at present. * */ protected function ___ajaxSave(Page $page) { if($this->config->demo) throw new WireException("Ajax save is disabled in demo mode"); if($page->is(Page::statusLocked)) throw new WireException($this->noticeLocked); if(!$this->ajaxEditable($page)) throw new WirePermissionException($this->noticeNoAccess); $this->session->CSRF->validate(); // throws exception when invalid $form = new InputfieldWrapper(); $form->useDependencies = false; $keys = array(); if(isset($_SERVER['HTTP_X_FIELDNAME'])) { $keys[] = $this->sanitizer->fieldName($_SERVER['HTTP_X_FIELDNAME']); } else { foreach($this->input->post as $key => $unused) { if($key == 'id') continue; $keys[] = $this->sanitizer->fieldName($key); } } foreach($keys as $key) { if(!$field = $page->template->fieldgroup->getField($key)) continue; if(!$this->ajaxEditable($page, $key)) continue; if(!$inputfield = $field->getInputfield($page)) continue; $inputfield->showIf = ''; // cancel showIf dependencies since other fields may not be present $inputfield->name = $key; $inputfield->value = $page->get($key); $form->add($inputfield); } $form->processInput($this->input->post); $page->setTrackChanges(true); $numFields = 0; $lastFieldName = null; foreach($form->children() as $inputfield) { $page->set($inputfield->name, $inputfield->value); $numFields++; $lastFieldName = $inputfield->name; } if($page->isChanged()) { if($numFields === 1) { $page->save((string)$lastFieldName); $this->message("Saved page '{$page->id}' field '$lastFieldName'"); } else { $page->save(); $this->message("Saved page '{$page->id}' multiple fields"); } } else { $this->message("Page not saved (no changes)"); } } /** * Returns true if this page may be ajax saved (user has access), or false if not * * @param Page $page * @param string $fieldName Optional field name * @return bool * */ protected function ___ajaxEditable(Page $page, $fieldName = '') { return $page->editable($fieldName); } /** * Return instance of the Page being edited * * For Inputfields/Fieldtypes to use if they want to retrieve the editing page rather than the viewing page * */ public function getPage() { return $this->page; } }