diff --git a/wire/core/PagesEditor.php b/wire/core/PagesEditor.php index b8d23292..2dc6aebe 100644 --- a/wire/core/PagesEditor.php +++ b/wire/core/PagesEditor.php @@ -566,11 +566,14 @@ class PagesEditor extends Wire { protected function savePageQueryException(Page $page, $query, $exception, array $options) { $errorCode = $exception->getCode(); - if($errorCode != 23000) return false; + + // 23000=integrity constraint violation, duplicate entry + if($errorCode != 23000) return false; if(!$this->pages->names()->hasAutogenName($page) && !$options['adjustName']) return false; // account for the duplicate possibly being a multi-language name field + // i.e. “Duplicate entry 'bienvenido-2-1001' for key 'name1013_parent_id'” if($this->wire('languages') && preg_match('/\b(name\d*)_parent_id\b/', $exception->getMessage(), $matches)) { $nameField = $matches[1]; } else { @@ -1025,28 +1028,15 @@ class PagesEditor extends Wire { if(is_string($options)) $options = Selectors::keyValueStringToArray($options); if(!isset($options['recursionLevel'])) $options['recursionLevel'] = 0; // recursion level + if($parent === null) $parent = $page->parent; - if(isset($options['set']) && isset($options['set']['name'])) { + if(isset($options['set']) && isset($options['set']['name']) && strlen($options['set']['name'])) { $name = $options['set']['name']; - } else { - // if parent is not changing, we have to modify name now - if(is_null($parent) || $parent->id == $page->parent->id) { - $parent = $page->parent; - $n = 1; - $name = $page->name . '-' . $n; - } else { - $name = $page->name; - $n = 0; - } - - // make sure that we have a unique name - while(count($parent->children("name=$name, include=all"))) { - $name = $page->name; - $nStr = "-" . (++$n); - if(strlen($name) + strlen($nStr) > Pages::nameMaxLength) $name = substr($name, 0, Pages::nameMaxLength - strlen($nStr)); - $name .= $nStr; - } + $name = $this->pages->names()->uniquePageName(array( + 'name' => $page->name, + 'parent' => $parent + )); } $of = $page->of(); diff --git a/wire/core/PagesNames.php b/wire/core/PagesNames.php index 933194ca..f3a8f78d 100644 --- a/wire/core/PagesNames.php +++ b/wire/core/PagesNames.php @@ -40,6 +40,14 @@ class PagesNames extends Wire { */ protected $delimiter = '-'; + /** + * Max length for page names + * + * @var int + * + */ + protected $nameMaxLength = 128; + /** * Construct * @@ -51,9 +59,10 @@ class PagesNames extends Wire { $pages->wire($this); $untitled = $this->wire('config')->pageNameUntitled; if($untitled) $this->untitledPageName = $untitled; + $this->nameMaxLength = Pages::nameMaxLength; parent::__construct(); } - + /** * Assign a name to given Page (if it doesn’t already have one) * @@ -224,17 +233,34 @@ class PagesNames extends Wire { * date() format, PHP strftime() format, as well as some other predefined options. * * @param Page $page - * @param string $format Optional format. If not specified, pulls from $page’s parent template. - * + * @param string|array $format Optional format. If not specified, pulls from $page’s parent template. + * @param array $options Options to modify behavior. May also be specified in $format argument. + * - `language` (Language|string): Language to use + * - `format` (string): Optional format to use, if $options were specified in $format argument. * @return string * */ - public function pageNameFromFormat(Page $page, $format = '') { + public function pageNameFromFormat(Page $page, $format = '', array $options = array()) { + $defaults = array( + 'format' => '', + 'language' => null, + ); + + if(is_array($format)) { + $options = $format; + $format = empty($options['format']) ? '' : $options['format']; + } + + $options = array_merge($defaults, $options); if(!strlen($format)) $format = $this->defaultPageNameFormat($page); $format = trim($format); $name = ''; + if($options['language']) { + $this->wire('languages')->setLanguage($options['language']); + } + if($format === 'title' && !strlen(trim((string) $page->title))) { $format = 'untitled-time'; } @@ -285,9 +311,15 @@ class PagesNames extends Wire { $name = $format; } + if(strlen($name) > $this->nameMaxLength) $name = $this->adjustNameLength($name); + $utf8 = $this->wire('config')->pageNameCharset === 'UTF8'; $sanitizer = $this->wire('sanitizer'); $name = $utf8 ? $sanitizer->pageNameUTF8($name) : $sanitizer->pageName($name, Sanitizer::translate); + + if($options['language']) { + $this->wire('languages')->unsetLanguage(); + } return $name; } @@ -303,35 +335,61 @@ class PagesNames extends Wire { * The returned value is not yet assigned to the given $page, so if it is something different than what * is already on $page, you’ll want to assign it manually after this. * - * @param string|Page $name Name to make unique, or Page to pull it from. - * @param Page||string|null You may optionally specify Page or name in this argument if not in the first. - * Note that specifying a Page here or in the first argument is important if the page already exists, as it is used - * as the page to exclude when checking for name collisions, and we want to exclude $page from that check. + * @param string|Page|array $name Name to make unique + * You may optionally substitute the $page argument or $options arguments here, if that is all you need. + * @param Page||string|null|array Page to exclude from duplicate check and/or to pull $name or parent from (if not otherwise specified). + * Note that specifying a Page is important if the page already exists, as it is used as the page to exclude when checking for + * name collisions, and we want to exclude $page from that check. You may optionally substitute the $options or $name arguments + * here, if that is all you need. If $parent or $name are specified separately from this $page argument, they will override + * whatever parent or name settings are on this $page argument. * @param array $options * - `parent` (Page|null): Optionally specify a different parent if $page does not currently have the parent you want to use. - * - `language` (Language|int): Get unique for this language (if multi-language page names active). + * - `language` (Language|int): Get unique for this language (if multi-language page names active). + * - `page` (Page|null): If you specified no $page argument, you can optionally bundle it in the $options array. + * - `name` (string): If you specified no $name argument, you can optionally bundle it in the $options array. * @return string Returns unique name * */ public function uniquePageName($name = '', $page = null, array $options = array()) { $defaults = array( + 'name' => '', 'page' => null, 'parent' => null, 'language' => null ); - $options = array_merge($defaults, $options); - - if($name instanceof Page) { + // handle argument substitutions + if(is_array($page)) { + // options specified in $page argument + $options = $page; + $page = !empty($options['page']) ? $options['page'] : null; + } else if(is_array($name)) { + // options specified in $name argument + $options = $name; + $name = !empty($options['name']) ? $options['name'] : ''; + } else if($name instanceof Page) { + // $page argument specified in $name argument $_name = is_string($page) ? $page : ''; $page = $name; $name = $_name; } + $options = array_merge($defaults, $options); + + if(empty($page) && !empty($options['page'])) $page = $options['page']; + if(empty($name) && !empty($options['name'])) $name = $options['name']; + if($page) { if($options['parent'] === null) $options['parent'] = $page->parent(); - if(!strlen($name)) $name = $page->name; + if(!strlen($name)) { + if($options['language']) { + $name = $page->get("name$options[language]"); + if(!strlen($name)) $name = $page->name; + } else { + $name = $page->name; + } + } $options['page'] = $page; } @@ -342,7 +400,7 @@ class PagesNames extends Wire { 'fallbackFormat' => $page->id ? 'random' : 'untitled-time', 'parent' => $options['parent'] )); - $name = $this->pageNameFromFormat($page, $format); + $name = $this->pageNameFromFormat($page, $format, array('language' => $options['language'])); } else { $name = $this->uniqueRandomPageName(); } @@ -351,6 +409,8 @@ class PagesNames extends Wire { while($this->pageNameExists($name, $options)) { $name = $this->incrementName($name); } + + if(strlen($name) > $this->nameMaxLength) $name = $this->adjustNameLength($name); return $name; } @@ -365,7 +425,7 @@ class PagesNames extends Wire { */ public function adjustNameLength($name, $maxLength = 0) { - if($maxLength < 1) $maxLength = Pages::nameMaxLength; + if($maxLength < 1) $maxLength = $this->nameMaxLength; if(strlen($name) <= $maxLength) return $name; $trims = implode('', $this->delimiters); @@ -415,16 +475,22 @@ class PagesNames extends Wire { list($namePrefix, $n) = $this->nameAndNumber($name); if($namePrefix !== $name) { + // name already had an increment if($num) { + // specific number was supplied $num = (int) $num; $name = $namePrefix . $this->delimiter . $num; } else { + // no number supplied + // make sure that any leading zeros are retained before we increment number $zeros = ''; while(strpos($name, $namePrefix . $this->delimiter . "0$zeros") === 0) $zeros .= '0'; + // increment the number $name = $namePrefix . $this->delimiter . $zeros . (++$n); } } else { - if(!is_int($num)) $num = 1; + // name does not yet have an increment, so make it "name-1" + if(!is_int($num) || $num < 1) $num = 1; $name = $namePrefix . $this->delimiter . $num; } @@ -433,14 +499,16 @@ class PagesNames extends Wire { /** * Is the given name is use by a page? + * + * If the `multilang` option is used, it checks if the page name exists in any language. + * IF the `language` option is used, it only checks that particular language (regardless of `multilang` option). * * @param string $name * @param array $options * - `page` (Page|int): Ignore this Page or page ID * - `parent` (Page|int): Limit search to only this parent. * - `multilang` (bool): Check other languages if multi-language page names supported? (default=false) - * - `language` (Language|int): Limit check to only this language [also implies multilang option] (default=null) - * + * - `language` (Language|int): Limit check to only this language (default=null) * @return int Returns quantity of pages using name, or 0 if name not in use. * */ @@ -465,9 +533,9 @@ class PagesNames extends Wire { if($languages) { foreach($languages as $language) { if($options['language'] && "$options[language]" !== "$language") continue; - $property = $language->isDefault() ? 'name' : 'name' . (int) $language->id; - $wheres[] = "$property=:name$language->id"; - $binds[":name$language->id"] = $name; + $property = $language->isDefault() ? "name" : "name" . (int) $language->id; + $wheres[] = "$property=:$property"; + $binds[":$property"] = $name; } $wheres = array('(' . implode(' OR ', $wheres) . ')'); } else { diff --git a/wire/modules/LanguageSupport/LanguageSupportPageNames.module b/wire/modules/LanguageSupport/LanguageSupportPageNames.module index f5fef902..4cb5724b 100644 --- a/wire/modules/LanguageSupport/LanguageSupportPageNames.module +++ b/wire/modules/LanguageSupport/LanguageSupportPageNames.module @@ -854,18 +854,22 @@ class LanguageSupportPageNames extends WireData implements Module, ConfigurableM /** @var Page $page */ $page = $event->arguments[0]; + /** @var Pages $pages */ $pages = $event->object; + + /** @var Sanitizer $sanitizer */ + $sanitizer = $this->wire('sanitizer'); + + /** @var array $extraData */ $extraData = $event->return; - if(!is_array($extraData)) $extraData = array(); + $alwaysActiveTypes = array('User', 'Role', 'Permission', 'Language'); $pageNameCharset = $this->wire('config')->pageNameCharset; + $isCloning = $pages->editor()->isCloning(); + + if(!is_array($extraData)) $extraData = array(); - $defaultN = 1; - if($pages->cloning && preg_match('/^(.+)-(\d+)$/', $page->name, $matches)) { - $defaultN = (int) $matches[2]; - } - foreach($this->wire('languages') as $language) { if($language->isDefault()) continue; @@ -873,21 +877,20 @@ class LanguageSupportPageNames extends WireData implements Module, ConfigurableM // populate a name123 field for each language $name = "name$language_id"; - $value = $this->wire('sanitizer')->pageNameUTF8($page->get($name)); + $value = $sanitizer->pageNameUTF8($page->get($name)); if(!strlen($value)) { $value = 'NULL'; - } else if($pages->cloning) { + } else if($isCloning) { // this save is the result of a clone() operation // make sure that the name is unique for other languages - $newValue = $value; - while($page->parent->children("$name=$newValue, include=all")->count() > 0) { - $newValue = $value . "-$defaultN"; - $defaultN++; - } - $value = $newValue; + $value = $pages->names()->uniquePageName(array( + 'name' => $value, + 'page' => $page, + 'language' => $language, + )); } if($pageNameCharset == 'UTF8') { - $extraData[$name] = $this->wire('sanitizer')->pageName($value, Sanitizer::toAscii); + $extraData[$name] = $sanitizer->pageName($value, Sanitizer::toAscii); } else { $extraData[$name] = $value; } diff --git a/wire/modules/Process/ProcessPageClone.module b/wire/modules/Process/ProcessPageClone.module index 686792e1..da1bb377 100644 --- a/wire/modules/Process/ProcessPageClone.module +++ b/wire/modules/Process/ProcessPageClone.module @@ -6,11 +6,15 @@ * For more details about how Process modules work, please see: * /wire/core/Process.php * - * ProcessWire 3.x, Copyright 2016 by Ryan Cramer + * ProcessWire 3.x, Copyright 2018 by Ryan Cramer * https://processwire.com * * Optional GET variables: * - redirect_page (int): Contains ID of page to redirect to after clone. + * + * @method InputfieldForm buildForm() + * @method string render() + * @method void process() * */ @@ -20,7 +24,7 @@ class ProcessPageClone extends Process { return array( 'title' => __('Page Clone', __FILE__), 'summary' => __('Provides ability to clone/copy/duplicate pages in the admin. Adds a "copy" option to all applicable pages in the PageList.', __FILE__), - 'version' => 103, + 'version' => 104, 'autoload' => "template=admin", // Note: most Process modules should not be 'autoload', this is an exception. 'permission' => 'page-clone', 'permissions' => array( @@ -38,6 +42,8 @@ class ProcessPageClone extends Process { /** * The page being cloned + * + * @var Page|null * */ protected $page; @@ -72,6 +78,8 @@ class ProcessPageClone extends Process { /** * Hook into ProcessPageListActions::getExtraActions to return a 'copy' action when appropriate + * + * @param HookEvent $event * */ public function hookPageListActions(HookEvent $event) { @@ -113,13 +121,12 @@ class ProcessPageClone extends Process { public function ___execute() { $this->headline($this->_('Copy Page')); // Headline $error = $this->_("Unable to load page"); - $id = (int) $this->wire('input')->get->id; + $id = (int) $this->wire('input')->get('id'); if(!$id) throw new WireException($error); $this->page = $this->wire('pages')->get($id); if($this->page->id < 2) throw new WireException($error); if(!$this->hasPermission($this->page)) throw new WirePermissionException($error); - if($this->wire('input')->post->submit_clone) $this->process(); - //if($this->wire('config')->ajax && $_SERVER['REQUEST_METHOD'] == 'POST') return $this->processAjax(); + if($this->wire('input')->post('submit_clone')) $this->process(); return $this->render(); } @@ -132,16 +139,19 @@ class ProcessPageClone extends Process { */ public function hasPermission(Page $page) { $user = $this->user; + $parent = $page->parent(); + $parentTemplate = $parent->template; + $pageTemplate = $page->template; if($page->hasStatus(Page::statusSystem) || $page->hasStatus(Page::statusSystemID)) return false; - if($page->parent->template->noChildren) return false; - if($page->template->noParents) return false; + if($parentTemplate->noChildren) return false; + if($pageTemplate->noParents) return false; - if(count($page->parent->template->childTemplates) && !in_array($page->template->id, $page->parent->template->childTemplates)) return false; - if(count($page->template->parentTemplates) && !in_array($page->parent->template->id, $page->template->parentTemplates)) return false; + if(count($parentTemplate->childTemplates) && !in_array($pageTemplate->id, $parentTemplate->childTemplates)) return false; + if(count($pageTemplate->parentTemplates) && !in_array($parentTemplate->id, $pageTemplate->parentTemplates)) return false; if($user->isSuperuser()) return true; - if($user->hasPermission('page-create', $page) && $user->hasPermission('page-clone', $page) && $page->parent->addable()) return true; + if($user->hasPermission('page-create', $page) && $user->hasPermission('page-clone', $page) && $parent->addable()) return true; return false; } @@ -149,91 +159,119 @@ class ProcessPageClone extends Process { /** * Return array with suggested 'name' and 'title' elements for given $page * - * @param $page + * @param Page $page * @return array * */ - protected function getSuggestedNameAndTitle($page) { - $n = 0; - $name = ''; - do { - $n++; - $name = $page->name; - $nStr = "-$n"; - if(strlen($name) + strlen($nStr) > Pages::nameMaxLength) $name = substr($name, 0, Pages::nameMaxLength - strlen($nStr)); - $name .= $nStr; - } while($this->wire('pages')->count("parent=$page->parent, include=all, name=$name")); + protected function getSuggestedNameAndTitle(Page $page) { + + /** @var Pages $pages */ + $pages = $this->wire('pages'); + + $name = $pages->names()->uniquePageName(array( + 'name' => $page->name, + 'parent' => $page->parent() + )); + $copy = $this->_('(copy)'); $copyN = $this->_('(copy %d)'); $title = $page->title; - // @todo support multi-language titles + + $n = (int) $pages->names()->hasNumberSuffix($name); if(strpos($title, $copy) !== false) $title = str_replace(" $copy", '', $title); $regexCopyN = str_replace('%d', '[0-9]+', $copyN); $regexCopyN = str_replace(array('(', ')'), array('\\(', '\\)'), $regexCopyN); $title = preg_replace("/$regexCopyN/", '', $title); $title .= ' ' . ($n > 1 ? sprintf($copyN, $n) : $copy); - $result = array('name' => $name, 'title' => $title, 'n' => $n); + + $result = array( + 'name' => $name, + 'title' => $title, + 'n' => $n + ); + if($this->wire('modules')->isInstalled('LanguageSupportPageNames')) { foreach($this->wire('languages') as $language) { if($language->isDefault()) continue; $value = $page->get("name$language"); - if(strlen($value)) $result["name$language"] = $value . '-' . $n; + if(!strlen($value)) continue; + $result["name$language"] = $pages->names()->incrementName($value, $n); } } + return $result; } /** * Render a form asking for information to be used for the new cloned page. + * + * @return InputfieldForm * */ protected function ___buildForm() { + + /** @var Page $page */ + $page = $this->page; + /** @var InputfieldForm $form */ $form = $this->modules->get("InputfieldForm"); - $form->attr('action', './?id=' . $this->page->id); + $form->attr('action', './?id=' . $page->id); $form->attr('method', 'post'); - $form->description = sprintf($this->_("This will make a copy of %s"), $this->page->path); // Form description/headline + $form->description = sprintf($this->_("This will make a copy of %s"), $page->path); // Form description/headline $form->addClass('InputfieldFormFocusFirst'); - $suggested = $this->getSuggestedNameAndTitle($this->page); + $suggested = $this->getSuggestedNameAndTitle($page); + /** @var InputfieldPageTitle $titleField */ $titleField = $this->modules->get("InputfieldPageTitle"); $titleField->attr('name', 'clone_page_title'); $titleField->attr('value', $suggested['title']); $titleField->label = $this->_("Title of new page"); // Label for title field + /** @var InputfieldPageName $nameField */ $nameField = $this->modules->get("InputfieldPageName"); $nameField->attr('name', 'clone_page_name'); $nameField->attr('value', $suggested['name']); - $nameField->parentPage = $this->page->parent; + $nameField->parentPage = $page->parent; + /** @var Languages $languages */ $languages = $this->wire('languages'); $useLanguages = $languages; if($useLanguages) { /** @var Field $title */ $title = $this->wire('fields')->get('title'); $titleUseLanguages = $title - && $this->page->template->fieldgroup->hasField($title) - && $title->getInputfield($this->page)->getSetting('useLanguages'); + && $page->template->fieldgroup->hasField($title) + && $title->getInputfield($page)->getSetting('useLanguages'); $nameUseLanguages = $this->wire('modules')->isInstalled('LanguageSupportPageNames'); if($titleUseLanguages) $titleField->useLanguages = true; if($nameUseLanguages) $nameField->useLanguages = true; foreach($languages as $language) { if($language->isDefault()) continue; if($titleUseLanguages) { - $value = $this->page->title->getLanguageValue($language); + /** @var LanguagesPageFieldValue $pageTitle */ + $pageTitle = $page->title; + $value = $pageTitle->getLanguageValue($language); $titleField->set("value$language->id", $value); } if($nameUseLanguages) { - $value = $this->page->get("name$language->id"); - if(strlen($value)) $nameField->set("value$language->id", $value . '-' . $suggested['n']); + $value = $page->get("name$language->id"); + if(strlen($value)) { + if(!empty($suggested["name$language"])) { + $nameLang = $suggested["name$language"]; + } else { + $nameLang = $value . '-' . $suggested['n']; + } + $nameField->set("value$language->id", $nameLang); + } } } } - if($this->page->template->fieldgroup->hasField('title')) $form->add($titleField); + if($page->template->fieldgroup->hasField('title')) $form->add($titleField); $form->add($nameField); + /** @var InputfieldCheckbox $field */ $field = $this->modules->get("InputfieldCheckbox"); $field->attr('name', 'clone_page_unpublished'); $field->attr('value', 1); @@ -242,7 +280,8 @@ class ProcessPageClone extends Process { $field->description = $this->_("If checked, the cloned page will be given an unpublished status so that it can't yet be seen on the front-end of your site."); $form->add($field); - if($this->page->numChildren && $this->user->hasPermission('page-clone-tree', $this->page)) { + if($page->numChildren && $this->user->hasPermission('page-clone-tree', $page)) { + /** @var InputfieldCheckbox $field */ $field = $this->modules->get("InputfieldCheckbox"); $field->attr('name', 'clone_page_tree'); $field->attr('value', 1); @@ -253,14 +292,17 @@ class ProcessPageClone extends Process { $form->add($field); } + /** @var InputfieldSubmit $field */ $field = $this->modules->get("InputfieldSubmit"); $field->attr('name', 'submit_clone'); $form->add($field); - - if(isset($_GET['redirect_page'])) { + + $redirectPageID = (int) $this->wire('input')->get('redirect_page'); + if($redirectPageID) { + /** @var InputfieldHidden $field */ $field = $this->wire('modules')->get('InputfieldHidden'); $field->attr('name', 'redirect_page'); - $field->attr('value', (int) $_GET['redirect_page']); + $field->attr('value', $redirectPageID); $form->add($field); } @@ -269,6 +311,8 @@ class ProcessPageClone extends Process { /** * Render a form asking for information to be used for the new cloned page. + * + * @return string * */ protected function ___render() { @@ -284,18 +328,19 @@ class ProcessPageClone extends Process { $page = clone $this->page; $input = $this->input; - $originalName = $page->name; $this->session->CSRF->validate(); $form = $this->buildForm(); - $form->processInput($this->wire('input')->post); - - if($input->post->clone_page_unpublished) $page->addStatus(Page::statusUnpublished); - $cloneTree = $input->post->clone_page_tree && $this->user->hasPermission('page-clone-tree', $this->page); + $form->processInput($input->post); $titleField = $form->get('clone_page_title'); $nameField = $form->get('clone_page_name'); + $cloneTree = $input->post('clone_page_tree') && $this->user->hasPermission('page-clone-tree', $this->page); + + if($input->post('clone_page_unpublished')) { + $page->addStatus(Page::statusUnpublished); + } if($nameField->useLanguages) { foreach($this->wire('languages') as $language) { @@ -305,38 +350,48 @@ class ProcessPageClone extends Process { $page->set($nameAttr, $value); } } else { - $page->name = $nameField->value; + $page->name = $nameField->attr('value'); } set_time_limit(3600); $clone = $this->pages->clone($page, $page->parent, $cloneTree); - if(!$clone->id) throw new WireException(sprintf($this->_("Unable to clone page %s"), $page->path)); + + if(!$clone->id) { + throw new WireException(sprintf($this->_("Unable to clone page %s"), $page->path)); + } if($titleField->getSetting('useLanguages') && is_object($clone->title)) { foreach($this->wire('languages') as $language) { $valueAttr = $language->isDefault() ? "value" : "value$language->id"; $value = $titleField->get($valueAttr); - $clone->title->setLanguageValue($language, $value); + /** @var LanguagesPageFieldValue $cloneTitle */ + $cloneTitle = $clone->title; + $cloneTitle->setLanguageValue($language, $value); } } else { $clone->title = $titleField->value; } - $clone->save(); + + $this->wire('pages')->save($clone, array('adjustName' => true)); + $this->message(sprintf($this->_('Cloned page "%1$s" to "%2$s"'), $originalName, $clone->name)); $redirectURL = null; - if(isset($_POST['redirect_page'])) { - $redirectPageID = (int) $_POST['redirect_page']; - if($redirectPageID > 0) { - $redirectPage = $this->wire('pages')->get((int) $_POST['redirect_page']); - if($redirectPage->viewable()) $redirectURL = $redirectPage->url; - } else { - $redirectURL = false; + $redirectID = (int) $input->post('redirect_page'); + + if($redirectID) { + $redirectPage = $this->wire('pages')->get($redirectID); + if($redirectPage->viewable()) { + $redirectURL = $redirectPage->url(); } } - if(is_null($redirectURL)) $redirectURL = $this->wire('config')->urls->admin . 'page/list/'; - if($redirectURL) $redirectURL .= "?open=$clone->id"; + + if(!$redirectURL) { + $redirectURL = $this->adminUrl . "page/list/"; + } + + $redirectURL .= "?open=$clone->id"; $this->session->redirect($redirectURL); } @@ -352,35 +407,19 @@ class ProcessPageClone extends Process { * */ public function processAjax(Page $original = null, $returnArray = false) { - - if(is_null($original)) $original = $this->page; - $page = clone $original; - $originalName = $original->name; - $suggested = $this->getSuggestedNameAndTitle($page); - $cloneOptions = array( - 'set' => array( - // keep original $page modified date and user id, since ajax mode doesn't - // give the user the option to edit the page before cloning it - 'modified' => $original->modified, - 'modified_users_id' => $original->modified_users_id, - // pages cloned in ajax are always unpublished - 'status' => $page->status | Page::statusUnpublished, - 'title' => $suggested['title'], - ) - ); - $cloneTree = false; // clone tree mode now allowed in ajax mode $error = null; + if($original === null) $original = $this->page; - try { - $clone = $this->wire('pages')->clone($page, $page->parent, $cloneTree, $cloneOptions); - } catch(\Exception $e) { - $error = $e->getMessage(); + if($this->hasPermission($original)) { + $clone = $this->cloneAjax($original, $error); + } else { + $clone = new NullPage(); } $result = array( 'action' => 'clone', - 'success' => $clone->id > 0 && $clone->id != $page->id && is_null($error), + 'success' => $clone->id > 0 && $clone->id != $original->id && empty($error), 'message' => '', 'page' => $clone->id, ); @@ -401,6 +440,59 @@ class ProcessPageClone extends Process { exit; } + /** + * Perform a clone during ajax request + * + * @param Page $original Page to clone + * @param string $error Variable to populate error message in + * @return Page|NullPage + * + */ + protected function cloneAjax(Page $original, &$error) { + + $page = clone $original; + $suggested = $this->getSuggestedNameAndTitle($page); + + $cloneOptions = array( + 'set' => array( + // keep original $page modified date and user id, since ajax mode doesn't + // give the user the option to edit the page before cloning it + 'modified' => $original->modified, + 'modified_users_id' => $original->modified_users_id, + // pages cloned in ajax are always unpublished + 'status' => $page->status | Page::statusUnpublished, + 'title' => $suggested['title'], + 'name' => $suggested['name'], + ) + ); + + if($this->wire('languages')) { + foreach($this->wire('languages') as $language) { + if($language->isDefault()) continue; + if(!empty($suggested["name$language"])) { + $cloneOptions['set']["name$language"] = $suggested["name$language"]; + } + } + } + + $cloneTree = false; // clone tree mode not allowed in ajax mode + + try { + $clone = $this->wire('pages')->clone($page, $page->parent, $cloneTree, $cloneOptions); + } catch(\Exception $e) { + $error = $e->getMessage(); + $clone = new NullPage(); + } + + return $clone; + } + + /** + * Get Page being cloned + * + * @return null|Page + * + */ public function getPage() { return $this->page; }