Force Same Date as EOT for Everyone

One of the articles on the main s2Member site explains how to make the End of Term date the same for everyone, no matter when they pay for access. This is something that I would like to do as we also make use of a private Facebook group as well as the private members area of our website and by having the same end date for everyone, makes it easier to manually check which people should no longer be able to access the Facebook group if all the end dates are the same.

Here is a link to the article for reference;

I thought I would try this, but the ezPHP plugin linked to in the article is no longer available. I’ve tried a couple of alternative plugins that can be used to enter php code directly onto a page, but whenever I’ve tried, the snippet that should be entered within the pro-form shortcode as the ‘rp’ attribute seems to break up the shortcode, so that it won’t work.

Does anyone have any alternative sugestions on how to do this without the ezPHP plugin?

I’m also interested in this, and am wondering if it’s possible with the free (framework) version of S2?
One way to maybe do it is to access the appropriate S2 hook, and include the php code from your link in the mu-plugin s2-hacks.php.
Here’s a list of Paypal-related S2 hooks:

Maybe experiment with some of these hooks and try to get something to work?

I’ve been having a look at this, but I really don’t understand enough about php to work out how to go about doing anything with it myself. Looking at some of the code for s2Member, I can vaguely understand what some snippets of it all does when generating the shortcode, but I’ve not got the knowledge to even begin working out how to piece something together that would work at the moment.

Bit of a nuisance really because seeing that this was possible in the documentation was part of the reason I went with giving s2Member a go. I didn’t realise at that point that the ezPHP plugin was no longer available and alternatives wouldn’t work.

A bit more detail of the problems I was having trying alternative plugins to allow the use of the php code to set the ‘rp’ value within the form shortcode: The form shortcode gets broken up due to the additional square brackets being used within it being read as the end of the shortcode instead of a shortcode within a shortcode.
There was enough information available in the shortcode to display a form, but then the last little part where the ‘rp’ value is set onwards just gets displayed as plain text.

I may have to give up with this feature…

Hi Adrian, at some point in near future I’m going to address this myself, and if it’s not possible through Paypal button, then I’ll run some automatic code in the middle of the night that does an inventory of all members, and set everyone’s EOT to a fixed date (12/31 for me). That’s not as nice as having it appear on Paypal button so the paying member can see it for themselves, but maybe in creating Paypal button I can write the description to explain things.

By the way, if you paid for PRO version of S2member, I think you’re entitled to ask the professional staff how to do this. We’re just other users here in this forum.

Hi Adrian, I’ve been thinking about this fixed EOT day, and the problem I see with the solution implemented in that knowledge base link you provided is that what if someone’s term hasn’t expired yet, and they want to tack on another year? Maybe they have the idea of buying another year right away, so that they have two years paid for, for example. The solution in that link doesn’t handle that case, because it alters the EOT during the purchase itself, and every time the member hits the button, it always sets the same EOT date, rather than tacking on another year. When that code is not used, and PayPal button is used in normal mode, then default behavior is that a buy-now button does tack on additional years, which is what we want.

The only thing we want to alter is after the membership has been paid for, then we want to examine the EOT and adjust it appropriately. So now I think the best approach is to do an inventory of all members in the middle of the night, look at their EOT’s, and adjust them accordingly. And by accordingly I mean round it to the day you want. For example, if we want the expiration date to be 12/31, and this is July right now and my inventory reveals someone just bought a 1-year membership so their EOT is 7/28/2021. I’ll have some code decide some cutoff date before which I round down the year, and after which I round up the year. For 7/28, I would round down to 2020, so EOT would be re-set to 12/31/2020. But if they bought their membership on 11/15, say, so their EOT is 11/15/2021, then that’s close enough that I would round up the year, so new EOT would be 12/31/2021.

I’m going to implement it this way, and if you like I can share the code when I’m done?

Also, as an aside, that ezphp plugin is actually available, just not at the WP plugin directory. But if you follow the link given in the article you posted, then it shows how to download the zip file. I tried that and it’s available.

You make a good point regarding the making of a second payment for an additional year straight away. That’s not something I even considered someone might try doing. I guess to some extent though, that would depend on what kind of membership access you are selling that people want to access and how the payment buttons are displayed for them to want to potentially make an additional payment straight away?

The website I am working on is basically a club website, but we have members across the country, so online payments mean we don’t have to wait for a meet-up or event to get physical cash off people when they want to join or renew and also easier for setting up their access to the member only part of the website for new members.

My intention was for new members to join using one button that set the EOT the same as everyone else for that year on the public area of the website, then in the member’s only area have a choice of a standard 1 year button one off payment or a standard yearly subscription button for membership renewals near the end of the membership year.

What I decided to settle on in the end was to manually change the variables in the shortcode each month to set how many months were being paid for on the non-recurring initial payment, so at the moment it’s set to 9 months (with renewals being due at the end of April), then in a couple of weeks change it to 8 months, etc.

If you manage to implement exactly what you have described with your example, that would be alot easier and remove the need to manually adjust the form shortcode as I have currently resigned to doing. It certainly sounds a pretty good solution, so if you don’t mind, it would be great if you could share the code when you’re done, thanks.

Regarding the ezPHP plugin, I hadn’t noticed the link to download the zip file. I just looked on the Wordpress plugin repository link and saw the message saying it had been removed from there due to a guideline violation.

Hey Adrian, I’m also managing a club, and am creating a website where they can go to update their annual membership and I can tie in with Mailchimp. Regarding my example of a member hitting the button twice, there’s actually another way things can go wrong with modifying PayPal button:
What if someone is nearing their expiration date EOT, and decide to renew before they expire. This is a common occurrence. Then if we modify the PayPal button as described in that link you initially shared, then it would make the new EOT the same as the old EOT! Unless you manually change the PayPal button every year, so that, say 3 months prior to your fixed EOT date you set the new EOT 1 year later. It’s not as bad as manually doing it as much as you’re now doing, but you’d still have to remember to do it every year.

So, I wrote some code that in the middle of the night loops through all the users, and looks at their EOT’s. If the code encounters an EOT that is different than the usual end-of-year EOT, then that means they just signed up for +1 year membership. So if they signed up today, their EOT would be 7/31/2021. The code has a built-in cutoff date (that can be set to whatever you desire), before which their membership goes until the end of the year (12/31/2020 in example above), and after which it extends to the end of following year.

In writing the code and testing it out, I discovered something new about S2Member, which is once an EOT expires, S2 makes it disappear in the user’s profile. I find that to be undesirable, because I’d like a record of past members, and when their membership last expired. So, to solve this, I added some extra code, to store a new custom field “last_eot_year”, which stores just the year (e.g. 2019). It keeps current with the year of the real EOT, but once the real EOT disappears, the Last EOT Year remains as a permanent record. If you don’t want that functionality, then of course erase that part of code. But if you like it, then you need to create custom field in S2Member before running the code.

To implement the code, I presume you have access to your WP file directory? In the directory wp-content, if it’s not already there then create a subdirectory called mu-plugins. Then save the following code in a file called whatever you want, but people here like to call it s2-hacks.php.

The code creates a scheduled task (“cron” job), and technically it will only run at that scheduled time if there’s somebody accessing the website at that time. Otherwise, it will execute after that scheduled time, the first thing once someone accesses site. If you want it to run absolutely at the scheduled time, regardless of user activity, then need to schedule an absolute cron job. I plan to do that once I migrate site over to HostGator. Here’s their article on how to do that:

Please test this code out on a test site (not real users) before going live on your real site! I recommend installing plugin “crontrol”, because it lets you run the cron job instantly, which is helpful during testing.
Good luck, and let me know if you have questions about the code. Here’s the code:

//Schedule an action if it's not already scheduled
if ( ! wp_next_scheduled( 'uea_cron_hook' ) ) {
		wp_schedule_event( strtotime('10:00:00'), 'daily', 'uea_cron_hook' ); //UTC time

//Hook into that action that'll fire daily
add_action( 'uea_cron_hook', 'uea_cron_function' );

//Function that runs on cron
function uea_cron_function() {
	$users = get_users('orderby=ID');
	$eot_day = '12/31/'; //Adjust EOT date as desired
	$eot_hour = ' 20:00:00'; //EOT hour (20:00 UTC = noon PST on 12/31)
	$cutoff_day = '10/01/'; //Adjust cutoff date as desired
	$cutoff_hour = ' 07:00:00'; //cutoff hour (07:00 UTC = 00:00 PDT)
	foreach ($users as $user){
		$user_id = $user->ID;
		$user_role = $user->roles[0]; //roles is an array with one element
		if ($user_role != 'administrator') {
// Adjust EOT if necessary.
			$keymeta = 'wp_s2member_auto_eot_time';
			$old_eot_time = intval(get_user_meta($user_id, $keymeta, true)); //if not null, then UTC time (sec's since 1970)
			if ($old_eot_time) { //non-zero EOT
				$eot_year = date('Y', $old_eot_time); //Year of current EOT, 4-digit format (string)
				$cutoff_time = strtotime($cutoff_day . $eot_year . $cutoff_hour);
				if ($old_eot_time < $cutoff_time) { //did not make cutoff time, so use previous year
					$eot_year = strval(intval($eot_year)-1);
				$new_eot_time = strtotime($eot_day . $eot_year . $eot_hour);
				if ($new_eot_time != $old_eot_time) { //A change was made to EOT, so update user
					update_user_meta($user_id, $keymeta, strval($new_eot_time));
// Also adjust Last EOT if necessary.
				$last_eot_year = '';
				$keymeta2 = 'wp_s2member_custom_fields';
				$valuemeta = get_user_meta($user_id, $keymeta2, true);
				if ($valuemeta) { //checks that there is non-empty custom field data
					foreach ($valuemeta as $keycustom => $valuecustom) { //loop through associative array
						if ($keycustom == 'last_eot_year' && $valuecustom) { //There is a non-null Last EOT year
							$last_eot_year = $valuecustom;
				if ($eot_year != $last_eot_year) { //A change was made to EOT, so update Last EOT year
					if (!$valuemeta) $valuemeta = array();
                    $valuemeta['last_eot_year'] = $eot_year;
					update_user_meta($user_id, $keymeta2, $valuemeta);

That’s some good work there. I think I basically understand what the code is doing, although your notes help with that. It seems a logical way that should work, so I’ll give it a go at some point this week.

I wouldn’t know where to start with trying to create something like this (partly due to my lack of knowledge of php), so this is amazing, thanks.

1 Like

Hi, I had to make a slight tweak to the code above. There may be a case where $valuemeta is null, and so before setting one of its array elements, I must declare it as array, so I added this line:
if (!$valuemeta) $valuemeta = array();

I already edited my post above to reflect that change.

Hello @EricT, I know this is a good few months since you helped with this before, but due to having to focus on other things, I have only just had a chance to try this out on a test website, however I am having problems.

I have created the file containing the code you provided and saved it in the described location, however it doesn’t seem to be working. On the Plugins list in the Wordpress Dashboard, the s2-hacks.php file seems to be recognised as a Must Use plugin and the cron job seems to be recognised by the Crontrol plugin. When I try running the cron job though, nothing happens.

Also, in the Site Health section of the Wordpress Dashboard, it says "A plugin has prevented updates by disabling wp_version_check() ". This message is only there when I have the s2-hacks.php containing your code in the mu-plugins subdirectory. When I remove that file, the error disappears again.

I’m guessing the two things are linked in some way. Would you have any suggestions at all?

Thanks in advance.

Hi Adrian,

I have experienced weird issues when I try to do things on a local test site. Is that what you’re doing to test things out, using your own local machine as a server?

If that is the case, then maybe on the real site you can just create a do-nothing s2-hacks.php file, which just has the barebones necessary to be functional (work with cron utility), but not change anything on your system. If that works, then you’ll know it’s your test site which is the problem.

There’s nothing seemingly in my code which interferes with version checking, that’s why I suspect it’s your test site.

I didn’t think there would be anything in the code that would cause the problem, but your response has helped me. (Your patience and assistance is much appreciated as I am still new to the php side of things).

I had another look at my file and I’d got an error in the header code, so there’s no problems related to this coming up on the Site Health section now.

Running the cron job with the Crontrol plugin still doesn’t seem to be doing anything though and I can’t see anything that I’ve missed. Because I’m still pretty new to using php, there’s probably something really obvious that I’ve not managed to get quite right…

For the test site, I set up a subdomain and installed all the same stuff, so everything should be the same (apart from the users’ personal data. I’ve just made up some ‘users’ with different EOTs for testing purposes) as the main site for testing with, so shouldn’t be any issues regarding local testing.

For the s2-hacks.php file I’ve pared it back to the barebones; I have the php opening tag, minimal header info (plugin name and description), your code as supplied above and the php closing tag. Am I missing something really obvious here?

Thanks in advance.

That’s good that you’re not getting that error anymore. But you say the cron job doesn’t do anything. When you look at the “Cron Events” in Crontrol, do you see the custom cron job listed, scheduled for some specific time? If it’s there, that’s good, but then to run it you need to hover your mouse over the event until you see “Run Now”, then click that.

Another thing is did you create the S2 custom field “last_eot_year”? That’s needed in the second part of my code, if you want that feature. If you don’t care about that, then delete that whole section, and then you don’t need to create custom field either.

Let me know if the above suggestions helped.

The Cron Events list includes the custom cron job, uea_cron_hook with the next scheduled run time shown as expected. Clicking Run Now reloads the page with the message " Scheduled the cron event uea_cron_hook to run now.", but when I go back to look at the Users section of the Dashboard, all of the EOTs remain as they were instead of all changing to the date specified in the code.

I did create the custom field “last_eot_year”, and there’s no change to the value of that visible in the Users list either. I’ve just tried removing that part of the code and running it again and I still can’t see any kind of changes to the Users data (although I wasn’t actually expecting that to make a difference in regards to the change of eot itsself).

Could it be possible that the host servers could be doing something to prevent it from running? That’s the only thing I can think of now.

I would say that it is being allowed to run. The mere fact that the cron job appears in Crontrol shows that the code is running.

Are you sure you have some non-zero EOT’s for the users? If you manually edit a member’s profile, maybe try changing it, to be sure. It’s the field “Automatic EOT Time”. Click the ? mark next to it to learn what format to stick in. I usually just write “+1 year”, because that mimics someone renewing for another year.

Other than that, I’m stumped why nothing is happening for you.