Auto EOT not working consistently

No, it wouldn’t, unless the customization is in an mu-plugin.

I will see if I can come up with some code to deal with this, but that might take a couple of weeks.

I have now opened a Github issue. I will see what I can do about writing some code for your situation but, as I said, it might take a while.

Thanks, Tim, much appreciated. I’ve got a start on the code. This finds the users who’s EOT has already passed:

SELECT * FROM [database].usermeta WHERE meta_key LIKE ‘%s2member_auto_eot_time%’ AND meta_value < now()

And the following should retrieve all users who aren’t ‘subscriber’. Subscriber is the role of expired members, as far as I can tell, so it should list all members who ARE NOT expired:

SELECT user_id FROM [database].usermeta WHERE meta_key LIKE ‘%capabilities%’ AND meta_value NOT LIKE ‘%subscriber%’

The part I’m stuck on right now is how to join these two based on the user_id field: I want to get a list of all user_ids that have a record matching the first query and the second query. But, because these are in the same table, I don’t know how to do the join.

Update: I may have figured this out. Here’s the query I’ve got currently, and it seems to work as

CREATE TEMPORARY TABLE IF NOT EXISTS usermeta_temp1 AS (SELECT * FROM [database].usermeta WHERE meta_key LIKE ‘%s2member_auto_eot_time%’ AND meta_value < now());
CREATE TEMPORARY TABLE IF NOT EXISTS usermeta_temp2 AS (SELECT user_id FROM [database].usermeta WHERE meta_key LIKE ‘%capabilities%’ AND meta_value NOT LIKE ‘%subscriber%’);
SELECT * FROM usermeta_temp1 INNER JOIN usermeta_temp2 ON usermeta_temp1.user_id = usermeta_temp2.user_id;

What do you think?

Nile, it isn’t the way I’d do it, because I’d prefer to use WP functions rather than interacting directly with the database, but that doesn’t mean your way is wrong. I was thinking more along these lines:

<?php

function kts_delete_members_at_eot() {
	$args = array(
		'role__in' => array( 's2member_level1', 's2member_level2', 's2member_level3', 's2member_level4' ),
	);
	$users = get_users( $args );

	foreach ( $users as $user ) {
		$eot = get_user_meta( $user->ID, 's2member_auto_eot_time', true );
		if ( current_time( 'mysql' ) > $eot ) {
			wp_delete_user( $user->ID );
		}
	}
}
add_action( 'init', 'kts_delete_members_at_eot' );

Note: that’s completely untested.

But if your method works, it works.

Thanks, Tim. I wold prefer to use Wordpress instead of interacting directly with the database, but wasn’t having much success figuring out how. Thank you for your example of concept.

Two questions about your method:

  1. Does it loop through every single user in the database? If yes, this seems really inefficient – it would be much faster if a search could be done to retrieve only users who’s EOT had already passed and who were not of role = subscriber.

  2. Does it delete the user who’s EOT has already passed? We need our code to change their role to ‘subscriber’, so the user is still in the database, but becomes an expired member.

Nile, on (1), the code is going to loop through every user to some extent, whether you do it by including or excluding roles (and whether via WP or directly within the database). The only question really is whether it’s faster to do it by including roles or by excluding them. But, when it comes to the deletion part, the list of users with the relevant roles is already stored as a variable, and so there’s no further call to the database for every user: only for those with the relevant roles.

If you think doing it by excluding roles would be better, you could use 'role__not_in' instead of 'role__in'. But that will then include administrators and the like, unless you add them into this array too.

On (2), yes, it deletes a member whose EOT has passed. If you want just to change the role, then you’d need something like $user->set_role( 'subscriber');

The real inefficiency of my code (if it works) is the fact that it will run on every page load because of the init hook. It should really be a cron function, but I don’t have time at the moment to work that bit out.

I have now tested some code. This seems to work:

<?php
function kts_delete_members_at_eot() {
	$args = array(
		'role__in' => array( 's2member_level1', 's2member_level2', 's2member_level3', 's2member_level4' ),
		'meta_key' => 'wp_s2member_auto_eot_time',
		'fields' => 'all_with_meta',
	);
	$users = get_users( $args );
	if ( empty( $users ) ) {
		return;
	}
	$time = time();

	foreach ( $users as $user ) {
		$eot = get_user_option( 'wp_s2member_auto_eot_time', $user->ID );
		if ( empty( $eot ) ) {
			return;
		}
		if ( $time > $eot ) {
			//wp_delete_user( $user->ID ); // deletes member
			$user->set_role( 'subscriber' ); // changes member's role
		}
	}
}
add_action( 'init', 'kts_delete_members_at_eot' );

You can add this as an mu-plugin. It will run on every page load. I will now experiment with getting it to run as a cronjob.

This should do it as a cronjob (now modified so as to work with any database prefix):

<?php
if ( !wp_next_scheduled( 'my_hourly_event' ) ) {
	wp_schedule_event( time(), 'hourly', 'my_hourly_event' );
}

function kts_delete_members_at_eot() {
	global $wpdb;
	$args = array(
		'role__in' => array( 's2member_level1', 's2member_level2', 's2member_level3', 's2member_level4' ),
		'meta_key' => $wpdb->prefix . 's2member_auto_eot_time',
		'fields' => 'all_with_meta',
	);
	$users = get_users( $args );
	if ( empty( $users ) ) {
		return;
	}
	$time = time();

	foreach ( $users as $user ) {
		$eot = get_user_option( $wpdb->prefix . 's2member_auto_eot_time', $user->ID );
		if ( empty( $eot ) ) {
			return;
		}
		if ( $time > $eot ) {
			//wp_delete_user( $user->ID );
			$user->set_role( 'subscriber' );
		}
	}
}
add_action( 'my_hourly_event', 'kts_delete_members_at_eot' );

@bask, It looks like the EOT cronjob sometimes just unsets itself. I don’t know why. But you might be able to get it working simply by resetting it again.

that’s not a new problem - I reported it a couple of times since over 4 years. It’s the only cronjob on my websites ever deleting itself - I guess there is some memory leak on that job and even if you setup php to jun jobs for a couple of hours and attribute a couple of GB to each job (my server to 128GB so I don’t care) it will disable itself at some point. S2member should either fix it or set up a second cron job that runs daily and checks if the cron job got deleted and then reset it…

Could I just run this by loading a PHP page through my web browser? I tried this, but it didn’t work:

<?php require_once("wp-load.php"); $args = array( 'role__in' => array( 's2member_level1', 's2member_level2', 's2member_level3', 's2member_level4' ), 'meta_key' => 'wp_s2member_auto_eot_time', 'fields' => 'all_with_meta', ); $users = get_users( $args ); if ( empty( $users ) ) { return; } $time = time(); foreach ( $users as $user ) { $eot = get_user_option( 'wp_s2member_auto_eot_time', $user->ID ); if ( empty( $eot ) ) { return; } if ( $time > $eot ) { //wp_delete_user( $user->ID ); // deletes member $user->set_role( 'subscriber' ); // changes member's role } } ?>

You could use my original code, and just add it to a different hook (e.g 'save_post') then perform the action associated with the hook (in this case, save a post) and then remove the code (or you could leave it there to be used in future).

Is possible to run it as a standalone PHP page that I can load through a web browser?

I tried the code this way, and I get no users found ($users is empty).

I tried it with save_post hook, but it did not expire the members. Is there some way to check if the code is working correctly?

Here’s the hook that I modified:
add_action( ‘init’, ‘kts_delete_members_at_eot’ );
changed to
add_action( ‘save_post’, ‘kts_delete_members_at_eot’ );

That code works for me. I think you have got something else happening on your site.

We’re stuck on Wordpress 4.2.2 because of customizations that were made – we can’t update. We also have an older version of s2Member. Do you think the code isn’t working for us because some of the functions aren’t supported by the older version of WP, or such?

Ah, that would be it. The code role__in was added in WP 4.4. You could try modifying this line:

'role__in' => array( 's2member_level1', 's2member_level2', 's2member_level3', 's2member_level4' ),

to this:

'role' => 's2member_level1',

and see if it works for members with that role. If so, you could then replace 1 with 2 and repeat to demote those at level 2 (or just repeat the code with 2 substituted for 1, and give the function a slightly different name).

Thanks, Tim, much appreciated.

I tried that, too, and it still didn’t find any users. I tried level 2 also, and no luck.

My client on this job has decided to stop pursuing this for the time being. They asked me to extend their gratitude for your assistance. Thank you!

1 Like