theo Posted January 21, 2018 Share Posted January 21, 2018 On this cold and rainy weekend in Switzerland, I was trying to learn something about Processwire modules. "Page Draft Control" seemed an ideal starting point, because it has not so many lines of code and looks rather abandoned. While trying to understand it, I've added some code. For me the result looks quite usable, but I am very open for any suggestions, about what could be done better theoretically. I'm not going to further develop this, because there is ProDrafts if you need something perfect. Processwire modules and "ecosystem" are really great. Thank you. Spoiler <?php namespace ProcessWire; class ProcessPageDraft extends Process implements Module { const IDENTIFIER = '__draft__'; public static function getModuleInfo() { return array( 'title' => 'Page Draft Control', 'version' => '0.2.0', 'summary' => 'Adds ability to create draft pages (unpublished copies) for edit/preview and then publish them (switch them to live)', 'permission' => 'page-delete', 'singular' => true, 'autoload' => true, 'icon' => 'eye' ); } public function ready() { $process = $this->wire->page->process; if ($process == 'ProcessPageList') { $this->addHookAfter("ProcessPageListRender::getPageActions", $this, 'hookPageListActions'); $this->addHookAfter("ProcessPageListRender::getPageLabel", $this, 'hookPageLabel'); } } public function hookPageLabel(HookEvent $event) { $page = $event->arguments(0); $isDraft = (strpos($page->name, self::IDENTIFIER) !== false); if ($isDraft) { $icn = $page->template->getIcon(); $event->return = '<i class="icon fa fa-fw fa-' . ($icn ? $icn : 'eye') . '"></i><span class="label_title"><i>' . $page->title . ' (draft)</i></span>'; } } public function hookPageListActions(HookEvent $event) { $page = $event->arguments[0]; if (!$page->editable()) return; if ($page->hasStatus(Page::statusSystemID)) return; if ($page->hasStatus(Page::statusTrash)) return; if (($page->template->name === 'admin') || ($page->id < 2)) return; $actions = $event->return; $isDraft = (strpos($page->name, self::IDENTIFIER) !== false); $origpage = $this->getOriginalPage($page); if ($origpage) { if ($origpage->hasStatus(Page::statusTrash)) return; } if (!$isDraft) { $actions[] = array( 'cn' => 'draftcreate', 'name' => $this->_('Create Draft'), 'url' => $this->config->urls->admin . "page/draft/create/?id={$page->id}" ); } else if ($origpage) { $actions[] = array( 'cn' => 'draftpublish', 'name' => $this->_('Publish Draft'), 'url' => $this->config->urls->admin . "page/draft/publish/?id={$page->id}" ); if ($origpage->numChildren()>0) $actions[] = array( 'cn' => 'draftchildren', 'name' => $this->_('Take Over Children'), 'url' => $this->config->urls->admin . "page/draft/children/?id={$page->id}" ); } $event->return = $actions; } public function executeChildren() { $page = $this->pages->get((int) $this->input->get->id); $origpage = $this->getOriginalPage($page); if ($origpage->numChildren()>0) { $this->takeOverChildren($origpage, $page); } $this->session->redirect($this->config->urls->admin . 'page/list/?open=' . $page->id); } public function executeCreate() { $page = $this->pages->get((int) $this->input->get->id); if (!$page->id) throw new WireException("Page doesn't exist"); if (!$page->editable()) throw new WirePermissionException("You don't have access to edit that page"); $nameadd = self::IDENTIFIER . $page->id . '__' . md5(time()); $clone = $this->wire->pages->clone($page, null, false); if ($this->wire->languages) { foreach ($this->wire->languages as $lang) { if ($lang->name == 'default') $lang = ''; $langname = $page->get("name$lang"); if ($langname) $clone->set("name$lang", $langname . $nameadd); } } else $clone->name = $page->name . $nameadd; $clone->addStatus(Page::statusHidden); $clone->removeStatus(Page::statusUnpublished); $clone->save(); $this->session->redirect($this->config->urls->admin . 'page/list/?open=' . $clone->id); } protected function takeOverChildren($origpage, $page) { if ($origpage->numChildren()>0) { foreach ($origpage->children as $child) { $child->of(false); $child->parent = $page; $child->save(); $child->of(true); } } } protected function getOriginalPage($page) { $identlen = strlen(self::IDENTIFIER); $pos = strpos($page->name, self::IDENTIFIER); if ($pos !== false) { $pos2 = strpos($page->name, '__', ($pos + $identlen)); $origid = substr($page->name, $pos + $identlen, $pos2 - $pos - $identlen); $origpage = $this->pages->get((int) $origid); if ($origpage->id !== 0) { return $origpage; } } } public function executePublish() { $page = $this->pages->get((int) $this->input->get->id); $origpage = $this->getOriginalPage($page); if ($origpage) { if ($page->parent->id === $origpage->parent->id) { if (!($origpage->isHidden())) $page->removeStatus(Page::statusHidden); if (!($origpage->isUnpublished())) $page->removeStatus(Page::statusUnpublished); if ($this->wire->languages) { foreach ($this->wire->languages as $lang) { if ($lang->name == 'default') $lang = ''; $langname = $origpage->get("name$lang"); if ($langname) $page->set("name$lang", $langname); } } else $page->name = $origpage->name; $this->takeOverChildren($origpage, $page); $this->pages->trash($origpage); $page->save(); } else $this->message("Publishing failed. Page ID:{$origpage->id} changed level."); } else $this->message("Publishing failed. Original page does not exist."); $this->session->redirect($this->config->urls->admin . 'page/list/?open=' . $page->id); } public function ___install() { if (ProcessWire::versionMajor == 2 && ProcessWire::versionMinor < 1) { throw new WireException("This module requires ProcessWire 2.1 or newer"); } $page = $this->getInstalledPage(); $this->message("Installed to {$page->path}"); } public function ___uninstall() { $page = $this->getInstalledPage(); if ($page->id !== 0) { $this->message("Removed {$page->path}"); $this->pages->delete($page); } } protected function getInstalledPage() { $parent = $this->pages->get("name=page,parent=" . $this->config->adminRootPageID); $page = $parent->child("name=draft,include=hidden"); if ($page->id === 0) { $page = new Page(); $page->parent = $parent; $page->template = $this->templates->get('admin'); $page->name = "draft"; $page->title = "Draft Page"; $page->process = $this; $page->sort = $parent->numChildren; $page->addStatus(Page::statusHidden); $page->save(); } return $page; } } 3 Link to comment Share on other sites More sharing options...
Robin S Posted January 22, 2018 Share Posted January 22, 2018 7 hours ago, theo said: While trying to understand it, I've added some code. Nice one! 7 hours ago, theo said: For me the result looks quite usable, but I am very open for any suggestions, about what could be done better theoretically. I think the fact that the ID of the original page is not retained when the draft replaces it is a bit of a problem. Because any pages that have a reference to the original page in a Page Reference field will lose that when the draft is published and the original is trashed. This issue is discussed earlier in the thread, and Ryan's suggestion was to copy the content of the draft page to the original page when the draft is ready to go live. Maybe you could look at using that approach? 2 Link to comment Share on other sites More sharing options...
theo Posted January 22, 2018 Share Posted January 22, 2018 9 hours ago, Robin S said: I think the fact that the ID of the original page is not retained when the draft replaces it is a bit of a problem. Because any pages that have a reference to the original page in a Page Reference field will lose that when the draft is published and the original is trashed. Yes, it is true. But this will get too complicated. It's just an exercise for me. Take ProDrafts if you need to cross reference elements by id. It will work in simpler scenarios it think. It is up to the "admin" to decide. At least the original page will remain untouched this way. You can even restore it from trash as if nothing happened. Thank you. 1 Link to comment Share on other sites More sharing options...
szabesz Posted January 22, 2018 Share Posted January 22, 2018 To tell the truth I have not yet used @Robin S's ConnectPageFields module, but maybe he could come up with a solution based on the inner workings of his module to take further this Page Draft module. I'm just wild guessing here and probably daydreaming however, it would be great to see a free alternative to ProDrafts as a draft feature would be welcome, but I do not have a project which needs all the extra features of ProDrafts, I could do with just draft+published states for the "body" field, keeping the already published state intact until it is overwritten with the draft version which can be saved too, independent of the published one. Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now