Jump to content
Rob

Page draft module - useful to anyone? Please feed back!

Recommended Posts

I've written a module that fulfils a small but hopefully useful need to provide clients with links to "live" but work-in-progress pages so they can be viewed by those that don't have a CMS login.

The basic process is to add a "create draft" link to the page listing (after edit, new, move, delete etc). When clicked, this link creates a clone of the page and adds an MD5 hash of the current time to the URL so that a draft clone with an effectively "un-guessable" URL (name) is created that can be given out to clients to view the page. This clone has a "publish" link added to it in the page listing so this can then be copied over the original published page, and the draft deleted, with one click. The cloned pages also has "(DRAFT)" added to the title.

Does this sound useful to anyone? The only problem is that, because the "draft" page is published it will show up in menu listings and anything else that is utilizing the site structure or API to grab live pages.

If the "published" nature of the cloned pages makes this idea unworkable then I will go back to the drawing board! But if it has potential to be useful then I will at the very least publish the source on github or copy it in here so it can be tampered with by others.

It occurs to me that it wouldn't be hard to filter out these pages with "(DRAFT)" at the end of the title so they don't show up in menu listings etc, but this would require extra effort on the part of the developer in every case where the fetching of pages is occurring.

  • Like 4

Share this post


Link to post
Share on other sites

That sounds interesting - here's a thought: if the URL is going to be "unguessable" anyhow, then why do the draft pages even have to be real, published pages?

It might make more sense to keep them unpublished, so you don't have to "fix it in the mix" by trying to filter them - I suspect that's going to lead down a long road of other side-effects.

Instead, you could set up a controller that takes the unguessable key as an argument - so you get something like "site.com/preview/2442cb34cfc234dbf41cbf3c4", where "preview" is a page that accepts the key as an argument... your controller (or I think the term is "process"?) can then fetch the unpublished preview and render the page.

Does that sound like a working approach?

  • Like 1

Share this post


Link to post
Share on other sites

That sounds plausible. I hadn't thought of having a specific page that handles the display of the drafts. It might well get round the problem of the published/unpublished state of the pages.

I'll give it some thought - thanks!

Share this post


Link to post
Share on other sites
Does this sound useful to anyone? The only problem is that, because the "draft" page is published it will show up in menu listings and anything else that is utilizing the site structure or API to grab live pages.

I think this sounds very useful, nice job with this. The solution to not having your draft pages show up in menu listings/searches is just to make use of the "hidden" status, already built into every page:

$page->addStatus(Page::statusHidden); 
$page->save(); 

Once a Page has hidden status, it won't show up in any multi-page retrieval functions unless you specify "include=hidden" or "include=all" in the selector.

I've stuck the code on github, perhaps someone would care to give this a quick test before I publish it into the module repo here?

Tested out here and seems to work very well! Thanks for your work with this module.

Wouldn't it be great if it would replace the original page once you published/unhid it? (and also deleted the draft). That would bring some great workflow potential... something I can see using all the time actually. But it would have to retain the id and name of the original page, so it would be more like moving the data to the existing page than actually replacing the existing page. If you are interested in doing this, let me know what I can do to help.

Some other suggestions I have:

- Use an integer for your version number. To reproduce the version number you have, you'd just use 10 rather than the string "0.1".

- I think it might be nice to make the PageList label just "draft" rather than "create draft", so it ties in better with the others. Also would be nice to make any of your text translatable like "draft" and "(DRAFT)". You can do this just by doing $this->_('draft'); rather than 'draft'.

- If a page is already a draft, it might be nice if it doesn't have the "create draft" option.

Share this post


Link to post
Share on other sites

Wouldn't it be great if it would replace the original page once you published/unhid it? (and also deleted the draft). That would bring some great workflow potential... something I can see using all the time actually.

This would be a great addition!

Share this post


Link to post
Share on other sites

It should already be replacing the original page when you hit "publish", if it isn't, there's a bug! It was doing that fine in the original code before I made some changes based on Mindplay's suggestions. It now utilises a specific "preview" page to render unpublished pages where before the page just sat with its sibling (the original) and could be viewed like any other. It is essentially publishing a copy rather than overwriting so anything relating to existing page IDs will be corrupted (linked pages etc).

I may revert the code back to it's original form and implement Ryan's "hidden" suggestion as it is a simpler and more elegant solution.

Thanks for all the input, I'll get to work trying to sort all this out.

Ryan - any pointers on overwriting the original page content rather than deleting original? Can I manually manipulate the ID? That way I could just copy but then update the ID to match the original, which seems like the fewest steps to the desired result.

  • Like 1

Share this post


Link to post
Share on other sites

I've started updating the code to make things more simple and elegant but I've run into a problem with Repeater fields.

Basically, I am now creating a cloned page (the draft) and when the user hits "publish draft" the code iterates every field on the draft page and sets the corresponding value in the original, thus copying over the data and retaining the original page ID so it doesn't mess with any linked fields etc. It's attempting a true "update" of the original page rather than a new copy being made and replacing the old.

Unfortunately the following code does not appear to work with repeater fields:

foreach($page->template->fields->getArray() as $field) {
if($field->name != 'title') $originalpage->set($field->name, $page->get($field->name));
}

What this code is trying to do is grab the data from the draft page field and update it into the original page field. This works, but not for repeaters, where the original data stays in place.

Ryan / anyone - any thoughts on what the problem might be? Is this a bug or do I need to tackle repeaters in a specifically different way?

Share this post


Link to post
Share on other sites
Ryan - any pointers on overwriting the original page content rather than deleting original? Can I manually manipulate the ID? That way I could just copy but then update the ID to match the original, which seems like the fewest steps to the desired result.

You should be able to manually manipulate the ID. But there will be some loops to jump through with certain fields that may require manual intervention (page references, file fields, repeaters)… and this is where things get tricky. So I think you are better off copying data back to the original page rather than deleting the original and changing the ID of the clone. There will still be challenges, but I think it's the safer bet (though not really 100% certain).

Regarding the repeater fields, I'm not really sure how to approach this one yet. I would be inclined to skip over repeater fields when it comes to this feature, for the short term. This is a big question and I think we can answer it, but it's going to take some time and research.

Share this post


Link to post
Share on other sites

Can't wait for the final result of this effort... It makes ProcessWire a viable option for many more clients. Thanks for all the hard work and refactoring Rob!

  • Like 1

Share this post


Link to post
Share on other sites

@rob, I echo bcartier's thanks for your work on this module. Looking forward to seeing it added to the PW arsenal.

@bcartier, welcome to the PW forum!

Share this post


Link to post
Share on other sites

Thanks for the kind words gentlemen, but I've been a bit busy (and on holiday in the caribbean) and haven't worked on this for a while. I am definitely going to pursue it, but I'll need a bit of help because the repeater issue has me stumped!

Share this post


Link to post
Share on other sites

Ryan - I'm poking through the core PW code and I have discovered, as yu no doubt are aware, that Repeater fields are not cloneable and the clone() methods simply throws and exception with relevant message.

I have been testing the pages->clone() method today as part of trying to develop this page draft module and when I copy a page within the page listing interface in the CMS, I can successfully copy a page, alter values in repeater fields in the new page copy, and save them without altering the original page. So, as far as I can tell, it is possible to clone repeater fields!

I am also trying to work out what my options are for trying to overwrite existing repeater fields, as mentioned above in this thread, for the purpose of overwriting a page with values from a draft/duplicate. Do you have any more insight into how I might be able to achieve this or if there is any development on the roadmap that will help?

I was also wondering if there is any documentation on the way repeaters work under the hood at the DB level? Or if not, could you try to lay it out for me so I can have a crack at writing some code to clone them? I've been examining the DB but I'm just getting a headache! A nutshell explanation would be really helpful!

I'm determined to get this thing working!

  • Like 2

Share this post


Link to post
Share on other sites

It's possible to clone pages with repeater fields, but not [yet] repeater fields themselves. Meaning, if you need to create a new repeater field definition, then you've got to start from scratch rather than cloning another. But luckily, the need to clone a repeater field is fairly rare.

I am also trying to work out what my options are for trying to overwrite existing repeater fields, as mentioned above in this thread, for the purpose of overwriting a page with values from a draft/duplicate. Do you have any more insight into how I might be able to achieve this or if there is any development on the roadmap that will help?

I don't have a good answer here yet, short of deleting the original and changing the references to the new. I think what we need is some core API method of copying data from one page to another, something like $page->import($page2). I'll put more thinking into this.

I was also wondering if there is any documentation on the way repeaters work under the hood at the DB level? Or if not, could you try to lay it out for me so I can have a crack at writing some code to clone them? I've been examining the DB but I'm just getting a headache! A nutshell explanation would be really helpful!

The best way to understand the way repeaters work under-the-hood is to look at the Repeaters structure under your admin page. Take a look at the page tree there. Repeaters don't consider the DB level, as they are completely built on top relation by page structure. They are using the PW API rather than database queries. Granted, you will see a field_* table for each of your repeater fields, but that is only used for lookups on certain find operations, and not part of the repeater in the way that field_* tables are for other fields. Essentially, you can ignore the field_* table used by your repeater field when it comes to understanding how they work. It all has to do with structure and little else. Repeater items are just pages under a parent. That parent that carries a reference to the page the repeater lives on in its 'name' field. It's a lot less complex than I think most would assume. At the core, repeaters are quite simple. What complexity there is comes more from accommodating specific situations, like ajax file uploads for instance.

Share this post


Link to post
Share on other sites

-- Moved into development --

Please create a separate topic in "modules" for the finished module.

Share this post


Link to post
Share on other sites

I have decided, for the moment, to remove the "publish" funcionality from this module and publish it for general use with the "draft" functionality alone.

90% of the purpose was to enable users to provide safe and private preview links to clients so they can see changes and sign them off. As it stands, changes targetted at live, published pages will have to be repeated on the "live" page but untiI I can figure out how to correctly clone and replace entire pages including repeaters, this version will suffice.

I'll add this to the official modules section shortly.

  • Like 1

Share this post


Link to post
Share on other sites

Thanks for making this module and adding it to the directory. 

Share this post


Link to post
Share on other sites

Having some confusion over the purpose/config of the 'permission' array item returned from getModuleInfo().

I can't find anything in the forums or the docs about how this works.  If  Iserach the PW codebase there are many references to various 'page-XYZ' permissions but I'm not really sure how to use them.  My module creates clones of pages, so do I need to explicitly specify some permission for this to be possible?

Obvioulsy it wouldn't be good to have people who aren't supposed to create/edit pages being able to create clones and then publish them.

Share this post


Link to post
Share on other sites

If you don't specify a permission, then your module is available to all admin users. If you specify an existing permission, like 'page-delete', then your module's execute() method(s) will only be available to users that have that permission. You can also have your module install a new permission, and then set it as the required permission. Example: 

$p = wire('permissions')->add('page-draft-control'); 
$p->title = 'Ability to create draft pges for edit/preview';
$p->save(); 

I think that the above is probably not exactly what you are looking for. In your case, you are using a Process module as an 'autoload' module, so your hooks are getting attached regardless of any specified permission (since that permission only applies to 'execute' methods). What you should do is check that the actual $page is editable() before performing runtime actions: 

public function hookPageListActions(HookEvent $event) {
  $page = $event->arguments[0];
  if(!$page->editable()) return; 
  ...
} 
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"); 
  ...
}

Share this post


Link to post
Share on other sites

Thanks Ryan, that's all useful stuff moving forward.

However what I am still unclear on is where I can find the list of existing permissions, be they created from the core PW code or other people's modules.

That is to say, the list of permissions that come from the PW core and core modules is 'page-edit'.....and what else? Is there a list or a reference?

Share this post


Link to post
Share on other sites
However what I am still unclear on is where I can find the list of existing permissions, be they created from the core PW code or other people's modules.

In your admin, click "Access" and then "Permissions". That shows you all the permissions currently in there. When editing a role, you can check the boxes for the permissions allowed for each role. 

Share this post


Link to post
Share on other sites

Hi Rob

are you still working with processwire as it is a while since the last post:

We tried to update your modules but they fail in 2.5.5
 

  • Page Edit Fold Status / PageEditFoldStatus +1
    Uses session to remember the open/close status of each field when page editing. By rob

  • Page Draft Creator / ProcessPageDraft
    Adds a link that allows for one-click creation of hidden, "draft" version of a page with an un-guessable URL. Changes can be safely shown to clients before being made to the "live" page. By rob


    Could you please check as after an update still the option to update the module gets called!

    Thanks Andi

     

Share this post


Link to post
Share on other sites

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 account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...