Rules with Node Expire

We encourage users to post events happening in the community to the community events group on https://www.drupal.org.
joemaine's picture

Rules is one of the best modules - thanks for all of the work!

Having problems with the following scenario:

Create content_type with a default (not editable by users) node expire date of now +60 days
...easy to do with node-expire

When content_type is updated; reset node expire date to now +60 days
...need help

When content_type is published or republished; reset node expire date to now +60 days
...need help

When content-type reaches expire date; unpublish content_type
...easy to do with node-expire

I assume that PHP code is needed, but can't seem to make it work... Is node expire the best way to accomplish this? I can't wrap my mind around rule sets...

Comments

You can do that all just by

fago's picture

You can do that all just by using the rules scheduler and an appropriate rule set for expiring the node.

Same issues

zapscribbles's picture

Hi

I am basically having the same issues and need some help. I am not unpublishing my nodes, but I do need the expiration date to be reset every time the node is updated. I have tried using rules scheduler/rule sets but cannot find any action like "Reset expiry date"

Did you work this out joemaine? Or do you have any further detail fago?

Thanks,
Stephen

on hold

joemaine's picture

Hi Stephen,

I'm still on hold with this issue.

Figured it out

zapscribbles's picture

Hi joemaine

I figured out how to do this using a combination of triggered rules, rule sets and the rule scheduler. The key was figuring out how to manipulate the data using PHP code.

Let me know if you still want to need this sorted and you want all the details

Regards,
Stephen

Please!

joemaine's picture

Hi Stephen,

That information would be greatly appreciated!

Hi, Stephen. I'd definitely

toddwoof's picture

Hi, Stephen.

I'd definitely like to see the details. I can do a simple "unpublish the expired nodes" rule with Node Expire, but resetting the expiration date on node edit would be really useful.

In my case, what I want is for a node to expire 60 days from the time the user posts or edits it. So if they just post, it unpublishes in 60 days (which is easy enough), but also they can go back and re-post it later on if they want, and it will stay published for another 60 days (which is the problem).

I wanted to use the default maximum expiration date field for that, but since it's based on the post date, that won't work. For example, if a node is published on day 1 and the maximum expiration is set to +60 days, then the user can't re-publish the node after 60 days, because you can't set the expiration date more than 60 days from the original post date.

If I could just have the expiration date set to today+60 each time the user edits the node, that would do the trick.

A Solution

zapscribbles's picture

Hey Guys,

It took me a while to figure it out, but once I did I can't believe how simple the code actually is.

In the Rule you create that gets triggered when the user edits the node (I used the "Content is going to be saved" event), add an "Execute custom PHP code" action. Then enter the following code (be sure to leave out the php delimiters though):

<?php
$node
->expire = date('Y-m-d',strtotime('+60 days'));
return array(
"node" => $node);
?>

The first line sets the node expiry date to +60 days from today, and makes sure it is in a format Node Expiry understands (Y-m-d = YYYY-MM-DD). The second line just applies these variable changes so that they are applied as if someone had edited the expiry date field manually.

I have tested this and it works perfectly. However, I am currently testing the rest of the process (Scheduling rule sets etc.) and I think there may be holes in my logic. I have it set up like this:

Triggered Rule A (described above) was created to unexpire the node and reset its expiry date.
Triggered Rule B is set off whenever a content is expired. When this rule is triggered it runs a Rule Set I created.
This Rule Set sends an email to the author telling them to review the content. It also schedules itself to be run again in +14 days time.

If the user edits the node within 14 days of receiving this e-mail, Triggered Rule A is set off and the node is unexpired again. If this has happened, when the Rule Set runs after it's 14-day wait it won't do anything as it only runs if the content is still expired.

On the other hand, if the user hasn't edited the content within the 14-day period, the Rule Set does run again, resending an e-mail and rescheduling it to run again in another 14 days time.

Assuming I haven't lost you (as even I am slightly confused typing this out!), my issue is that I am not unpublishing or republishing the node. This is because a) I need users to still be able to see the content even if it is expired, and b) I want to avoid giving my users access to unpublished nodes for various reasons. Because of this, Triggered Rule A doesn't just run the first time the node is expired, but every cron run while it is still expired! I am currently thinking up a way around this, potentially adding a CCK boolean field to the nodes and turning that on and off to replace publishing/unpublishing.

Anyway, hope this all helps. If you have any ideas that will help with my triggering dilemma let me know, otherwise, given enough time, I will eventually work it - I hope :-)

Regards,
Stephen

exactly what I needed

dhcp's picture

That idea and little snippet to extend the expiration date is EXACTLY what I needed. Thanks for posting that!

We'll give it a run

joemaine's picture

It's worth a try; we'll line it up for next week. In our situation, permissioned users (content authors) can publish/unpublish the specific content type with the Publish Content Module and can also access unpublished content through a view. Our concern was with the PHP code to reset the expiry date.

So...if we can send an email to the author when the node is unpublished and they return and publish the node and save it then the expiry will be reset to today +60 (does that make any sense?)

We'll post again if it works!

Thanks!

Partial solution

agileadam's picture

Update: This code doesn't work as expected... my testing procedures weren't catching a problem

Maybe this will help? Use this as a rules action to auto set an expiration date if there is an expired date on or before today, or if the node is flagged as "expired".

Enable PHP Filters in your modules page, and add this code to a new "Execute custom PHP code" action. As Stephen said above, don't include the "<?php ?>" tags.

I added this on a "Content is going to be saved" trigger with a conditional for the content type. It runs when creating, editing, publishing, unpublishing (and more?) a node of type "job_listing." Not quite what you wanted, but it should get you started.

Lastly, note that when creating/editing a node that has node_expire, you cannot give it an expiration date in the past. So, while this will help you out when publishing or unpublishing a node without opening it (via Publish Content for example), your users will still get an error if they open (edit) an expired node and try to save without fixing the expiration date. I believe, though, that you could automate/fix this using hook_form_alter and hook_nodeapi.

<?php
$daysUntilExpire
= 60;
$today = time();
$expireDate = strtotime($node->expire);

/**
* If the node expiration date is earlier than today, or the node is already expired
* set the node expiration date to "today + XXX days", and change expired to false
**/
if($expireDate <= $today || $node->expired){
   
$node->expire = date('Y-m-d',strtotime('+' . $daysUntilExpire . ' days'));
   
$node->expired = 0;
   
drupal_set_message("Automatically set an expiration date " . $daysUntilExpire . " days from today. (" . $node->expire . ")");
    return array(
"node" => $node);
}
?>

By the way... there seemed to be a problem with node_expire in my install. Please check this:
http://drupal.org/node/426636#comment-1651286.

The logic works, but when

agileadam's picture

The logic works, but when publishing the node, $node->expire isn't available or isn't being read properly.

Whoops

agileadam's picture

My testing procedure wasn't catching a major problem:

The strtotime() function was turning a non-existent $node-expire into a valid-looking date. I overlooked the fact that the date was not correct. Timestamps! arg.

Sorry!

Solution for auto-setting expiration

agileadam's picture

This setup does the following:
1) Uses a CCK date field (with handy popup calendar)
2) Auto-sets expiration date if field is empty (only happens when node is published)
3) Auto-sets expiration date if existing expiration date is older than today (only happens when node is published)
4) Provides nice (imo) messages

This will hopefully give you another push in the right direction.

I've attached a screenshot of how to set up the cck field (right click and view image to see full size). I've also attached an export of the rule that I created.

The next thing you'd have to do is make a node unpublish based on a rule that looks at the expiration date field and compares it to today... and schedule this rule to run daily.

Also, because I worked with this code as a new module before making it into a rule, I'll paste (below) the nodeapi function for the module (which is named auto_node_expire; no dependencies).

<?php
function auto_node_expire_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {

   
//how many days ahead to set the expiration date
   
$daysUntilExpire = 60;

   
//today as a timestamp
   
$today = time();

    switch (
$op) {
        case
'presave':
           
//only deal with the expiration date if the node is published
           
if($node->status && $node->type == "job_posting"){
                if(
$node->field_expiration_date[0][value]){
                   
//date is set
                   
$expireDate = $node->field_expiration_date[0][value];

                   
//check if date is in the past; if true, set to XX days ahead
                   
if(strtotime($expireDate) <= $today){
                       
$newExpDate = date('Y-m-d',strtotime('+' . $daysUntilExpire . ' days'));
                       
$node->field_expiration_date[0][value] = $newExpDate;
                       
drupal_set_message("Expired already. Automatically resetting an expiration date "
                                           
. $daysUntilExpire . " days from today. (" . $newExpDate . ")");
                    }
                }else{
                   
//no date set, set to XX days ahead
                   
$newExpDate = date('Y-m-d',strtotime('+' . $daysUntilExpire . ' days'));
                   
$node->field_expiration_date[0][value] = $newExpDate;
                   
drupal_set_message("No expiration date specified. Automatically set an expiration date "
                                       
. $daysUntilExpire . " days from today. (" . $newExpDate . ")");
                }
            }

            break;

        case
'load':
        case
'insert':
        case
'update':
        case
'view':
    }
}
?>

I've mostly got this figured out, but not quite...

toddwoof's picture

After reading Stephen's notes and keystr0k's suggestions, I'm 95% of the way there on this, but need help with Rule sets or cron-based Rules.

I think this is a support request (getting tokens to work with cron-based Rules), so I posted notes in the Rules queue, here: http://drupal.org/node/508224

Did you find a solution?

agileadam's picture

Did you find a solution?
Just curious. I've been very busy and haven't had time to tinker.

Did you find a solution?

agileadam's picture

Did you find a solution?
Just curious. I've been very busy and haven't had time to tinker.

No solution yet

toddwoof's picture

No complete solution yet. (No takers on the post here or in Rules queue, yet.) I think we'll have to run with the plan to use an "about to view" rule to unpublish nodes based on the expiration date. Not very elegant, but it will have to do.

Anyone out there know how to use Rule Sets? I think that might be the real solution, but I can't figure it out.

A Rule Set is a set of rules

aeronox's picture

A Rule Set is a set of rules without a trigger. They are passed arguments (Node1, Node2, Comment, User) and then they do something with those arguments.

My route was to have 2 rule sets:

Rule Set: Unpublish node
- Rule: Unpublish the node
- - Action: Unpublish content

Rule Set: "Check node expiry"
- Rule: "Expired; unpublish"
- - Condition: Numeric Comparison - If the node's CCK_time_field is less than (or equal to) "now"
- - Action: Run Rule Set "Unpublish node"
- Rule: "Not expired; reschedule check"
- - Condition: Numeric Comparison - If the node's CCK_time_field is greater than "now"
- - Action: Schedule Rule Set "Check node expiry" to be run at time of "CCK_time_field"

And 1 triggered rule:

Triggered Rule: "New node"
Triggers on: Saving new content
- Action: Schedule Rule Set "Check node expiry" to be run at time "CCK_time_field"

This works in my situation. It loops until the node is expired. I have made mine more complex by re-publishing after OG node submission, node edit increasing the CCK_time_field. What this doesn't allow is changing the CCK_time_field value to an earlier time; it will remain scheduled at the later time. For me that wasn't an issue since that field value will only ever increase, and is handled by the system, not users. The ability to un-schedule scheduled tasks is being worked on by klausi and fago, and would come in handy here.

Aeronox, Can Rule Sets be

nyleve101's picture

[edit] i get it now.

Thanks!

Blind leading the blind

I_am_trying_to_understand's picture

toddwoof

To build upon what aeronox wrote, but hopefully not condescending to you, as you seem to be further on than than I, here is another explanation.



A simple Rule = Condition + Action
Or a Rule = If this is true + Then start doing this.

The Then part can be a Singular 'do this' or a Set of If/Then Rules (hence 'Rule Set').

Therefore a Rule Set = Set of Conditions + their Actions.

Some Rule Sets on a site may be the same applied to many Rules, therefore having a Rule Set or Sets that you can select when creating a new Rule saves you the time and hassle of setting it up per Rule.


So how do you do it?



(Best to ask aantonop http://drupal.org/node/430086 as he seems to know his stuff. The introductory video that Fago presented last year only glosses over Rule Sets at 11mins http://www.archive.org/details/aug_27_4_everything )

But I will have a go.

Rule Sets are created at /admin/rules/rule_sets/add

I am picking you know this bit from creating a rule but anyway: Give the Rule Set a name that will help you identify exactly what it does, and a machine readable name (usually the same).

Then for the arguments (think 'key group that this set applies to'), give this a name according to what the rules you are putting in the set are going to do, and choose the general group of rules that it will belong to, again giving it a personalised machine readable name.

I think the mental block (that at least I have) comes when you see only a limited listing of 'data types' in the drop down box for each 'argument'. You have to know which group your 'argument' or 'key group' belongs to. Whichever group your argument is chosen to belong to determines whether is shows in the drop-down selection box for the 'event' when creating a new rule. [A chart would be good here.] Save.


So to clarify - a Rule has a Condition (If) and an Action (Then). An Action can be a single action or be further qualified by If/Then Rules. These If/Then rules can often be grouped into a Rule Set.




The next screen defines each of the (sub) Rules in your Rule Set.

These will be familiar to you as they are Rules in themselves. [ Add as many rules to your Rule Set as you need, by clicking on the 'Add New Rule' tab.]

Rule Sets then appear in your Action drop-down when creating a new rule.

[Note: Is it possible to have more than 2 rules or 1 argument in a set - but the deeper you 'thread' the Rule the more complex it is!!!]






[Please 'critics' I am learning this as I type it trying to help someone else hence the subject title - kind correction though is helpful to us all.]

having a Rule Set or Sets

aeronox's picture

having a Rule Set or Sets that you can select when creating a new Rule saves you the time and hassle of setting it up per Rule.

This is the power of Rule Sets. In my example above, I had a Rule Set named "Unpublish the node":

Rule Set: Unpublish node
- Rule: Unpublish the node
- - Action: Unpublish content

You might ask why i didn't just put the Unpublish action into the Rule within "Check" Rule Set, like so:

- Rule: "Expired; unpublish"
- - Condition: Numeric Comparison - If the node's CCK_time_field is less than (or equal to) "now"
- - Action: Run Rule Set "Unpublish node"
- - Action: Unpublish content

The reasons for separating it into a different Rule Set are the following:

  1. I may want to add extra actions to the "Unpublish node" Ruleset, such as "Email user".
  2. I may have another Rule that will run the "Unpublish node" Ruleset, such as the Trigger "User flags content".
  3. I may want to schedule the "Unpublish node" Ruleset at a fixed time, thereby bypassing the "Check node expiry" procedure.

In a nutshell: if you think you will be re-using an action (or sequence of actions and conditions), make it a Ruleset. You will save time if/when you want to modify that action(s).

Once you understand Rulesets in a general sense, the scheduling will become obvious.

Illustration of a Simple Rule and Rule Sets

I_am_trying_to_understand's picture

I tried to attach this image to my post earlier.

Sorry to those who got that message more than once.

Initially I uploaded an image that was too wide, and tried to replace it without success.

Hopefully it will attach now?





[Edit: No dice - can anyone help me figure out how to do an attachment/inline image? And sorry again to those that will get this twice. Well I will try to attach without preview and see if that works but I am not holding my breathe]

Pretty diagram, but I prefer

aeronox's picture

Pretty diagram, but I prefer to visualise Rule Sets as separate subprocesses.

See attachment.

More complex arguments are required when dealing with changed/unchanged content (updating existing) etc.

Rule Sets with Scheduler, maybe?

toddwoof's picture

ANY notes on Rules and Rules Sets are good notes -- thanks!

But I should have been more clear on the specific need, which is Rules and/or Rules sets that are triggered based on either of two conditions:

Case 1: A user posts a new job, or publishes an expired/unpublished job listing to renew it. A Rule based on "content is going to be saved" (or a Rule Set, as aeronox notes) works fine for this: When content is going to be saved, check the date and set it to now +60 if it's less than or equal to now.

Case 2: The job listing just expires, based on the expiration date. In this case the "going to be saved" trigger won't work, whether the actions are in a Rule or a Rule Set, because nobody is saving the node.

I can think of three possible solutions:

  1. A cron-based Rule: Check published jobs on cron run, and unpublish them if the date has passed. Perfect, but I can't get this to work. See notes here: http://drupal.org/node/508224 .

  2. Some other trigger, like "about to view." This works, but it means people see access denied messages, which is inelegant. (Notes in that same entry.)

  3. Rules Sets with Scheduling. This is probably what I should have asked about: How to set up a Scheduler arrangement that will cover the cases. It has to:
    (a) Schedule the unpublish event when the node is saved.
    (b) Un-schedule or re-schedule it if someone changes the date and saves.

Has anyone used Rule Sets with Scheduling, to unpublish nodes based on the value of a CCK date field? Or, am I missing some other, simpler approach?

You could use the setup I

aeronox's picture

You could use the setup I provided to check regularly - daily or weekly - until the date is in the past.

What would trigger the daily

toddwoof's picture

What would trigger the daily or weekly check?

The image in

aeronox's picture

The image in http://groups.drupal.org/node/21377#comment-83827 shows how to loop at regular intervals until content meets a certain condition. That condition could be "has it been edited in the last 3 days?" by using the [node:mod- ] day/date tokens in a numerical comparison, less than:

<?php
echo strtotime("-3 days",time());
?>

To change the checking interval:
Change Scheduled time in the "New article" to "+1 week"
And change Schedule time in the "Check Expiry -> Not expired;reschedule check" rule to "+1 week"

For testing purposes, turn on the prcoess display in Rules Settings, change the schedule time in your rules to +1 minute, and run cron from the Administration menu. You see results faster that way.

Just think like a computer. You have a set of available events, conditions, and actions. Figure out exactly what you want, what you need to use, then sketch it down in a flow diagram like mine above.

Rule and Rule Set that might work

toddwoof's picture

An interesting approach -- thanks for the notes.

But I can't fire a triggered rule based on "Saving new content" because it won't address the case where someone wants to re-post an old listing. Here's a similar approach that I'm testing right now, that uses one triggered rule and one rule set with two rules in it, fired based on "Content is going to be saved":

A. Triggered Rule: When Job is saved, update expiration date if needed and schedule the date check.
Event: Content is going to be saved
Conditions: Saved content is Job, Content is published
Actions: Execute custom PHP code (if expiration date < now, set it to now +60 days), Schedule the rule set for +X days.

B. Rule Set: date check -- Unpublish expired Jobs and re-schedule date check for non-expired jobs.
Argument: Content

Rule 1: (if expired, unpublish it)
Conditions: Content is Job, Content is published, Execute custom PHP code (return “true” if expiration < now)
Action: Unpublish content

Rule 2: (If not expired, re-fire the triggered rule to re-schedule the check)
Conditions: Content type is job, Content is published
Action: Save a content

Seems as if it should work... But "thinking like a computer," I don't like either approach, because it means checking over and over again to see if a date has been reached, when I know exactly what the date is. Also, I think it means scheduling multiple (redundant) tasks for the same node if someone edits/saves several times. I could add more conditions to check the last time saved, etc, but at some point it starts to look like I'm trying to build a module using Rules...

So, a follow-up question: Is there a way to schedule a rule to fire on a specific date (the date entered in the date field at the time you save the node), but to have the scheduled task canceled or updated accordingly if someone changes the expiration date and saves again?

Another question: how do I see the scheduled tasks?

to have the scheduled task

aeronox's picture

to have the scheduled task canceled or updated accordingly

This is being worked on.

how do I see the scheduled tasks?

I'm not sure if this is possible - and can't check right now. It would involve Views.

This approach didn't work

toddwoof's picture

Quick follow-up: Still some issues to work on. For one thing, I can't use "about to be saved" with scheduled rules, because until the event is actually saved, there isn't a node yet. So, next step will be to try splitting it up and making a separate rule for new content that doesn't include the scheduling part (but somehow prompts it after saving).

A new module...a new solution

joemaine's picture

The auto expire module http://drupal.org/project/auto_expire seems to address the expiration (unpublish) and renewal of a node. It generates an email message (with tokens) to the author of the node prior to 'expiration' and allows the author to extend the 'life' of the node. It also enables the admin to set an expiration date x days into the future, and a date (optional) to purge the info in the database. ...and it works with views.

Granted, it's less flexible than rules...but it seems easier to implement.

Do be warned, it works great with content created after the module is loaded, but you will need to modify a table (Table: auto_expire) to add previously posted (affected) nodes.

Question

mileZ-gdo's picture

I would like to use aeronox's method. But I'm still a newbie :-)

I've a little issue with the "number comparision" thing. I tried this but I'm doing something wrong;

if [node:field_cck_date-timestamp], srttotime('+1 day') equal to date('d-m-Y') is true unpublish node

What I'm doing wrong?

Thanx

mileZ

Great stuff

mrust's picture

I successfully used aeronox's method. Just a few tips for anyone else trying this:

I initially used two tests (Less Than and Greater Than) to check node expiry. Since the cron job is triggered on the second and can run quite fast, it's important to get the OR EQUAL TO into one of the tests... Easiest to negate the condition for both rules and use Less Than for both.

I used the date time instead of the time.

It's a good idea to add the trigger for both creation and updating.

I added an extra condition so that rules are only scheduled if the content is published.

If you name the scheduled task "Task-Unpublish-[node:nid]" then the other scheduled task will be removed in case the user updates the expiry date.

It's a good idea to filter by content-type so that you don't accidentally unpublish new content just because it contains a date field.

Not sure how this will work with repeated dates.. It might trigger after the first date in the set passes expiry.. Maybe not what we want.

I have also a problem with the numeric comparaison

gagarine's picture

Thanks for all... this posts help me a lot.

My ruleset run every days this work great. But rules inside this ruleset with numeric comparison never run...

[node:field_property_expiration_date-timestamp] is less than

<?php
echo strtotime('now');
?>

I add a capture her: http://www.2shared.com/file/11785237/9741101a/Picture_2.html (we can upload file on drupal group any more?)

EDIT: All my fault this work now. It's a stupid problem with an other rule.

sergh27's picture

To set rules is not so easy as looks.
How to set step by step ( I mean 1. enable the module a,d,c,..., 2. Go and .. 3. In do ...)
automatically delete (by user who own the site) content that is older that x days ?

@sergh27 Yeah rules is hard

gagarine's picture

@sergh27 Yeah rules is hard for newcomers. I think specially if you are not a programmer and the interface is not always crystal clear...

You can use this howto http://drupal.org/node/517674 and change the publish action to delete. You can also use http://drupal.org/project/auto_expire

Rules

Group organizers

Group categories

Categories

Group notifications

This group offers an RSS feed. Or subscribe to these personalized, sitewide feeds:

Hot content this week