Jump to content

Release: SchedulePages


formmailer

Recommended Posts

Hi,

I just uploaded the first version of the SchedulePages module to Github:

https://github.com/f...r/SchedulePages

This plugin module allows you to schedule (un)publication of pages.

I hope you guys will like it.

//Jasper

Usage:

======

  • Place the SchedulePages.module file into the site/modules folder and install the plugin from the admin area.
  • Install/activate the LazyCron module (if you haven't already). This module is part of the Processwire core, but it isn't activated by default.
  • Add the following date fields to your template:
    publish_from
    publish_until

Note: the fields are already created during the installation of the module

  • That't all. LazyCron will run take care of (un)publishing your pages that have a the publish dates set. It will run on every hour.
    Please note: LazyCron hooks are only executed during pageviews that are delivered by ProcessWire. They are not executed when using ProcessWire's API from other scripts.

Edit: Changed the name of the module and function as Ryan suggested below.

Edit 2: Updated instructions. Required fields are now automatically created and from now it runs every hour.

Edit 3: Added module dependency. The updated module is on Github

  • Like 8
Link to comment
Share on other sites

Great stuff formmailer, this was quick! Will definitely test this and probably start using too!

EDIT: I took quick look to code. It seems that you hook into LazyCron::everyDay. This means it checks status only on daily basis. I think that is fine for most cases, but I think it would be good to have possibility for tighter schedule. I know our clients schedule many time on hourly basis (ie. they write press release at the morning and want it to be released 14:00).

Link to comment
Share on other sites

Formmailer this looks great, and incredibly useful! This is definitely one of those things that clients ask for and I have a feeling your module is going to get a lot of use. :)

I agree with Antti that a great upgrade would be to have a little finer control (maybe down to the hour?) whether in this version or some future version.

Another nice upgrade would be having your module install/uninstall the publish_from and publish_until fields automatically. I'll be happy to show you how to do this if you'd like?

---

Edit: also wanted to add: you might want to just call the module 'SchedulePosts' rather than ProcessSchedulePosts. That's because 'Process' is meant to refer to modules that extend the 'Process' module type (i.e. interactive admin modules). Also, 'Posts' is not a term that's used in ProcessWire... nothing wrong with using it, but maybe 'Pages' or something would be better? :) Like 'SchedulePages' or something like that

Link to comment
Share on other sites

I agree with Antti that a great upgrade would be to have a little finer control (maybe down to the hour?) whether in this version or some future version.

I actually thought about this, but since I only used a date field in this release, it didn't seem necesary to run more than once a day. But I will add (half)hour support as well.

Just for those who don't like LazyCron, it's also possible to run this plugin from a specific template, with the following:

<?php 
$SchedulePages = $modules->get("SchedulePages");
$SchedulePages->RunSchedulePages();

This would allow you to set-up a real cron job using wget or curl, just to activate the scheduling function. But just now this doesn't add antything since the date fields are set to midnight.

As I can see the current date picker doesn't support times, or does it? This would almost be a requirement for adding time support to this module.

Another nice upgrade would be having your module install/uninstall the publish_from and publish_until fields automatically. I'll be happy to show you how to do this if you'd like?

That would be great! I tried to find out how to do that, but gave up after a while.

And while you are at it: would it be possible to create a FieldsetTab (called Schedule) as well and add this with a single click (or drag-n-drop) to a template? It would be great if these scheduled dates alway would appear in their own Schedule tab without having a user creating the FielssetTab field and add the date fields in it.

Edit: also wanted to add: you might want to just call the module 'SchedulePosts' rather than ProcessSchedulePosts. That's because 'Process' is meant to refer to modules that extend the 'Process' module type (i.e. interactive admin modules). Also, 'Posts' is not a term that's used in ProcessWire... nothing wrong with using it, but maybe 'Pages' or something would be better? :) Like 'SchedulePages' or something like that

Good points. I changed the name, both for the module and the function.

/Jasper

Link to comment
Share on other sites

Here's how to create a new field from your install function:

<?php

public function ___install() {
    $field = new Field();
    $field->type = $this->modules->get("FieldtypeDatetime");
    $field->name = 'publish_from';
    $field->label = 'Publish From Date';
    $field->dateOutputFormat = wire('config')->dateFormat;
    $field->dateInputFormat = wire('config')->dateFormat;
    $field->datepicker = 1; // if you want datepicker enabled
    $field->defaultToday = 1; // if you want dates to default to today
    $field->save();
    // repeat for publish_until field
}

public function ___uninstall() {
    // only do the following if you want to uninstall the fields that were installed
    // this may be one thing that's safe to leave to the user
    $field = wire('fields')->get('publish_from'); 
    if($field && $field->numFieldgroups() > 0) 
        throw new WireException("Can't uninstall because field publish_from is still being used. Please remove it from any templates."); 
    wire('fields')->delete($field); 
    // repeat for publish_until field
}

Creating tabs is a little different because not only will you have to create the field, but you'll have to add it to fieldgroups. And I'm not sure how you determine what fieldgroups it should be added to. I think this is almost something that is better to leave to the user... or handle on the module's config settings, where they can check boxes next to the templates they want to have scheduled pages. If you are interested, I can help with that next week – my wife gives me crap about being on the computer on the weekend. :)

Link to comment
Share on other sites

Thanks Ryan, I didn't know that creating the new fields was so easy.  :)

I leave uninstalling the fields to the user, like you suggested.

I had the following idea regarding the schedule tab. It would be very nice if the plugin could add a "Do you want to enable scheduled publishing for this template-checkbox" on the Edit template page.

When this checkbox is checked a tab called "Schedule" should be created and it should contain the publish_from and publish_until fields.

Would it be possible to do something like this? I think this would be more user friendly than selecting templates in the module config.

my wife gives me crap about being on the computer on the weekend. :)

I know how that feels. :)

/Jasper

Link to comment
Share on other sites

  • 5 weeks later...

Ryan,

...or handle on the module's config settings, where they can check boxes next to the templates they want to have scheduled pages. If you are interested, I can help with that next week – my wife gives me crap about being on the computer on the weekend. 

Do you have time to help me with this or my idea above (if that's possible to do it like that).

/Jasper

Link to comment
Share on other sites

Sorry, I forgot about this earlier. Here's how to add such a configuration option. First off, tell ProcessWire that your module will be configurable by adding 'implements ConfigurableModule', like this:

class SchedulePages extends WireData implements Module, ConfigurableModule {

Next, determine the name of the variable you will use to hold the template IDs: I'll use $scheduleTemplateIDs. Set that in your __construct() to initialize it as a known variable in your module:

<?php
public function __construct() {
    $this->set('scheduleTemplateIDs', array()); 
} 

Now you need to implement the ConfigurableModule interface by adding a static function called getModuleConfigInputfields. It is static so that ProcessWire's Modules section can call upon it without actually initializing the module. Or to put it another way, it ensures that if you have CSS or JS files being loaded in your __construct() or init() then they won't be altering the display of the module configuration screen. :)

This function is given an array of the current configuration data saved with your module. If it's the first time your module is configured, it will very likely be empty.

<?php
static public function getModuleConfigInputfields(array $data) {

    // create a wrapper to hold our field(s)
    $form = new InputfieldWrapper(); 

    // set our default if this config var isn't already present
    if(!isset($data['scheduleTemplateIDs'])) $data['scheduleTemplateIDs'] = array(); 

    // create a field to select templates
    $f = wire('modules')->get('InputfieldCheckboxes');
    $f->label = 'Select the templates you want';
    $f->description = 'Additional description if you want it';
    $f->notes = 'Some optional extra notes at the bottom'; 
    $f->attr('name', 'scheduleTemplateIDs'); 

    // add the checkbox options
    foreach(wire('templates') as $template) {
        $f->addOption($template->id, $template->name); 
    }

    // set the current value
    $f->attr('value', $data['scheduleTemplateIDs']); 

    // add it to the form
    $form->add($f); 

    return $form; 
}

Now when the user clicks on your module in Admin > Modules, they will see checkboxes they can use. After they save, PW will now know to automatically send a $scheduleTemplateIDs var to your module every time it's loaded. That variable will be set before your module's init() is called. So you could do something like this in your init() or anywhere in your module:

<?php
$page = wire('page'); 
if(in_array($page->template->id, $this->scheduleTemplateIDs)) {
    // do something
} 
Link to comment
Share on other sites

  • 2 months later...

Jasper, I just send you a pull request for this module. I added few minor features:

  • Checks publish dates right after saving, and keeps page unpublished if required.
  • Made module configurable to choose cron interval
  • Simplified the code little bit (took 2 unnecessary foreach loops out).

I had earlier done this kind of stuff on selector level, but this is much nicer - shows scheduled pages as unpublished on admin pagetree also.

  • Like 2
Link to comment
Share on other sites

Thanks Antti! I just merged the request.

Good idea to check for the dates when saving. This eliminates the need of the 2 foreach loops. These were there to ensure that pages without a published until date would be published. But it's better to require a date. If the page needs to be there forever you someone can use a date in 2062 or something like that.

I like the configurable part for the cron interval. Thanks for adding this.

/Jasper

Link to comment
Share on other sites

Thanks Jasper. It was nice coding, since you had clean and well commented code.

About removing the foreach: I don't think I changed any functionality (at least not on purpose). There is still 2 foreach loops, but there were 4 before my commit. You had foreach where you removed certain items from PageArray, and after that another foreach where you published/unpublished those pages. Don't know if that has any real performance advantage, but it makes code shorter.

On page save I only check if:

a) You have publish_from > publish_until => don't publish, dates don't make any sense

b) publish_from > current time => don't publish, even if I try to publish

c) publish_until < current time => don't publish, even if I try to publish

  • Like 1
Link to comment
Share on other sites

If the page needs to be there forever you someone can use a date in 2062 or something like that.

That was bug on my side, I didn't check that publish_until actually has a value. Fixed and send new pull request for you.

Link to comment
Share on other sites

Hmm.. Another lang issue, now with Shop module. I am sure it did worked before and on the same site. Only thing I can think of is that now I have module folder as symlink, and module files in totally different folder (in both of these, Schedulepages and Shop). Could this cause the issue? Translator finds the files ok and lets translate without problems.

Link to comment
Share on other sites

You are right about this, because the module's path+file forms it's textdomain, and that's the primary key for PW to connect the file with the translations. I'm just wondering if I can somehow update the translation functions to use the location of the symlink rather than the path it point to. PW finds the textdomain by using the __FILE__ magic constant, and this returns the actual path of the file. I don't know if there is a way to get PHP to recognize the path of the symlink, at least not in a similar manner?

Link to comment
Share on other sites

  • 2 months later...
  • 1 month later...

hello everybody,

I realized that this nice module can even be used with repeater to schedule not a whole page but just an element on a page since by means of repeater this element really is a page anyway.

but there is a hindrance to this idea. it's works nicely if I run the function RunSchedulePages by "Include & Bootstrap".

but it does not succeed if the function gets called by LazyCron. I guess the reason is, that "find" yields no hits, cause it is an admin page. If I am right, this may be a concern in other cases too.

So I ask you experts for help.

Link to comment
Share on other sites

I haven't had time to test with repeatable elements yet, so I am not sure how this works. Maybe someone else has a suggestion and if a change to the module is required, I'll be more than happy to update the module.

/Jasper

Link to comment
Share on other sites

MarionRaven I think you are right about the reason why it's not working, because of the find(). Though not positive why it would work in the include & bootstrap context you mentioned. But I think it may be as simple as updating the two find() calls in RunSchedulePages() to have an "include=all" added to their selectors, i.e.

$unpublished = wire("pages")->find("status=unpublished, publish_from<$time, include=all");
$published = wire("pages")->find("status=published, publish_until>0, include=all");

If you test it out, let us know. My only concern would be that "include=all" may override the "status=", but hopefully not. If it does, try instead to add "check_access=0" rather than the "include=all".

Link to comment
Share on other sites

formmailer, yes I got it to work.

ryan, I already found a hint from apeisa in another threat concerning include=all.

I ended up with

$published = wire("pages")->find("include=all, status=published");

foreach($published as $p) {

The conditions regarding publish_from and publish_until are not part of the find. They are handled in the foreach loop.

That solved my issue.

Thanks both of you.

  • Like 2
Link to comment
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
×
×
  • Create New...