Page 1 of 1

"Timed" Custom Capabilities (Jason Please Help Finalize)

PostPosted: January 14th, 2011, 6:08 pm
by smitchell360
Like many, I've been trying to figure out how to handle custom capabilities that I want to last for a limited period of time. My solution is the following (I will post my code once I obfuscate and clean it up a bit).

Given a custom capability of: custom_timed_cap

When you initially provision the capability, always create TWO entries for the custom capability:
- custom_timed_cap
- custom_timed_cap_beg20110101_end20110201

this means that the capability should begin on 2011-Jan-01 and end on 2001-Feb-01

What my code does is, every night (or every hour or every 5 minutes) loops through your users, grabs all capabilities, looks for "timed" capabilities and adds/removes the capability as indicated.

What is important (to me) is that the "custom_timed_cap_beg20110101_end20110201" is NEVER removed. This way, I have a record of every custom capability that was provisioned.

I only use "custom_timed_cap" to determine if someone can access content.

Make sense?

Hopefully Jason will chime in on this.

Re: "Timed" Custom Capabilities (Jason Please Help Finalize)

PostPosted: January 14th, 2011, 6:12 pm
by smitchell360
Here is the code (no idea how to get syntax highlighting turned on for this). It is REALLY rough but should be a good start for many of you. Please keep this thread going with comments so that we can shore up this functionality in all of our sites.

Here is the code (no idea how to get syntax highlighting turned on for this). It is REALLY rough but should be a good start for many of you. Please keep this thread going with comments so that we can shore up this functionality in all of our sites.

Code: Select all

function grccertify_check_for_expired_capabilities($siteuser = NULL)
{
   date_default_timezone_set("America/Phoenix");
   $start_date_regex = "/beg[0-9]+/i";
   $end_date_regex = "/end[0-9]+/i";
   $allusers = NULL;
   
   if ($siteuser == NULL) {
   
      // check all users
      $allusers = get_users_of_blog();
      //error_log("all users of the blog: " . print_r($allusers, TRUE), 0);
            
      if ($allusers) {
      
         foreach ($allusers as $singleuser) {
            
            error_log("processing user: " . $singleuser->ID, 0);
            $user = new WP_User( $singleuser->ID );
            //error_log("all user info: " . print_r($user, TRUE), 0);
            //error_log(" -- allcaps: " . print_r($user->allcaps, TRUE), 0);
            
            // check the list for any timed capabilities in the form:
            // capability_name_begyyymmdd_endyyymmdd
            if ($user->allcaps) {
            
               foreach ($user->allcaps as $single_capability => $single_capability_value){
                  
                  $cap_to_process = $single_capability;
                  //error_log("capability to process: " . print_r($cap_to_process, TRUE), 0);
                  
                  $end_date = null;
                  $start_date = null;
                  $capability = null;
                  
                  // parse the capability
                                 
                  // get and trim off end date
                  if ( preg_match($end_date_regex, $cap_to_process, $end_date_string_array) ) {
                  
                     //error_log("getting end_date: " . $end_date_string_array[0] . " from: " . print_r($cap_to_process, TRUE), 0);
                     
                     $end_date = grccertify_string_to_date( $end_date_string_array[0] );
                     //error_log("end_date: " . print_r($end_date, TRUE), 0);
                     
                     //error_log("trimming end_date from: " . print_r($cap_to_process, TRUE), 0);
                     $cap_to_process = str_replace ("_" . $end_date_string_array[0], "", $cap_to_process);
                     //error_log("end_date trimmed and capability string is now: " . print_r($cap_to_process, TRUE), 0);
                  }
                  
                  // get and trim off start date
                  if ( preg_match($start_date_regex, $cap_to_process, $start_date_string_array) ) {
                  
                     //error_log("getting start_date: " . $start_date_string_array[0] . " from: " . print_r($cap_to_process, TRUE), 0);
                     $start_date = grccertify_string_to_date( $start_date_string_array[0] );
                     //error_log("start_date: " . print_r($start_date, TRUE), 0);
                     
                     //error_log("trimming start_date from: " . print_r($cap_to_process, TRUE), 0);
                     $cap_to_process = str_replace ("_" . $start_date_string_array[0] , "", $cap_to_process);
                     //error_log("start_date trimmed and capability string is now: " . print_r($cap_to_process, TRUE), 0);
                  }   

                  // you are left with just the capability
                  $capability = $cap_to_process;
                  
                  if ( $start_date || $end_date ) {
                  
                     //error_log("capability to add or remove is: " . print_r($capability, TRUE), 0);
                     //error_log("start_date is: " . $start_date, 0);
                     //error_log("end_date is: " . $end_date, 0);
                     //error_log("now is: " . strtotime ("now"), 0);
                     
               
                     // ensure that the capability_name is removed if the start-end range is not met
                     if ( strtotime ("now") > $end_date )
                     {
                        error_log("AFTER END DATE. Removing capability: " . print_r($capability, TRUE) . " from user ID: " . print_r($user->ID, TRUE), 0);
                        $user->remove_cap ($capability);         
                     }
                     
                     if ( strtotime ("now") < $start_date )
                     {
                        error_log("BEFORE START DATE. Removing capability: " . print_r($capability, TRUE) . " from user ID: " . print_r($user->ID, TRUE), 0);
                        $user->remove_cap ($capability);         
                     }                     
                     
                     // ensure that the capability_name is present if the start-end range is met
                     if ( (strtotime ("now") >= $start_date) && ($end_date == null) )
                     {
                        error_log("MET START DATE and NO END DATE. Adding capability: " . print_r($capability, TRUE) . " to user ID: " . print_r($user->ID, TRUE), 0);
                        $user->add_cap ($capability);
                     }
                     if ( (strtotime ("now") >= $start_date) && (strtotime ("now") <= $end_date) )
                     {
                        error_log("BETWEEN START DATE AND END DATE. Adding capability: " . print_r($capability, TRUE) . " to user ID: " . print_r($user->ID, TRUE), 0);
                        $user->add_cap ($capability);
                     }   
                     if ( (strtotime ("now") <= $end_date) && ($start_date == null) )
                     {
                        error_log("NO START DATE AND BEFORE END DATE. Adding capability: " . print_r($capability, TRUE) . " to user ID: " . print_r($user->ID, TRUE), 0);
                        $user->add_cap ($capability);
                     }                        
                  
                  }
                  else {
                     // this is not a timed capability, do nothing
                     error_log("capability is not timed: " . print_r($capability, TRUE), 0);
                  }
               }
            }
            else {
               error_log("no capabilities found for user:" . print_r($user->ID, TRUE), 0);
            }
         }
      }
   }
   else {
   
      // TODO: just check a single user
      // TODO: make subfunction below
   }   
}


function grccertify_string_to_date ( $date_as_string ) {

   //error_log("converting string: " . print_r($date_as_string, TRUE), 0);
   $year = substr($date_as_string, 3, 4);
   $month = substr($date_as_string, 7, 2);
   $day = substr($date_as_string, 9, 2);
   
   //create a date
   $date_to_return = strtotime( $year . "-" . $month . "-" . $day );
   //error_log("date to return: " . print_r($date_to_return, TRUE), 0);
   
   return $date_to_return;
}

Re: "Timed" Custom Capabilities (Jason Please Help Finalize)

PostPosted: January 14th, 2011, 8:15 pm
by Cristián Lávaque
To color the code you just add "=php" to the opening code tag and then wrap between PHP tags what you want highlighted:

Code: Select all
[code=php]Lorem <?php echo 'Ipsum'; ?>[/code]


I think it's great that you started working on the code to add this functionality and are sharing it with us. Thanks! :)

I'll give the code a closer look later, but first a couple comments on what you said in the opening post.

I think that you could dispense with the "beg" and "end" prefixes, just having the dates would be enough since the position relative to the hyphen would indicate which is which, like this:

..._yyyymmdd-yyyymmdd
..._yyyymmdd-
..._-yyyymmdd


The regex would be simple (although you can also explode the string first with the underscore and then the hyphen).

Code: Select all
   $start_date_regex = "~_(\d{8})-~";
   $end_date_regex = "~-(\d{8})$~";


How are these dates calculated? Do you enter them by hand or are they calculated by the plugin adding a certain number of days to the date the ccap was given to the member?

Re: "Timed" Custom Capabilities (Jason Please Help Finalize)

PostPosted: January 14th, 2011, 8:34 pm
by Cristián Lávaque
Why do you need the grccertify_string_to_date function? To remove the beg/end prefixes and change the date to the YY-MM-DD format? I'm pretty sure YY MM DD (e.g. 20100114) works fine with strtotime.

http://mx.php.net/manual/en/datetime.formats.date.php

Re: "Timed" Custom Capabilities (Jason Please Help Finalize)

PostPosted: January 14th, 2011, 10:04 pm
by Cristián Lávaque
You can probably do this part

Code: Select all
<?php

foreach 
($user->allcaps as $single_capability => $single_capability_value){

    $cap_to_process = $single_capability;
    //error_log("capability to process: " . print_r($cap_to_process, TRUE), 0);

    $end_date = null;
    $start_date = null;
    $capability = null;

    // parse the capability

    // get and trim off end date
    if ( preg_match($end_date_regex, $cap_to_process, $end_date_string_array) ) {

        //error_log("getting end_date: " . $end_date_string_array[0] . " from: " . print_r($cap_to_process, TRUE), 0);

        $end_date = grccertify_string_to_date( $end_date_string_array[0] );
        //error_log("end_date: " . print_r($end_date, TRUE), 0);

        //error_log("trimming end_date from: " . print_r($cap_to_process, TRUE), 0);
        $cap_to_process = str_replace ("_" . $end_date_string_array[0], "", $cap_to_process);
        //error_log("end_date trimmed and capability string is now: " . print_r($cap_to_process, TRUE), 0);
    }

    // get and trim off start date
    if ( preg_match($start_date_regex, $cap_to_process, $start_date_string_array) ) {

        //error_log("getting start_date: " . $start_date_string_array[0] . " from: " . print_r($cap_to_process, TRUE), 0);
        $start_date = grccertify_string_to_date( $start_date_string_array[0] );
        //error_log("start_date: " . print_r($start_date, TRUE), 0);

        //error_log("trimming start_date from: " . print_r($cap_to_process, TRUE), 0);
        $cap_to_process = str_replace ("_" . $start_date_string_array[0] , "", $cap_to_process);
        //error_log("start_date trimmed and capability string is now: " . print_r($cap_to_process, TRUE), 0);
    }

    // you are left with just the capability
    $capability = $cap_to_process;
 


like this

Code: Select all
<?php

foreach ($user->allcaps as $capability => $v){

    
// Parse the capability.
    
if (preg_match('~_(\d{8}?)-(\d{8}?)$~'$capability$matches)){
        
$end_date = empty($matches[1]) ? null strtotime($matches[1]);
        
$start_date = empty($matches[2]) ? null strtotime($matches[2]);
        
$capability str_replace($matches[0] , ''$capability);
    }
 


I used the date format without the prefixes for the regex, and didn't add the error log stuff cause I'm not sure how to do it. I didn't test it at all, so forgive me if I messed it up somewhere. :roll:

Re: "Timed" Custom Capabilities (Jason Please Help Finalize)

PostPosted: January 15th, 2011, 12:25 pm
by smitchell360
Thanks. I'll take a look at this.

The main reason that I add the "beg" and "end" is that I want the information stored to be a bit more readable.

The custom capabilities cannot contain "-". They use "_" or alphanumeric.

strtotime did not convert 20110101. It requires it to be formatted a bit more. As such, I need to do some formatting ... and adding the "beg" and "end" will probably help another developer down the road.

Re: "Timed" Custom Capabilities (Jason Please Help Finalize)

PostPosted: January 15th, 2011, 12:26 pm
by smitchell360
To make this even more robust, we should probably add functions that return the correct PayPal button string based on:

- custom_capability
- start_date
- end_date

I'll work on that today. Unless you have some time...

Re: "Timed" Custom Capabilities (Jason Please Help Finalize)

PostPosted: January 15th, 2011, 1:47 pm
by Cristián Lávaque
smitchell360 wrote:strtotime did not convert 20110101.


That's odd. I tried this

Code: Select all
<?php echo strtotime('20110101'); ?>


and it outputs 1293861600.

smitchell360 wrote:The main reason that I add the "beg" and "end" is that I want the information stored to be a bit more readable.

The custom capabilities cannot contain "-". They use "_" or alphanumeric.


Got it. Then my regex I think would be

Code: Select all
'~_beg(\d{8})_end(\d{8})$~'


Using the prefixes, you don't even need the underscore between the dates, e.g. capabilityname_beg20110112end20110211.


I wanted to ask you something: the capability with the dates is added to each member, right? How does the script determine the beg/end dates of the capability? You must tell s2Member somewhere that the capability starts after n days and ends after n days, right? Where does this happen?

smitchell360 wrote:To make this even more robust, we should probably add functions that return the correct PayPal button string based on:

- custom_capability
- start_date
- end_date

I'll work on that today. Unless you have some time...


I'll let you do it. I'm actually not that good writing new code, am better working with existing one.


Back to what I ask above, what'd happen if you add a start_date and end_date to the button and the person who visits the registration page with that button leaves that browser window/tab open for a couple of days before purchasing, then the dates would be wrong.

I think it should be start_time and end_time in days. That could be saved that way and the dates calculated when needed, or calculate the dates right after the order is complete and stored as dates.

Maybe capabilities with time triggers could have the times in days for begins and ends as part of the name, e.g. capabilityname_beg30end365. Then the calculated dates could be added to that right after purchase, e.g. capabilityname_beg30end365_beg20110214end20120213. It'd be easy to tell them apart.

Re: "Timed" Custom Capabilities (Jason Please Help Finalize)

PostPosted: January 15th, 2011, 3:27 pm
by smitchell360
Good ideas. Not sure why my strtotime was not working. It must have been another bug. I'll work more on this next week.

Re: "Timed" Custom Capabilities (Jason Please Help Finalize)

PostPosted: January 18th, 2011, 11:21 am
by smitchell360
Here is what I'm doing:

Code: Select all
$beg_date = strtotime ("now");
$end_date = strtotime ("+1 week");

$beg_date_string = date ( "Ymd", $beg_date );
$end_date_string = date ( "Ymd", $end_date );

$custom_capabilities_string = "take_grc_professional_exam,take_grc_professional_exam_beg" . $beg_date_string . "_end" . $end_date_string;


Then add this string to the PayPal form (I'm putting the actual form code on the page instead of the S2 shortcode)

Code: Select all
<input type="hidden" name="item_number" value="1:<?php echo $custom_capabilities_string; ?>" />

Re: "Timed" Custom Capabilities (Jason Please Help Finalize)

PostPosted: January 18th, 2011, 12:33 pm
by Cristián Lávaque
I see what you're doing, that's good. Where do you put that code?

Doesn't that fix the time ranges for you, though, since to change them you'd have to edit the code or the member's account after creation?

Re: "Timed" Custom Capabilities (Jason Please Help Finalize)

PostPosted: January 18th, 2011, 1:16 pm
by Cristián Lávaque
Nevermind, I re-read your post and now I'm guessing you do all that in the post's page. Is that right?

That works, but since it's not obfuscated, you could have someone edit the times in the form and submit one which gives him a longer range, right? They could probably even find all the custom capabilities you used in other buttons and add them all to a single purchase. :?

You could probably pass them as a variable that's unreadable or looks like something else, but gets turned back into the capabilities after the registration, but before saving it in the database. Probably using base64_encode and base64_decode...

Code: Select all
<?php
$capability 
= 'take_grc_professional_exam';
$beg_time = 'now';
$end_time = '+1 week';

$capability = base64_encode($capability . ',' . $capability . '_beg' . date('Ymd', strtotime($beg_time)) .  'end' . date('Ymd', strtotime($end_time)));

echo '<input type="hidden" name="item_number" value="1:' , $capability , '" />';
?>


Now, someone with PHP knowledge may try base64-decoding that string, but that'd be a small number of people, if any. If you want to make it harder to break, you could add a salt.

Re: "Timed" Custom Capabilities (Jason Please Help Finalize)

PostPosted: January 18th, 2011, 3:02 pm
by Cristián Lávaque
That same code could actually be made into a function to use anywhere you want to add capabilities with date range to a button. Something like

Code: Select all
<?php
function capability_dated
($capability, $beg_time, $end_time)
{
    echo base64_encode($capability . ',' . $capability . '_beg' . date('Ymd', strtotime($beg_time)) .  'end' . date('Ymd', strtotime($end_time)));
}
?>


And then used like

Code: Select all
<input type="hidden" name="item_number" value="1:<?php capability_dated('take_grc_professional_exam', 'now', '+1 week'); ?>" />

Re: "Timed" Custom Capabilities (Jason Please Help Finalize)

PostPosted: January 18th, 2011, 7:09 pm
by smitchell360
Indeed. I plan to clean everything up (and will post the plugin) once I get my entire flow working. I have this odd infinite loop after getting a successful payment notification from the PayPal sandbox account.

Re: "Timed" Custom Capabilities (Jason Please Help Finalize)

PostPosted: January 18th, 2011, 7:10 pm
by smitchell360
Indeed. I plan to clean everything up (and will post the plugin) once I get my entire flow working. I have this odd infinite loop after getting a successful payment notification from the PayPal sandbox account.

Re: "Timed" Custom Capabilities (Jason Please Help Finalize)

PostPosted: January 18th, 2011, 7:11 pm
by smitchell360
Regarding obfuscation, the pricing is already on the form right now ... in plain view ... so I'm not sure that this is a big deal :) ... but very good point.

Re: "Timed" Custom Capabilities (Jason Please Help Finalize)

PostPosted: January 19th, 2011, 5:49 pm
by Cristián Lávaque
Great. I look forward to your update. I'll help with what I can. :)

Re: "Timed" Custom Capabilities (Jason Please Help Finalize)

PostPosted: October 12th, 2011, 2:06 am
by kennymcnett
smitchell360 wrote:Indeed. I plan to clean everything up (and will post the plugin) once I get my entire flow working. I have this odd infinite loop after getting a successful payment notification from the PayPal sandbox account.


smitchell, did you ever finalize this? Would be awesome for me.

Thanks,

Kenny