Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ parameters:
- tests/phpstan/stubs.php
dynamicConstantNames:
- CRONTROL_DISALLOW_PHP_EVENTS
- CRONTROL_DISALLOW_URL_EVENTS
- DISABLE_WP_CRON
WPCompat:
pluginFile: wp-crontrol.php
Expand Down
90 changes: 74 additions & 16 deletions src/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ function action_handle_posts() {
if ( PHPCronEvent::HOOK_NAME === $cr->hookname ) {
wp_die( esc_html__( 'You are not allowed to add new PHP cron events.', 'wp-crontrol' ), 403 );
}
if ( URLCronEvent::HOOK_NAME === $cr->hookname ) {
wp_die( esc_html__( 'You are not allowed to add new URL cron events.', 'wp-crontrol' ), 403 );
}
$args = json_decode( $cr->args, true );

if ( empty( $args ) || ! is_array( $args ) ) {
Expand Down Expand Up @@ -217,8 +220,8 @@ function action_handle_posts() {
exit;

} elseif ( isset( $_POST['crontrol_action'] ) && ( 'new_url_cron' === $_POST['crontrol_action'] ) ) {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'You are not allowed to add new cron events.', 'wp-crontrol' ), 403 );
if ( ! current_user_can_manage_url_cron_events() ) {
wp_die( esc_html__( 'You are not allowed to add new URL cron events.', 'wp-crontrol' ), 403 );
}
check_admin_referer( 'crontrol-new-cron' );

Expand Down Expand Up @@ -262,7 +265,7 @@ function action_handle_posts() {
return $event;
}, 99 );

$added = Event\add( $next_run_local, $cr->schedule, 'crontrol_url_cron_job', $args );
$added = Event\add( $next_run_local, $cr->schedule, URLCronEvent::HOOK_NAME, $args );

$hookname = ( ! empty( $cr->eventname ) ) ? $cr->eventname : __( 'URL Cron', 'wp-crontrol' );
$redirect = array(
Expand Down Expand Up @@ -325,7 +328,7 @@ function action_handle_posts() {
return $event;
}, 99 );

$added = Event\add( $next_run_local, $cr->schedule, 'crontrol_cron_job', $args );
$added = Event\add( $next_run_local, $cr->schedule, PHPCronEvent::HOOK_NAME, $args );

$hookname = ( ! empty( $cr->eventname ) ) ? $cr->eventname : __( 'PHP Cron', 'wp-crontrol' );
$redirect = array(
Expand Down Expand Up @@ -355,6 +358,10 @@ function action_handle_posts() {
wp_die( esc_html__( 'You are not allowed to edit PHP cron events.', 'wp-crontrol' ), 403 );
}

if ( URLCronEvent::HOOK_NAME === $cr->hookname && ! current_user_can_manage_url_cron_events() ) {
wp_die( esc_html__( 'You are not allowed to edit URL cron events.', 'wp-crontrol' ), 403 );
}

$args = json_decode( $cr->args, true );

if ( empty( $args ) || ! is_array( $args ) ) {
Expand Down Expand Up @@ -499,7 +506,7 @@ function action_handle_posts() {
return $event;
}, 99 );

$added = Event\add( $next_run_local, $cr->schedule, 'crontrol_url_cron_job', $args );
$added = Event\add( $next_run_local, $cr->schedule, URLCronEvent::HOOK_NAME, $args );

if ( is_wp_error( $added ) ) {
set_message( $added->get_error_message() );
Expand Down Expand Up @@ -581,7 +588,7 @@ function action_handle_posts() {
return $event;
}, 99 );

$added = Event\add( $next_run_local, $cr->schedule, 'crontrol_cron_job', $args );
$added = Event\add( $next_run_local, $cr->schedule, PHPCronEvent::HOOK_NAME, $args );

if ( is_wp_error( $added ) ) {
set_message( $added->get_error_message() );
Expand Down Expand Up @@ -721,10 +728,13 @@ function action_handle_posts() {
$deleted = false;
check_admin_referer( "crontrol-delete-hook_{$hook}" );

// Sanity check
// Sanity checks
if ( PHPCronEvent::HOOK_NAME === $hook ) {
wp_die( esc_html__( 'You are not allowed to delete PHP cron events.', 'wp-crontrol' ), 403 );
}
if ( URLCronEvent::HOOK_NAME === $hook ) {
wp_die( esc_html__( 'You are not allowed to delete URL cron events.', 'wp-crontrol' ), 403 );
}

$deleted = wp_unschedule_hook( $hook, true );

Expand Down Expand Up @@ -774,6 +784,10 @@ function action_handle_posts() {
wp_die( esc_html__( 'You are not allowed to run cron events.', 'wp-crontrol' ), 403 );
}

if ( ( URLCronEvent::HOOK_NAME === $hook ) && ! url_cron_events_enabled() ) {
wp_die( esc_html__( 'You are not allowed to run cron events.', 'wp-crontrol' ), 403 );
}

$ran = Event\run( $hook, $sig );

$redirect = array(
Expand Down Expand Up @@ -855,7 +869,7 @@ function action_handle_posts() {

$hook = wp_unslash( $_GET['crontrol_id'] );

if ( PHPCronEvent::HOOK_NAME === $hook ) {
if ( ( PHPCronEvent::HOOK_NAME === $hook ) || ( URLCronEvent::HOOK_NAME === $hook ) ) {
wp_die( esc_html__( 'You are not allowed to pause or resume cron events.', 'wp-crontrol' ), 403 );
}

Expand Down Expand Up @@ -939,13 +953,15 @@ function action_handle_posts() {

if ( $event->is_php_cron() ) {
$args = __( 'PHP Code', 'wp-crontrol' );
} elseif ( $event->is_url_cron() ) {
$args = $event->args[0]['method'] . ' ' . $event->args[0]['url'];
} elseif ( empty( $event->args ) ) {
$args = '';
} else {
$args = \Crontrol\json_output( $event->args, false );
}

if ( $event->is_php_cron() ) {
if ( ( PHPCronEvent::HOOK_NAME === $event->hook ) || ( URLCronEvent::HOOK_NAME === $event->hook ) ) {
$action = 'WP Crontrol';
} else {
$callbacks = array();
Expand Down Expand Up @@ -1631,7 +1647,15 @@ function show_cron_form( $editing ) {
}

$can_manage_php = current_user_can_manage_php_cron_events();
$allowed = ( ! $is_editing_php || $can_manage_php );
$can_manage_url = current_user_can_manage_url_cron_events();

if ( $is_editing_php ) {
$allowed = $can_manage_php;
} elseif ( $is_editing_url ) {
$allowed = $can_manage_url;
} else {
$allowed = true;
}
?>
<div id="crontrol_form" class="wrap narrow">
<?php
Expand Down Expand Up @@ -1677,6 +1701,8 @@ function show_cron_form( $editing ) {
'<input type="hidden" name="crontrol_action" value="%s"/>',
esc_attr( $action )
);
} elseif ( ! $can_manage_php && ! $can_manage_url ) {
echo '<input type="hidden" name="crontrol_action" value="new_cron"/>';
} else {
?>
<tr class="hide-if-no-js">
Expand All @@ -1694,12 +1720,14 @@ function show_cron_form( $editing ) {
<?php esc_html_e( 'Standard cron event', 'wp-crontrol' ); ?>
</label>
</p>
<p>
<label>
<input type="radio" name="crontrol_action" value="new_url_cron">
<?php esc_html_e( 'URL cron event', 'wp-crontrol' ); ?>
</label>
</p>
<?php if ( $can_manage_url ) { ?>
<p>
<label>
<input type="radio" name="crontrol_action" value="new_url_cron">
<?php esc_html_e( 'URL cron event', 'wp-crontrol' ); ?>
</label>
</p>
<?php } ?>
<?php if ( $can_manage_php ) { ?>
<p>
<label>
Expand Down Expand Up @@ -2625,6 +2653,11 @@ function json_output( $input, $pretty = true ) {
/**
* Fetches the URL in a URL cron event using the HTTP API.
*
* If the `CRONTROL_DISALLOW_URL_EVENTS` constant is defined and set to `true` then URL cron events will be disabled
* completely. Any existing URL cron events will remain in place but their URL will not be fetched, and no URL cron
* events can be added, edited, or manually run. Users with permission to edit URL cron events will still be able to
* delete these events.
*
* The URL that's saved in a URL cron event is protected with an integrity check which prevents it from being fetched
* if the URL is tampered with.
*
Expand All @@ -2649,6 +2682,10 @@ function json_output( $input, $pretty = true ) {
* } $args
*/
function action_url_cron_event( array $args ): void {
if ( ! url_cron_events_enabled() ) {
return;
}

list(
'url' => $url,
'method' => $method,
Expand Down Expand Up @@ -2823,3 +2860,24 @@ function php_cron_events_enabled(): bool {
function current_user_can_manage_php_cron_events(): bool {
return ( php_cron_events_enabled() && current_user_can( 'edit_files' ) );
}

/**
* Returns whether URL cron events are enabled.
*
* The URL cron event functionality can be disabled by defining the `CRONTROL_DISALLOW_URL_EVENTS` constant and setting
* its value to `true`. This constant can be defined in the `wp-config.php` file.
*/
function url_cron_events_enabled(): bool {
if ( defined( 'CRONTROL_DISALLOW_URL_EVENTS' ) && CRONTROL_DISALLOW_URL_EVENTS ) {
return false;
}

return true;
}

/**
* Returns whether URL cron events are enabled and can be managed by the current user.
*/
function current_user_can_manage_url_cron_events(): bool {
return ( url_cron_events_enabled() && current_user_can( 'manage_options' ) );
}
67 changes: 59 additions & 8 deletions src/event-list-table.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use DateTimeImmutable;

use function Crontrol\php_cron_events_enabled;
use function Crontrol\url_cron_events_enabled;

require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';

Expand All @@ -29,13 +30,27 @@ class Table extends \WP_List_Table {
*/
protected static $can_manage_php_crons;

/**
* Whether the current user has the capability to create or edit URL cron events.
*
* @var bool Whether the user can create or edit URL cron events.
*/
protected static $can_manage_url_crons;

/**
* Whether PHP cron events are allowed.
*
* @var bool Whether PHP cron events are allowed.
*/
protected static $php_crons_enabled;

/**
* Whether URL cron events are allowed.
*
* @var bool Whether URL cron events are allowed.
*/
protected static $url_crons_enabled;

/**
* Array of the count of each hook.
*
Expand Down Expand Up @@ -69,7 +84,9 @@ public function __construct() {
*/
public function prepare_items() {
self::$can_manage_php_crons = current_user_can( 'edit_files' );
self::$can_manage_url_crons = current_user_can( 'manage_options' );
self::$php_crons_enabled = php_cron_events_enabled();
self::$url_crons_enabled = url_cron_events_enabled();
self::$count_by_hook = count_by_hook();

$events = get();
Expand Down Expand Up @@ -370,7 +387,11 @@ public function single_row( $event ) {
$classes[] = 'crontrol-paused';
}

if ( $event->is_php_cron() && ! php_cron_events_enabled() ) {
if ( $event->is_php_cron() && ! self::$php_crons_enabled ) {
$classes[] = 'crontrol-inactive';
}

if ( $event->is_url_cron() && ! self::$url_crons_enabled ) {
$classes[] = 'crontrol-inactive';
}

Expand Down Expand Up @@ -416,8 +437,17 @@ protected function handle_row_actions( $event, $column_name, $primary ) {
return $this->row_actions( $links );
}

// PHP cron events can be edited as long as they are enabled and the user has permission.
$can_edit = ! $event->is_php_cron() || ( self::$can_manage_php_crons && self::$php_crons_enabled );
if ( $event->is_php_cron() ) {
// PHP cron events can be edited as long as they are enabled and the user has permission.
$can_edit = ( self::$can_manage_php_crons && self::$php_crons_enabled );
} elseif ( $event->is_url_cron() ) {
// URL cron events can be edited as long as they are enabled and the user has permission.
$can_edit = ( self::$can_manage_url_crons && self::$url_crons_enabled );
} else {
// All other cron events can be edited.
$can_edit = true;
}

$has_error = $event->has_error();

if ( $can_edit ) {
Expand All @@ -443,8 +473,16 @@ protected function handle_row_actions( $event, $column_name, $primary ) {
);
}

// PHP cron events can be run as long as they are enabled.
$can_run = ( ! $event->is_php_cron() || self::$php_crons_enabled ) && ! $has_error;
if ( $event->is_php_cron() ) {
// PHP cron events can be run as long as they are enabled.
$can_run = ( self::$php_crons_enabled && ! $has_error );
} elseif ( $event->is_url_cron() ) {
// URL cron events can be run as long as they are enabled.
$can_run = ( self::$url_crons_enabled && ! $has_error );
} else {
// All other cron events can be run.
$can_run = ( ! $has_error );
}

if ( ! $event->is_paused() && $can_run ) {
$link = array(
Expand Down Expand Up @@ -595,7 +633,7 @@ protected function column_crontrol_hook( Event $event ): string {
$output = esc_html__( 'PHP cron event', 'wp-crontrol' );
}

if ( ! php_cron_events_enabled() ) {
if ( ! self::$php_crons_enabled ) {
$output .= sprintf(
' &mdash; <strong class="status-crontrol-inactive post-state"><span class="dashicons dashicons-controls-pause" aria-hidden="true"></span> %s</strong>',
/* translators: State of a cron event, adjective */
Expand Down Expand Up @@ -638,7 +676,13 @@ protected function column_crontrol_hook( Event $event ): string {
$output = esc_html__( 'URL cron event', 'wp-crontrol' );
}

if ( $event->integrity_failed() ) {
if ( ! self::$url_crons_enabled ) {
$output .= sprintf(
' &mdash; <strong class="status-crontrol-inactive post-state"><span class="dashicons dashicons-controls-pause" aria-hidden="true"></span> %s</strong>',
/* translators: State of a cron event, adjective */
esc_html__( 'Inactive', 'wp-crontrol' )
);
} elseif ( $event->integrity_failed() ) {
$output .= sprintf(
' &mdash; <strong class="status-crontrol-inactive post-state"><span class="dashicons dashicons-warning" aria-hidden="true"></span> %s</strong>',
esc_html__( 'Needs checking', 'wp-crontrol' )
Expand Down Expand Up @@ -682,13 +726,20 @@ protected function column_crontrol_hook( Event $event ): string {
* @return string The cell output.
*/
protected function column_crontrol_actions( Event $event ): string {
if ( $event->is_php_cron() && ! php_cron_events_enabled() ) {
if ( $event->is_php_cron() && ! self::$php_crons_enabled ) {
return sprintf(
'<span class="status-crontrol-warning"><span class="dashicons dashicons-warning" aria-hidden="true"></span> %s</span>',
esc_html__( 'PHP cron events are disabled.', 'wp-crontrol' )
);
}

if ( $event->is_url_cron() && ! self::$url_crons_enabled ) {
return sprintf(
'<span class="status-crontrol-warning"><span class="dashicons dashicons-warning" aria-hidden="true"></span> %s</span>',
esc_html__( 'URL cron events are disabled.', 'wp-crontrol' )
);
}

$hook_callbacks = $event->get_callbacks();

if ( $event->is_crontrol_event() ) {
Expand Down