/** * Save individual Page fields and supporting actions * * triggers hooks: saved, added, moved, renamed, templateChanged * * @param Page $page * @param bool $isNew * @param array $options * @return bool * */ protected function savePageFinish(Page $page, $isNew, array $options) { $changes = $page->getChanges(); $changesValues = $page->getChanges(true); // update children counts for current/previous parent if ($isNew) { $page->parent->numChildren++; } else { if ($page->parentPrevious && $page->parentPrevious->id != $page->parent->id) { $page->parentPrevious->numChildren--; $page->parent->numChildren++; } } // if page hasn't changed, don't continue further if (!$page->isChanged() && !$isNew) { $this->debugLog('save', '[not-changed]', true); $this->saved($page, array()); return true; } // if page has a files path (or might have previously), trigger filesManager's save if (PagefilesManager::hasPath($page)) { $page->filesManager->save(); } // disable outputFormatting and save state $of = $page->of(); $page->of(false); // when a page is statusCorrupted, it records what fields are corrupted in _statusCorruptedFields array $corruptedFields = $page->hasStatus(Page::statusCorrupted) ? $page->_statusCorruptedFields : array(); // save each individual Fieldtype data in the fields_* tables foreach ($page->fieldgroup as $field) { if (isset($corruptedFields[$field->name])) { continue; } // don't even attempt save of corrupted field if (!$field->type) { continue; } try { $field->type->savePageField($page, $field); } catch (Exception $e) { $error = sprintf($this->_('Error saving field "%s"'), $field->name) . ' - ' . $e->getMessage(); $this->trackException($e, true, $error); } } // return outputFormatting state $page->of($of); if (empty($page->template->sortfield)) { $this->sortfields->save($page); } if ($options['resetTrackChanges']) { $page->resetTrackChanges(); } // determine whether we'll trigger the added() hook if ($isNew) { $page->setIsNew(false); $triggerAddedPage = $page; } else { $triggerAddedPage = null; } // check for template changes if ($page->templatePrevious && $page->templatePrevious->id != $page->template->id) { // the template was changed, so we may have data in the DB that is no longer applicable // find unused data and delete it foreach ($page->templatePrevious->fieldgroup as $field) { if ($page->template->fieldgroup->has($field)) { continue; } $field->type->deletePageField($page, $field); $this->message("Deleted field '{$field}' on page {$page->url}", Notice::debug); } } if ($options['uncacheAll']) { $this->uncacheAll($page); } // determine whether the pages_access table needs to be updated so that pages->find() // operations can be access controlled. if ($isNew || $page->parentPrevious || $page->templatePrevious) { new PagesAccess($page); } // lastly determine whether the pages_parents table needs to be updated for the find() cache // and call upon $this->saveParents where appropriate. if ($page->parentPrevious && $page->numChildren > 0) { // page is moved and it has children $this->saveParents($page->id, $page->numChildren); if ($page->parent->numChildren == 1) { $this->saveParents($page->parent_id, $page->parent->numChildren); } } else { if ($page->parentPrevious && $page->parent->numChildren == 1 || $isNew && $page->parent->numChildren == 1 || $page->forceSaveParents) { // page is moved and is the first child of it's new parent // OR page is NEW and is the first child of it's parent // OR $page->forceSaveParents is set (debug/debug, can be removed later) $this->saveParents($page->parent_id, $page->parent->numChildren); } } if ($page->parentPrevious && $page->parentPrevious->numChildren == 0) { // $page was moved and it's previous parent is now left with no children, this ensures the old entries get deleted $this->saveParents($page->parentPrevious->id, 0); } // trigger hooks $this->saved($page, $changes, $changesValues); if ($triggerAddedPage) { $this->added($triggerAddedPage); } if ($page->namePrevious && $page->namePrevious != $page->name) { $this->renamed($page); } if ($page->parentPrevious) { $this->moved($page); } if ($page->templatePrevious) { $this->templateChanged($page); } if (in_array('status', $changes)) { $this->statusChanged($page); } $this->debugLog('save', $page, true); return true; }
/** * Is the given page in a state where it can be saved? * * @param Page $page * @param string $reason Text containing the reason why it can't be saved (assuming it's not saveable) * @return bool True if saveable, False if not * */ public function isSaveable(Page $page, &$reason) { $saveable = false; $outputFormattingReason = "Call \$page->setOutputFormatting(false) before getting/setting values that will be modified and saved. "; if ($page instanceof NullPage) { $reason = "Pages of type NullPage are not saveable"; } else { if ((!$page->parent || $page->parent instanceof NullPage) && $page->id !== 1) { $reason = "It has no parent assigned"; } else { if (!$page->template) { $reason = "It has no template assigned"; } else { if (!strlen(trim($page->name))) { $reason = "It has an empty 'name' field"; } else { if ($page->is(Page::statusCorrupted)) { $reason = $outputFormattingReason . " [Page::statusCorrupted]"; } else { if ($page->id == 1 && !$page->template->useRoles) { $reason = "Selected homepage template cannot be used because it does not define access."; } else { if ($page->id == 1 && !$page->template->hasRole('guest')) { $reason = "Selected homepage template cannot be used because it does not have the required 'guest' role in it's access settings."; } else { $saveable = true; } } } } } } } // check if they could corrupt a field by saving if ($saveable && $page->outputFormatting) { // iternate through recorded changes to see if any custom fields involved foreach ($page->getChanges() as $change) { if ($page->template->fieldgroup->getField($change) !== null) { $reason = $outputFormattingReason . " [{$change}]"; $saveable = false; break; } } // iterate through already-loaded data to see if any are objects that have changed if ($saveable) { foreach ($page->getArray() as $key => $value) { if (!$page->template->fieldgroup->getField($key)) { continue; } if (is_object($value) && $value instanceof Wire && $value->isChanged()) { $reason = $outputFormattingReason . " [{$key}]"; $saveable = false; break; } } } } // check for a parent change if ($saveable && $page->parentPrevious && $page->parentPrevious->id != $page->parent->id) { // page was moved if ($page->template->noMove && ($page->is(Page::statusSystem) || $page->is(Page::statusSystemID) || !$page->isTrash())) { // make sure the page's template allows moves. only move laways allowed is to the trash, unless page has system status $saveable = false; $reason = "Pages using template '{$page->template}' are not moveable (template::noMove)"; } else { if ($page->parent->template->noChildren) { $saveable = false; $reason = "Chosen parent '{$page->parent->path}' uses template that does not allow children."; } else { if ($page->parent->id && $page->parent->id != $this->config->trashPageID && count($page->parent->template->childTemplates) && !in_array($page->template->id, $page->parent->template->childTemplates)) { // make sure the new parent's template allows pages with this template $saveable = false; $reason = "Can't move '{$page->name}' because Template '{$page->parent->template}' used by '{$page->parent->path}' doesn't allow children with this template."; } else { if (count($page->template->parentTemplates) && $page->parent->id != $this->config->trashPageID && !in_array($page->parent->template->id, $page->template->parentTemplates)) { $saveable = false; $reason = "Can't move '{$page->name}' because Template '{$page->parent->template}' used by '{$page->parent->path}' is not allowed by template '{$page->template->name}'."; } else { if (count($page->parent->children("name={$page->name},status<" . Page::statusMax))) { $saveable = false; $reason = "Chosen parent '{$page->parent->path}' already has a page named '{$page->name}'"; } } } } } } return $saveable; }