DEV Community

Cover image for WP schedule event role and permissions (capabilities)
Ingo Steinke
Ingo Steinke

Posted on • Updated on

WP schedule event role and permissions (capabilities)

If an event was scheduled by a user with a certain role and permissions, does that imply that the callback runs with the same role and permissions?

TL;DR

Does it? See below and jump to the conclusion ...

... or read about my current plugin (side) project and feel free to contribute.

I should rephrase my question to match my requirements:

As a developer, I want to schedule a WordPress function that runs with certain expected roles and permissions that have been set explicitly.

That's much better, and closer to a solution!

It could also implicitly inherit the caller's capabilities, or put otherwise, persist the scheduling user's role in the scheduled callback.

But why do I want to do something beyond the usual documented default use cases that seemed good enough for most users for the last twenty years? Why can't I find a plugin that offers the required functionality when there are tens of thousands of free plugins in the marketplace?

Use case for wp_schedule_event

When I had to add a contact form in WordPress recently, I used the popular combination of Contact Form 7, Akismet, and Flamingo for a persistent form message inbox with spam protection.

But webmasters have to check possible spam for false positives manually in wp-admin to be sure not to miss any legitimate inbound message.

Pending plugin review queue

There have been similar feature requests for Flamingo Spam Reports, but no option or plugin yet. Maybe someone submitted a plugin but it's there waiting for review together with > 1000 other new plugin submissions, so the estimated waiting time for a WordPress plugin review is > 100 days.

Screenshot: pending plugin review queue

The review is team is currently working on improving the review process, but there would probably be even more plugins in the queue if people wouldn't write their own code without even trying following the outdated coding style predating PHP standards recommendations. But we don't need to wait for the WordPress plugin marketplace to release open-source plugins!

Work in progress

Here is my work in progress on GitHub:

https://github.com/openmindculture/wp-contact-form-inbox-report-mailer-plugin

npm run build:zip creates a zip file that can be uploaded on the "install plugin" from a file page.

Screenshot: If you have a plugin in a .zip format, you may install or update it by uploading it here.

Pragmatic debugging

WordPress events like wp_schedule_event or wp_mail have some handy presets, but it's hard to test and write tested clean code given their limited output and debug options.

I split my code into (untested) units and made an admin page to display the inbox report that I want to send by mail, show the next scheduled event time, and trigger a mailing on each page request.

Everything seemed to work, but no mail was sent when triggered by the schedule callback function.

Inspecting the code I wrote, I found that I explicitly added a condition not to send an email if the report is empty.

if ($report && !empty($report))
Enter fullscreen mode Exit fullscreen mode

Maybe that's an anti-pattern even in production, but it definitely doesn't help debugging, so I'd rather send an empty mail where I can add further information.

Roles and permissions

I questioned another assumption: even if the event was scheduled by a user with permissions to view the inbox items, does that imply that the callback has the same role and permissions?

The scheduler is no real cron job, but

The action will trigger when someone visits your WordPress site if the scheduled time has passed.

So maybe it's that visitor's role running the job? If it's an external site visitor, of course they don't have permission to view my contact form inbox.

Unfortunately, the wp_schedule_event function reference isn't specific about this aspect yet. Comments and code examples aren't either.

The only helpful hint that I found after researching for about half an hour is an answer on wordpress.stackexchange.com:

You can do something like this

function your_cron_job(){
    $u = new WP_User(3);
    $u->set_role('editor');
}
Enter fullscreen mode Exit fullscreen mode

Something like this could mean set_role('administrator') or something less excessive like adding a specific capability for reading a very specific content type.

Conclusion

You tell me!

I'm sorry, but if you know, add your answer as a comment, if you don't, wait until I will. The code snippet above might looks promising and it implies that we need to specify a role explicitly to use a cron schedule to access data that is not available publicly in WordPress.

Meanwhile, I will try and finish my plugin. Maybe I will even submit it to the plugin directory. Hopefully they don't require coding like it's 2003 in their WordPress Coding Standards anymore. But my code is on GitHub, and so will be the latest release, ready to use on any WordPress site with a Flamingo inbox. Feel free to contribute!

Screenshot of report page, debug output and code snippet described in the article above.

Top comments (6)

Collapse
 
ingosteinke profile image
Ingo Steinke • Edited

After pausing my project for a while, there is still no answer.

Meanwhile, I did some more research and experiments. It seems that WordPress cron jobs have the lowest possible rights, just like a visitor who isn't logged in at all, if this StackOverflow answer from 2011 is still correct: WordPress cron is

running as no one, as get_current_user_id() returns 0

I tried setting permissions explicitly, but I suspect we shouldn't be able to elevate the current user's privileges as that would cause a security vulnerability. I'm still not even sure if that's the real problem, but it wouldn't harm to make the scheduled callback inherit the caller's permissions.

I didn't find authoritative statements or code examples in the WordPress Codex, so I proceeded to do some more trial and error and waited for one hour after uploading a new plugin version, as I'm too lazy to define my own interval < 1h. I already proved that my installation can send emails and generate an inbox report and send that by email, at least if it's done outside the scheduled callback below.

function openmindculture_cfirm_schedule() {
    require_once( plugin_dir_path( __FILE__ ) . 'generate-report.php' );
    $openmindculture_cfirm_report = '';

    try {
        $u = new WP_User(3);
        $u->set_role('administrator');
    } catch(Exception $ex) {
        $openmindculture_cfirm_report = 'Failed to set user role for mail report ' . $ex->getMessage();
    }

    try {
        // generating the report requires capabilities to access backend data
        $openmindculture_cfirm_report = openmindculture_generate_report ();
    } catch(Exception $ex) {
        $openmindculture_cfirm_report = 'Failed to generate report ' . $ex->getMessage();
    }

    if ( !$openmindculture_cfirm_report || empty( $openmindculture_cfirm_report ) ) {
        $openmindculture_cfirm_report = 'Nothing to report, but the mail interval works.';
    }
    require_once( plugin_dir_path( __FILE__ ) . 'send-report.php' );
    openmindculture_cfirm_send_report ( $openmindculture_cfirm_report );
}
Enter fullscreen mode Exit fullscreen mode

So no matter what's the problem, I should receive an email soon...

but I didn't!

Do I need to reschedule to make sure that the new callback code is executed?

Maybe! So I rescheduled and waited ... nothing!
I deactivated and uninstalled my plugin. Uploaded my update – which now displays its version number – activated it, checked the settings and report preview, waited again ... no mails! :-(

I added more diagnostics and made the preview page send an additional mail again.
This works:

This message was generated automatically by the Contact Form Inbox Report Mailer WordPress plugin 1.0.3 (current user ID: 1).
Next scheduled email report: 2023-10-09 12:13:40 (server time 2023-10-09 11:48:06)

I can see this message on my plugin page, the scheduled time stamp changes every hour, and I get an email when I open or reload the page. So far, so good.

But no mail gets sent. How can I even be sure that the cron job got called at all?
And why there so little detailed documentation about this topic?

Collapse
 
ingosteinke profile image
Ingo Steinke • Edited

I installed WP Crontrol to show more information about scheduled jobs and intervals. There are much more than I expected, and there is the one scheduled by my report mailer plugin.

cron jobs overview screenshot

And it's got an unexpected problem: it's got no action!

I must have made a mistake in my callback definition.

Revisiting the documentation

wp_schedule_event( int $timestamp, string $recurrence, string $hook, array $args = array(), bool $wp_error = false ): bool|WP_Error
Enter fullscreen mode Exit fullscreen mode

I see that my implicit assumption about the third parameter was wrong.

$hook string Required Action hook to execute when the event is run

makes it quite clear that wp_schedule_event doesn't expect a function but an action hook. That's basically an additional wrapper around the existing function. I could have copied the first user provided code example in the comments.

if (! wp_next_scheduled ( 'my_hourly_event', $args )) {
    wp_schedule_event( time(), 'hourly', 'my_hourly_event', $args );
}

add_action( 'my_hourly_event', 'do_this_hourly', 10, 2 );
function do_this_hourly($args_1, $args_2 ) {
    // do something every hour
}
Enter fullscreen mode Exit fullscreen mode

We can even pass arguments (and if we don't, we need to change the 2 to 0 in add_action).

But we still can't pass user sessions and their permissions.

Collapse
 
ingosteinke profile image
Ingo Steinke • Edited

I discovered that we can fire a single scheduled event immediately from the WP Crontrol list view. Although the callback action evaluates to the existing function name, nothing happens. No empty mail, no echo output. So there is still something wrong with my schedule or callback.

Meanwhile, I had another idea.

If I had access to the SQL database, I could write a simple query like

SELECT * FROM wp_posts WHERE post_type = 'flamingo_inbound' AND post_date > '2023-10-09 08:07'

That would also assume that I know the current table prefix and the connection details.

But $wpdb knows.

global $wpdb;
$results = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}posts WHERE post_type = 'flamingo_inbound'", OBJECT );
Enter fullscreen mode Exit fullscreen mode

Of course, it wouldn't let me retrieve arbitrary data using a custom SQL statement without adding a capability constraint.

Or would it?

Collapse
 
ingosteinke profile image
Ingo Steinke

Proceeding to talk to myself, hoping for a solution to set WP schedule event role and permissions (capabilities):

  • [ ] WordPress custom user role for cron schedule callback

So we still can't pass user sessions and their permissions when scheduling our cron job.
And I still don't get how WordPress offers no best practice how to handle this requirement safely.

Allowing everyone access to the inbox items is no solution. Even if there are no public pages exposing this data, someone might still try and access it via an API call.

Running all cron schedule callbacks with administrator rights is no good solution either. We shouldn't even run this very specific job as an administrator.

Using a real cron job with file access on a UNIX system, I would create a unique new user and add it to a group of users. Then I would let this user group own the required file and set group permissions accordingly.

There are equivalent concepts in WordPress. User roles correspond to user groups, permissions correspond to their capabilities. As an administrator, which is roughly equivalent to an Administrator on Windows, not to a UNIX root, we should have sufficient capabilities to set permissions accordingly. But if the schedule callback runs as nobody, changing its role to anybody would be a privilege escalation. Do we have any chance to run a callback as somebody under any circumstances at all?

Thread Thread
 
ingosteinke profile image
Ingo Steinke • Edited

Several days later, suddenly, some mails in my inbox!

nothing to report, but the mail interval works

screenshot of the above mail subject

Maybe "hourly" means "daily" or "weekly" on a WordPress website with few visitors? Or maybe frontend caching prevents triggering the wp cron job schedule?

Some people seemed to have caching vs. wp cron issues.

But in my case, the "next scheduled" time changed and still it seemed that my cron callback was not executed.

Thread Thread
 
ingosteinke profile image
Ingo Steinke

After talking to myself in support issues, like I often do, logging erratic plugin behavior, and nobody seems to be able to help, I will test alternative solutions:

  • switch caching plugins to verify if it is W3TC related or if the same problems occurs with WP Rocket or other alternatives,
  • use an alternative to WP_Cron which seems like an unrealiable hack anyway.