From ffed31a909699dfb813d721109219f25778a2846 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 13 Mar 2024 22:45:41 +0000 Subject: [PATCH 01/17] Introduce a new cron event type for fetching a URL. --- css/wp-crontrol.css | 4 + docs/docs/deactivation.md | 4 +- docs/docs/php-cron-events.md | 8 +- js/wp-crontrol.js | 21 ++- readme.md | 8 +- src/bootstrap.php | 261 +++++++++++++++++++++++++++++- src/request.php | 14 ++ tests/acceptance/AddEventCest.php | 23 ++- 8 files changed, 314 insertions(+), 29 deletions(-) diff --git a/css/wp-crontrol.css b/css/wp-crontrol.css index b330e25..12dad6e 100644 --- a/css/wp-crontrol.css +++ b/css/wp-crontrol.css @@ -96,7 +96,11 @@ table.wp-list-table { width: 100px; } +.crontrol-edit-event-url .crontrol-event-standard, +.crontrol-edit-event-url .crontrol-event-php, +.crontrol-edit-event-standard .crontrol-event-url, .crontrol-edit-event-standard .crontrol-event-php, +.crontrol-edit-event-php .crontrol-event-url, .crontrol-edit-event-php .crontrol-event-standard { display: none; } diff --git a/docs/docs/deactivation.md b/docs/docs/deactivation.md index 741870b..dab74fb 100644 --- a/docs/docs/deactivation.md +++ b/docs/docs/deactivation.md @@ -6,9 +6,9 @@ If you deactivate or delete the WP Crontrol plugin, your existing cron events wi Any cron hooks that you've paused through WP Crontrol will be resumed because the paused functionality is provided by WP Crontrol. If you reactivate WP Crontrol, they will become paused again. -## PHP cron events +## URL cron events and PHP cron events -If you've created a PHP cron event with WP Crontrol, these events will remain in place after you deactivate WP Crontrol but they will _cease to operate_ because these events are processed by WP Crontrol. If you reactivate WP Crontrol, they will resume operating as normal. +If you've created a URL cron event or PHP cron event with WP Crontrol, these events will remain in place after you deactivate WP Crontrol but they will _cease to operate_ because these events are processed by WP Crontrol. If you reactivate WP Crontrol, they will resume operating as normal. ## Custom schedules diff --git a/docs/docs/php-cron-events.md b/docs/docs/php-cron-events.md index f450cc9..872e86e 100644 --- a/docs/docs/php-cron-events.md +++ b/docs/docs/php-cron-events.md @@ -18,7 +18,7 @@ If you wish to prevent PHP cron events from being added or edited on your site t ## How do I create a new PHP cron event? -In the Tools → Cron Events admin panel, click on "Add New". In the form that appears, select "PHP cron event" and enter the schedule and next run time. In the "PHP Code" area, enter the PHP code that should be run when your cron event is executed. Don't include the PHP opening tag (` { const customDateElement = document.getElementById( 'crontrol_next_run_date_local_custom_date' ); const customTimeElement = document.getElementById( 'crontrol_next_run_date_local_custom_time' ); const newCronElement = document.querySelector( 'input[value="new_cron"]' ); + const newURLCronElement = document.querySelector( 'input[value="new_url_cron"]' ); const newPHPCronElement = document.querySelector( 'input[value="new_php_cron"]' ); const hookCodeElement = document.getElementById( 'crontrol_hookcode' ); const hookNameElement = document.getElementById( 'crontrol_hookname' ); @@ -18,14 +19,24 @@ document.addEventListener( 'DOMContentLoaded', () => { customDateElement && customDateElement.addEventListener( 'change', checkCustom ); customTimeElement && customTimeElement.addEventListener( 'change', checkCustom ); + newCronElement.addEventListener( 'click', () => { + editEventElement.classList.remove( 'crontrol-edit-event-url' ); + editEventElement.classList.remove( 'crontrol-edit-event-php' ); + editEventElement.classList.add( 'crontrol-edit-event-standard' ); + hookNameElement.setAttribute( 'required', true ); + } ); + + newURLCronElement.addEventListener( 'click', () => { + editEventElement.classList.remove( 'crontrol-edit-event-standard' ); + editEventElement.classList.remove( 'crontrol-edit-event-php' ); + editEventElement.classList.add( 'crontrol-edit-event-url' ); + hookNameElement.removeAttribute( 'required' ); + } ); + if ( newPHPCronElement ) { - newCronElement.addEventListener( 'click', () => { - editEventElement.classList.remove( 'crontrol-edit-event-php' ); - editEventElement.classList.add( 'crontrol-edit-event-standard' ); - hookNameElement.setAttribute( 'required', true ); - } ); newPHPCronElement.addEventListener( 'click', () => { editEventElement.classList.remove( 'crontrol-edit-event-standard' ); + editEventElement.classList.remove( 'crontrol-edit-event-url' ); editEventElement.classList.add( 'crontrol-edit-event-php' ); hookNameElement.removeAttribute( 'required' ); if ( ! hookCodeElement.classList.contains( 'crontrol-editor-initialized' ) ) { diff --git a/readme.md b/readme.md index 1d5d935..3229704 100644 --- a/readme.md +++ b/readme.md @@ -93,13 +93,7 @@ You can change the time and recurrence of a cron event by clicking the "Edit" li ### How can I create a cron event that requests a URL? -From the Tools → Cron Events → Add New screen, create a PHP cron event that includes PHP that fetches the URL using the WordPress HTTP API. For example: - -~~~php -wp_remote_get( 'http://example.com' ); -~~~ - -[You can read all about the features and security of PHP cron events on the WP Crontrol website](https://wp-crontrol.com/docs/php-cron-events/). +From the Tools → Cron Events → Add New screen, select the "Request a URL" option under the "Event Type" list. Fill out the rest of the details as required and press the "Add Event" button. ### Why do changes that I make to some cron events not get saved? diff --git a/src/bootstrap.php b/src/bootstrap.php index 197604a..405ff68 100644 --- a/src/bootstrap.php +++ b/src/bootstrap.php @@ -8,6 +8,7 @@ use Crontrol\Event\Table; use stdClass; use WP_Error; +use Exception; use function Crontrol\Event\check_integrity; @@ -35,6 +36,7 @@ function init_hooks() { add_filter( 'cron_schedules', __NAMESPACE__ . '\filter_cron_schedules' ); add_action( 'crontrol_cron_job', __NAMESPACE__ . '\action_php_cron_event' ); + add_action( 'crontrol_url_cron_job', __NAMESPACE__ . '\action_url_cron_event', 10, 2 ); add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\enqueue_assets' ); add_action( 'crontrol/tab-header', __NAMESPACE__ . '\show_cron_status', 20 ); add_action( 'activated_plugin', __NAMESPACE__ . '\flush_status_cache', 10, 0 ); @@ -188,6 +190,64 @@ function action_handle_posts() { wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) ); 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' ), 401 ); + } + check_admin_referer( 'crontrol-new-cron' ); + + $cr = $request->init( wp_unslash( $_POST ) ); + + if ( 'crontrol_cron_job' === $cr->hookname ) { + wp_die( esc_html__( 'You are not allowed to add new PHP cron events.', 'wp-crontrol' ), 401 ); + } + + $next_run_local = ( 'custom' === $cr->next_run_date_local ) ? $cr->next_run_date_local_custom_date . ' ' . $cr->next_run_date_local_custom_time : $cr->next_run_date_local; + $args = array( + 'url' => $cr->url, + 'method' => $cr->method, + ); + + add_filter( 'schedule_event', function( $event ) { + if ( ! $event ) { + return $event; + } + + /** + * Fires after a new URL cron event is added. + * + * @param stdClass $event { + * An object containing the event's data. + * + * @type string $hook Action hook to execute when the event is run. + * @type int $timestamp Unix timestamp (UTC) for when to next run the event. + * @type string|false $schedule How often the event should subsequently recur. + * @type mixed[] $args Array containing each separate argument to pass to the hook's callback function. + * @type int $interval The interval time in seconds for the schedule. Only present for recurring events. + * } + */ + do_action( 'crontrol/added_new_url_event', $event ); + + return $event; + }, 99 ); + + $added = Event\add( $next_run_local, $cr->schedule, 'crontrol_url_cron_job', $args ); + + $hookname = ( ! empty( $cr->eventname ) ) ? $cr->eventname : __( 'URL Cron', 'wp-crontrol' ); + $redirect = array( + 'page' => 'crontrol_admin_manage_page', + 'crontrol_message' => '5', + 'crontrol_name' => rawurlencode( $hookname ), + ); + + if ( is_wp_error( $added ) ) { + set_message( $added->get_error_message() ); + $redirect['crontrol_message'] = 'error'; + } + + wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) ); + exit; + } elseif ( isset( $_POST['crontrol_action'] ) && ( 'new_php_cron' === $_POST['crontrol_action'] ) ) { if ( ! current_user_can( 'edit_files' ) ) { wp_die( esc_html__( 'You are not allowed to add new PHP cron events.', 'wp-crontrol' ), 401 ); @@ -338,6 +398,93 @@ function action_handle_posts() { wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) ); exit; + } elseif ( isset( $_POST['crontrol_action'] ) && ( 'edit_url_cron' === $_POST['crontrol_action'] ) ) { + if ( ! current_user_can( 'manage_options' ) ) { + wp_die( esc_html__( 'You are not allowed to edit cron events.', 'wp-crontrol' ), 401 ); + } + + $cr = $request->init( wp_unslash( $_POST ) ); + + check_admin_referer( "crontrol-edit-cron_{$cr->original_hookname}_{$cr->original_sig}_{$cr->original_next_run_utc}" ); + + $args = array( + 'url' => $cr->url, + 'method' => $cr->method, + ); + $hookname = ( ! empty( $cr->eventname ) ) ? $cr->eventname : __( 'URL Cron', 'wp-crontrol' ); + $redirect = array( + 'page' => 'crontrol_admin_manage_page', + 'crontrol_message' => '4', + 'crontrol_name' => rawurlencode( $hookname ), + ); + + $original = Event\get_single( $cr->original_hookname, $cr->original_sig, $cr->original_next_run_utc ); + + if ( is_wp_error( $original ) ) { + set_message( $original->get_error_message() ); + $redirect['crontrol_message'] = 'error'; + wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) ); + exit; + } + + $deleted = Event\delete( $cr->original_hookname, $cr->original_sig, $cr->original_next_run_utc ); + + if ( is_wp_error( $deleted ) ) { + set_message( $deleted->get_error_message() ); + $redirect['crontrol_message'] = 'error'; + wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) ); + exit; + } + + $next_run_local = ( 'custom' === $cr->next_run_date_local ) ? $cr->next_run_date_local_custom_date . ' ' . $cr->next_run_date_local_custom_time : $cr->next_run_date_local; + + /** + * Modifies an event before it is scheduled. + * + * @param stdClass|false $event An object containing the new event's data, or boolean false. + */ + add_filter( 'schedule_event', function( $event ) use ( $original ) { + if ( ! $event ) { + return $event; + } + + /** + * Fires after a URL cron event is edited. + * + * @param stdClass $event { + * An object containing the new event's data. + * + * @type string $hook Action hook to execute when the event is run. + * @type int $timestamp Unix timestamp (UTC) for when to next run the event. + * @type string|false $schedule How often the event should subsequently recur. + * @type mixed[] $args Array containing each separate argument to pass to the hook's callback function. + * @type int $interval The interval time in seconds for the schedule. Only present for recurring events. + * } + * @param stdClass $original { + * An object containing the original event's data. + * + * @type string $hook Action hook to execute when the event is run. + * @type int $timestamp Unix timestamp (UTC) for when to next run the event. + * @type string|false $schedule How often the event should subsequently recur. + * @type mixed[] $args Array containing each separate argument to pass to the hook's callback function. + * @type int $interval The interval time in seconds for the schedule. Only present for recurring events. + * } + */ + do_action( 'crontrol/edited_url_event', $event, $original ); + + return $event; + }, 99 ); + + $added = Event\add( $next_run_local, $cr->schedule, 'crontrol_url_cron_job', $args ); + + if ( is_wp_error( $added ) ) { + set_message( $added->get_error_message() ); + $redirect['crontrol_message'] = 'error'; + } + + wp_safe_redirect( add_query_arg( $redirect, admin_url( 'tools.php' ) ) ); + exit; + } elseif ( isset( $_POST['crontrol_action'] ) && ( 'edit_php_cron' === $_POST['crontrol_action'] ) ) { if ( ! current_user_can( 'edit_files' ) ) { wp_die( esc_html__( 'You are not allowed to edit PHP cron events.', 'wp-crontrol' ), 401 ); @@ -1342,6 +1489,7 @@ function show_cron_form( $editing ) { } $is_editing_php = ( $existing && 'crontrol_cron_job' === $existing['hookname'] ); + $is_editing_url = ( $existing && 'crontrol_url_cron_job' === $existing['hookname'] ); if ( $is_editing_php ) { $helper_text = esc_html__( 'Cron events trigger actions in your code. Enter the schedule of the event, as well as the PHP code to execute when the action is triggered.', 'wp-crontrol' ); @@ -1424,8 +1572,16 @@ function show_cron_form( $editing ) { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped $helper_text ); + + if ( $is_editing_php ) { + $cron_type = 'php'; + } elseif ( $is_editing_url ) { + $cron_type = 'url'; + } else { + $cron_type = 'standard'; + } ?> -
+ ', esc_attr( $action ) ); - } elseif ( $can_add_php ) { + } else { ?> @@ -1455,6 +1618,12 @@ function show_cron_form( $editing ) {

+

+ +

+ +

+

+ +

+ + + + + + + + + + + + @@ -1517,7 +1726,7 @@ function show_cron_form( $editing ) { @@ -2249,6 +2458,42 @@ function json_output( $input, $pretty = true ) { return $output; } +/** + * Fetches the URL in a cron event using the HTTP API. + * + * @throws Exception If the request fails. + * + * @param string $url The URL to fetch. + * @param string $method The HTTP method to use. + */ +function action_url_cron_event( $url, $method ): void { + $args = array( + 'timeout' => 30, + 'method' => $method, + ); + $response = wp_remote_request( $url, $args ); + + if ( is_wp_error( $response ) ) { + throw new Exception( sprintf( + 'WP Crontrol: Failed to fetch URL %s: %s', + $url, + $response->get_error_message() + ) ); + } + + $code = wp_remote_retrieve_response_code( $response ); + $message = wp_remote_retrieve_response_message( $response ); + + if ( $code < 200 || $code >= 300 ) { + throw new Exception( sprintf( + 'WP Crontrol: Unexpected response code for URL %s: HTTP %s %s', + $url, + $code, + $message + ) ); + } +} + /** * Evaluates the code in a PHP cron event using eval. * diff --git a/src/request.php b/src/request.php index d7d71ab..36dea80 100644 --- a/src/request.php +++ b/src/request.php @@ -66,6 +66,20 @@ class Request { */ public $eventname = ''; + /** + * Description. + * + * @var string + */ + public $url = ''; + + /** + * Description. + * + * @var string + */ + public $method = ''; + /** * Description. * diff --git a/tests/acceptance/AddEventCest.php b/tests/acceptance/AddEventCest.php index ab6c2c6..9a8e7c6 100644 --- a/tests/acceptance/AddEventCest.php +++ b/tests/acceptance/AddEventCest.php @@ -27,12 +27,33 @@ public function AddingANewEvent( AcceptanceTester $I ) { $I->seeAdminSuccessNotice( 'Created the cron event my_hookname.' ); } + public function AddingANewURLEvent( AcceptanceTester $I ) { + $I->amOnCronEventListingPage(); + $I->click( 'Add New', '#wpbody' ); + $I->dontSee( 'PHP Code' ); + $I->dontSee( 'URL' ); + $I->dontSee( 'HTTP Method' ); + $I->selectOption( 'input[name="crontrol_action"]', 'Request a URL' ); + $I->dontSee( 'PHP Code' ); + $I->see( 'URL' ); + $I->see( 'HTTP Method' ); + $I->fillField( 'URL', 'https://example.org/' ); + $I->click( 'Add Event' ); + $I->see( 'Cron Events', 'h1' ); + $I->seeAdminSuccessNotice( 'Created the cron event URL Cron.' ); + $I->see( 'https://example.org/' ); + } + public function AddingANewPHPEvent( AcceptanceTester $I ) { $I->amOnCronEventListingPage(); $I->click( 'Add New Cron Event', '#wpbody' ); $I->dontSee( 'PHP Code' ); - $I->selectOption( 'input[name="crontrol_action"]', 'PHP cron event' ); + $I->dontSee( 'URL' ); + $I->dontSee( 'HTTP Method' ); + $I->selectOption( 'input[name="crontrol_action"]', 'Execute PHP' ); $I->see( 'PHP Code' ); + $I->dontSee( 'URL' ); + $I->dontSee( 'HTTP Method' ); $I->fillPHPEditorField( 'amazing();' ); $I->click( 'Add Event' ); $I->see( 'Cron Events', 'h1' ); From 734a729dd8d9ac98bb08307037f90f60c56ca3a4 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 12 Jun 2024 23:19:47 +0100 Subject: [PATCH 02/17] Let's not rename this. --- docs/docs/php-cron-events.md | 2 +- tests/acceptance/AddEventCest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/php-cron-events.md b/docs/docs/php-cron-events.md index 872e86e..775c703 100644 --- a/docs/docs/php-cron-events.md +++ b/docs/docs/php-cron-events.md @@ -18,7 +18,7 @@ If you wish to prevent PHP cron events from being added or edited on your site t ## How do I create a new PHP cron event? -In the Tools → Cron Events admin panel, click on "Add New". In the form that appears, select the "Execute PHP" option under the "Event Type" list and enter the schedule and next run time. In the "Hook code" area, enter the PHP code that should be run when your cron event is executed. Don't include the PHP opening tag (`dontSee( 'PHP Code' ); $I->dontSee( 'URL' ); $I->dontSee( 'HTTP Method' ); - $I->selectOption( 'input[name="crontrol_action"]', 'Execute PHP' ); + $I->selectOption( 'input[name="crontrol_action"]', 'PHP cron event' ); $I->see( 'PHP Code' ); $I->dontSee( 'URL' ); $I->dontSee( 'HTTP Method' ); From 575b2d17c0b971122c54a93faa64e97b906f3e6a Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 12 Jun 2024 23:26:05 +0100 Subject: [PATCH 03/17] Docs. --- docs/docs/how-to-use.md | 2 +- docs/docs/php-cron-events.md | 4 ++-- readme.md | 6 +++--- tests/acceptance/AddEventCest.php | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/docs/how-to-use.md b/docs/docs/how-to-use.md index e360f03..40a4873 100644 --- a/docs/docs/how-to-use.md +++ b/docs/docs/how-to-use.md @@ -29,7 +29,7 @@ There are two steps to creating a functioning cron event that executes regularly ### Step one: Adding the hook -In the Tools → Cron Events admin panel, click on "Add New" and enter the details of the hook. You're best off using a hook name that conforms to normal PHP variable naming conventions. The event schedule is how often your hook will be executed. If you don't see a good interval, then add one first in the Settings → Cron Schedules admin panel. +In the Tools → Cron Events admin panel, click on "Add New Cron Event" and enter the details of the hook. You're best off using a hook name that conforms to normal PHP variable naming conventions. The event schedule is how often your hook will be executed. If you don't see a good interval, then add one first in the Settings → Cron Schedules admin panel. ### Step two: Writing the function diff --git a/docs/docs/php-cron-events.md b/docs/docs/php-cron-events.md index 775c703..5263439 100644 --- a/docs/docs/php-cron-events.md +++ b/docs/docs/php-cron-events.md @@ -18,7 +18,7 @@ If you wish to prevent PHP cron events from being added or edited on your site t ## How do I create a new PHP cron event? -In the Tools → Cron Events admin panel, click on "Add New". In the form that appears, select the "PHP cron event" option under the "Event Type" list and enter the schedule and next run time. In the "Hook code" area, enter the PHP code that should be run when your cron event is executed. Don't include the PHP opening tag (`amOnCronEventListingPage(); - $I->click( 'Add New', '#wpbody' ); + $I->click( 'Add New Cron Event', '#wpbody' ); $I->dontSee( 'PHP Code' ); $I->dontSee( 'URL' ); $I->dontSee( 'HTTP Method' ); From d1775684431635aca458fa2d76df555310de2f26 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 12 Jun 2024 23:51:56 +0100 Subject: [PATCH 04/17] This isn't needed. --- src/bootstrap.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/bootstrap.php b/src/bootstrap.php index 405ff68..f5a2104 100644 --- a/src/bootstrap.php +++ b/src/bootstrap.php @@ -198,10 +198,6 @@ function action_handle_posts() { $cr = $request->init( wp_unslash( $_POST ) ); - if ( 'crontrol_cron_job' === $cr->hookname ) { - wp_die( esc_html__( 'You are not allowed to add new PHP cron events.', 'wp-crontrol' ), 401 ); - } - $next_run_local = ( 'custom' === $cr->next_run_date_local ) ? $cr->next_run_date_local_custom_date . ' ' . $cr->next_run_date_local_custom_time : $cr->next_run_date_local; $args = array( 'url' => $cr->url, From cd829a6476733f623da9160c38e12f16dc0a134b Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 12 Jun 2024 23:52:23 +0100 Subject: [PATCH 05/17] Update the args handling. --- src/bootstrap.php | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/bootstrap.php b/src/bootstrap.php index f5a2104..308574c 100644 --- a/src/bootstrap.php +++ b/src/bootstrap.php @@ -199,9 +199,12 @@ function action_handle_posts() { $cr = $request->init( wp_unslash( $_POST ) ); $next_run_local = ( 'custom' === $cr->next_run_date_local ) ? $cr->next_run_date_local_custom_date . ' ' . $cr->next_run_date_local_custom_time : $cr->next_run_date_local; - $args = array( - 'url' => $cr->url, - 'method' => $cr->method, + $args = array( + array( + 'url' => $cr->url, + 'method' => $cr->method, + 'name' => $cr->eventname, + ), ); add_filter( 'schedule_event', function( $event ) { @@ -404,8 +407,11 @@ function action_handle_posts() { check_admin_referer( "crontrol-edit-cron_{$cr->original_hookname}_{$cr->original_sig}_{$cr->original_next_run_utc}" ); $args = array( - 'url' => $cr->url, - 'method' => $cr->method, + array( + 'url' => $cr->url, + 'method' => $cr->method, + 'name' => $cr->eventname, + ), ); $hookname = ( ! empty( $cr->eventname ) ) ? $cr->eventname : __( 'URL Cron', 'wp-crontrol' ); $redirect = array( @@ -1640,7 +1646,7 @@ function show_cron_form( $editing ) { - + @@ -1657,7 +1663,7 @@ function show_cron_form( $editing ) {

@@ -1670,7 +1676,7 @@ function show_cron_form( $editing ) { - + From 48eefcaf998119eede1c77320862e093a103f907 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Wed, 12 Jun 2024 23:55:51 +0100 Subject: [PATCH 06/17] More URL event handling. --- src/event-list-table.php | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/event-list-table.php b/src/event-list-table.php index 2ad2453..fe9e34f 100644 --- a/src/event-list-table.php +++ b/src/event-list-table.php @@ -433,7 +433,7 @@ protected function handle_row_actions( $event, $column_name, $primary ) { /* translators: Resume is a verb */ $links[] = "" . esc_html__( 'Resume this hook', 'wp-crontrol' ) . ''; - } elseif ( 'crontrol_cron_job' !== $event->hook ) { + } elseif ( 'crontrol_cron_job' !== $event->hook && 'crontrol_url_cron_job' !== $event->hook ) { $link = array( 'page' => 'crontrol_admin_manage_page', 'crontrol_action' => 'pause-hook', @@ -460,7 +460,7 @@ protected function handle_row_actions( $event, $column_name, $primary ) { $links[] = "" . esc_html__( 'Delete', 'wp-crontrol' ) . ''; } - if ( 'crontrol_cron_job' !== $event->hook ) { + if ( 'crontrol_cron_job' !== $event->hook && 'crontrol_url_cron_job' !== $event->hook ) { if ( self::$count_by_hook[ $event->hook ] > 1 ) { $link = array( 'page' => 'crontrol_admin_manage_page', @@ -575,6 +575,25 @@ protected function column_crontrol_hook( $event ) { return $output; } + if ( 'crontrol_url_cron_job' === $event->hook ) { + if ( ! empty( $event->args[0]['name'] ) ) { + /* translators: %s: Details about the URL cron event. */ + $output = esc_html( sprintf( __( 'URL cron event (%s)', 'wp-crontrol' ), $event->args[0]['name'] ) ); + } elseif ( ! empty( $event->args[0]['url'] ) ) { + $url = sprintf( + '%s', + esc_html( $event->args[0]['url'] ) + ); + + /* translators: %s: Details about the URL cron event. */ + $output = sprintf( esc_html__( 'URL cron event (%s)', 'wp-crontrol' ), $url ); + } else { + $output = esc_html__( 'URL cron event', 'wp-crontrol' ); + } + + return $output; + } + $output = esc_html( $event->hook ); if ( is_paused( $event ) ) { @@ -604,7 +623,7 @@ protected function column_crontrol_hook( $event ) { protected function column_crontrol_actions( $event ) { $hook_callbacks = \Crontrol\get_hook_callbacks( $event->hook ); - if ( 'crontrol_cron_job' === $event->hook ) { + if ( 'crontrol_cron_job' === $event->hook || 'crontrol_url_cron_job' === $event->hook ) { return 'WP Crontrol'; } elseif ( ! empty( $hook_callbacks ) ) { $callbacks = array(); From d41cb4970f731d030869dced3c6c13c310f999cb Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Thu, 13 Jun 2024 00:04:05 +0100 Subject: [PATCH 07/17] Update the handling of the required status of the URL field. --- js/wp-crontrol.js | 8 ++++++-- src/bootstrap.php | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/js/wp-crontrol.js b/js/wp-crontrol.js index 3a184a6..3f0e166 100644 --- a/js/wp-crontrol.js +++ b/js/wp-crontrol.js @@ -14,22 +14,25 @@ document.addEventListener( 'DOMContentLoaded', () => { const newPHPCronElement = document.querySelector( 'input[value="new_php_cron"]' ); const hookCodeElement = document.getElementById( 'crontrol_hookcode' ); const hookNameElement = document.getElementById( 'crontrol_hookname' ); + const hookURLElement = document.getElementById( 'crontrol_url' ); const editEventElement = document.querySelector( '.crontrol-edit-event' ); customDateElement && customDateElement.addEventListener( 'change', checkCustom ); customTimeElement && customTimeElement.addEventListener( 'change', checkCustom ); - newCronElement.addEventListener( 'click', () => { + newCronElement && newCronElement.addEventListener( 'click', () => { editEventElement.classList.remove( 'crontrol-edit-event-url' ); editEventElement.classList.remove( 'crontrol-edit-event-php' ); editEventElement.classList.add( 'crontrol-edit-event-standard' ); hookNameElement.setAttribute( 'required', true ); + hookURLElement.removeAttribute( 'required' ); } ); - newURLCronElement.addEventListener( 'click', () => { + newURLCronElement && newURLCronElement.addEventListener( 'click', () => { editEventElement.classList.remove( 'crontrol-edit-event-standard' ); editEventElement.classList.remove( 'crontrol-edit-event-php' ); editEventElement.classList.add( 'crontrol-edit-event-url' ); + hookURLElement.setAttribute( 'required', true ); hookNameElement.removeAttribute( 'required' ); } ); @@ -39,6 +42,7 @@ document.addEventListener( 'DOMContentLoaded', () => { editEventElement.classList.remove( 'crontrol-edit-event-url' ); editEventElement.classList.add( 'crontrol-edit-event-php' ); hookNameElement.removeAttribute( 'required' ); + hookURLElement.removeAttribute( 'required' ); if ( ! hookCodeElement.classList.contains( 'crontrol-editor-initialized' ) ) { wp.codeEditor.initialize( 'crontrol_hookcode', window.wpCrontrol.codeEditor ); } diff --git a/src/bootstrap.php b/src/bootstrap.php index 308574c..b1c5ffb 100644 --- a/src/bootstrap.php +++ b/src/bootstrap.php @@ -1646,7 +1646,7 @@ function show_cron_form( $editing ) { - + From 795367379027e83185ba87b7413e2d14f0448a91 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Thu, 13 Jun 2024 00:05:10 +0100 Subject: [PATCH 08/17] Switch to more sentence case. --- readme.md | 2 +- src/event-list-table.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index b4c74ad..59bac72 100644 --- a/readme.md +++ b/readme.md @@ -141,7 +141,7 @@ function my_function() { ### How do I create a new PHP cron event? -In the Tools → Cron Events admin panel, click on "Add New Cron Event". In the form that appears, select "PHP Cron Event" and enter the schedule and next run time. The event schedule is how often your event will be executed. If you don't see a good interval, then add one in the Settings → Cron Schedules admin panel. In the "PHP Code" area, enter the PHP code that should be run when your cron event is executed. You don't need to provide the PHP opening tag (`hook ) { if ( ! empty( $event->args[0]['name'] ) ) { /* translators: %s: Details about the PHP cron event. */ - $output = esc_html( sprintf( __( 'PHP Cron (%s)', 'wp-crontrol' ), $event->args[0]['name'] ) ); + $output = esc_html( sprintf( __( 'PHP cron event (%s)', 'wp-crontrol' ), $event->args[0]['name'] ) ); } elseif ( ! empty( $event->args[0]['code'] ) ) { $lines = explode( "\n", trim( $event->args[0]['code'] ) ); $code = reset( $lines ); @@ -549,9 +549,9 @@ protected function column_crontrol_hook( $event ) { ); /* translators: %s: Details about the PHP cron event. */ - $output = sprintf( esc_html__( 'PHP Cron (%s)', 'wp-crontrol' ), $php ); + $output = sprintf( esc_html__( 'PHP cron event (%s)', 'wp-crontrol' ), $php ); } else { - $output = esc_html__( 'PHP Cron', 'wp-crontrol' ); + $output = esc_html__( 'PHP cron event', 'wp-crontrol' ); } if ( integrity_failed( $event ) ) { From 1a2a55c3396371cdbbfea2c394b574084c49e240 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Thu, 13 Jun 2024 00:08:11 +0100 Subject: [PATCH 09/17] More docs. --- docs/docs/php-cron-events.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/php-cron-events.md b/docs/docs/php-cron-events.md index 5263439..49995bc 100644 --- a/docs/docs/php-cron-events.md +++ b/docs/docs/php-cron-events.md @@ -18,7 +18,7 @@ If you wish to prevent PHP cron events from being added or edited on your site t ## How do I create a new PHP cron event? -In the Tools → Cron Events admin panel, click on "Add New Cron Event". In the form that appears, select the "PHP cron event" option under the "Event Type" list and enter the schedule and next run time. In the "PHP code" area, enter the PHP code that should be run when your cron event is executed. Don't include the PHP opening tag (` Date: Thu, 13 Jun 2024 00:11:45 +0100 Subject: [PATCH 10/17] Get more specific with these assertions. --- tests/acceptance/AddEventCest.php | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/tests/acceptance/AddEventCest.php b/tests/acceptance/AddEventCest.php index af6bd8c..a02ab9b 100644 --- a/tests/acceptance/AddEventCest.php +++ b/tests/acceptance/AddEventCest.php @@ -21,6 +21,9 @@ public function NavigatingToTheAddCronEventScreen( AcceptanceTester $I ) { public function AddingANewEvent( AcceptanceTester $I ) { $I->amOnCronEventListingPage(); $I->click( 'Add New Cron Event', '#wpbody' ); + $I->dontSee( 'PHP Code', '#crontrol_form' ); + $I->dontSee( 'URL', '#crontrol_form' ); + $I->dontSee( 'HTTP Method', '#crontrol_form' ); $I->fillField( 'Hook Name', 'my_hookname' ); $I->click( 'Add Event' ); $I->see( 'Cron Events', 'h1' ); @@ -30,13 +33,13 @@ public function AddingANewEvent( AcceptanceTester $I ) { public function AddingANewURLEvent( AcceptanceTester $I ) { $I->amOnCronEventListingPage(); $I->click( 'Add New Cron Event', '#wpbody' ); - $I->dontSee( 'PHP Code' ); - $I->dontSee( 'URL' ); - $I->dontSee( 'HTTP Method' ); + $I->dontSee( 'PHP Code', '#crontrol_form' ); + $I->dontSee( 'URL', '#crontrol_form' ); + $I->dontSee( 'HTTP Method', '#crontrol_form' ); $I->selectOption( 'input[name="crontrol_action"]', 'Request a URL' ); - $I->dontSee( 'PHP Code' ); - $I->see( 'URL' ); - $I->see( 'HTTP Method' ); + $I->dontSee( 'PHP Code', '#crontrol_form' ); + $I->see( 'URL', '#crontrol_form' ); + $I->see( 'HTTP Method', '#crontrol_form' ); $I->fillField( 'URL', 'https://example.org/' ); $I->click( 'Add Event' ); $I->see( 'Cron Events', 'h1' ); @@ -47,13 +50,13 @@ public function AddingANewURLEvent( AcceptanceTester $I ) { public function AddingANewPHPEvent( AcceptanceTester $I ) { $I->amOnCronEventListingPage(); $I->click( 'Add New Cron Event', '#wpbody' ); - $I->dontSee( 'PHP Code' ); - $I->dontSee( 'URL' ); - $I->dontSee( 'HTTP Method' ); + $I->dontSee( 'PHP Code', '#crontrol_form' ); + $I->dontSee( 'URL', '#crontrol_form' ); + $I->dontSee( 'HTTP Method', '#crontrol_form' ); $I->selectOption( 'input[name="crontrol_action"]', 'PHP cron event' ); - $I->see( 'PHP Code' ); - $I->dontSee( 'URL' ); - $I->dontSee( 'HTTP Method' ); + $I->see( 'PHP Code', '#crontrol_form' ); + $I->dontSee( 'URL', '#crontrol_form' ); + $I->dontSee( 'HTTP Method', '#crontrol_form' ); $I->fillPHPEditorField( 'amazing();' ); $I->click( 'Add Event' ); $I->see( 'Cron Events', 'h1' ); From 9f26c0c9e09da4e76c8abca3625c7b9fb9ef300f Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Thu, 13 Jun 2024 00:21:02 +0100 Subject: [PATCH 11/17] Docs. --- docs/.vitepress/config.mts | 4 ++++ docs/docs/url-cron-events.md | 11 +++++++++++ 2 files changed, 15 insertions(+) create mode 100644 docs/docs/url-cron-events.md diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index a920507..48f6315 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -68,6 +68,10 @@ export default defineConfig({ text: 'PHP cron events', link: '/docs/php-cron-events/', }, + { + text: 'URL cron events', + link: '/docs/url-cron-events/', + }, { text: 'What happens if I deactivate WP Crontrol?', link: '/docs/deactivation/', diff --git a/docs/docs/url-cron-events.md b/docs/docs/url-cron-events.md new file mode 100644 index 0000000..0cfabc4 --- /dev/null +++ b/docs/docs/url-cron-events.md @@ -0,0 +1,11 @@ +# URL cron events + +::: tip New +This feature is new in WP Crontrol 1.17 +::: + +WP Crontrol includes a feature that allows users to create events in the WP-Cron system that request a URL. This is a convenience wrapper around functionality that you would otherwise need to write PHP in order to achieve. + +## How do I create a cron event that requests a URL? + +From the Tools → Cron Events → Add New Cron Event screen, select the "Request a URL" option under the "Event Type" list. Fill out the rest of the details as required and press the "Add Event" button. From 953f0fd7375181a375e55fd4a20b1f377c78c2e6 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Thu, 13 Jun 2024 00:26:28 +0100 Subject: [PATCH 12/17] Update the callback for a URL cron event. --- src/bootstrap.php | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/bootstrap.php b/src/bootstrap.php index b1c5ffb..9424bfd 100644 --- a/src/bootstrap.php +++ b/src/bootstrap.php @@ -2461,19 +2461,27 @@ function json_output( $input, $pretty = true ) { } /** - * Fetches the URL in a cron event using the HTTP API. + * Fetches the URL in a URL cron event using the HTTP API. * * @throws Exception If the request fails. * - * @param string $url The URL to fetch. - * @param string $method The HTTP method to use. + * @param array $args The event args array. + * @phpstan-param array{ + * url: string, + * name: string, + * method: 'GET'|'POST', + * } $args */ -function action_url_cron_event( $url, $method ): void { - $args = array( +function action_url_cron_event( array $args ): void { + list( + 'url' => $url, + 'method' => $method, + ) = $args; + $request_args = array( 'timeout' => 30, 'method' => $method, ); - $response = wp_remote_request( $url, $args ); + $response = wp_remote_request( $url, $request_args ); if ( is_wp_error( $response ) ) { throw new Exception( sprintf( From 70b9a50f10010543801bf6d7099eada781840746 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Thu, 13 Jun 2024 18:32:53 +0100 Subject: [PATCH 13/17] More fixes for the tests. --- tests/acceptance/AddEventCest.php | 32 +++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/acceptance/AddEventCest.php b/tests/acceptance/AddEventCest.php index a02ab9b..f294217 100644 --- a/tests/acceptance/AddEventCest.php +++ b/tests/acceptance/AddEventCest.php @@ -21,9 +21,9 @@ public function NavigatingToTheAddCronEventScreen( AcceptanceTester $I ) { public function AddingANewEvent( AcceptanceTester $I ) { $I->amOnCronEventListingPage(); $I->click( 'Add New Cron Event', '#wpbody' ); - $I->dontSee( 'PHP Code', '#crontrol_form' ); - $I->dontSee( 'URL', '#crontrol_form' ); - $I->dontSee( 'HTTP Method', '#crontrol_form' ); + $I->dontSee( 'PHP Code', '#crontrol_form th' ); + $I->dontSee( 'URL', '#crontrol_form th' ); + $I->dontSee( 'HTTP Method', '#crontrol_form th' ); $I->fillField( 'Hook Name', 'my_hookname' ); $I->click( 'Add Event' ); $I->see( 'Cron Events', 'h1' ); @@ -33,14 +33,14 @@ public function AddingANewEvent( AcceptanceTester $I ) { public function AddingANewURLEvent( AcceptanceTester $I ) { $I->amOnCronEventListingPage(); $I->click( 'Add New Cron Event', '#wpbody' ); - $I->dontSee( 'PHP Code', '#crontrol_form' ); - $I->dontSee( 'URL', '#crontrol_form' ); - $I->dontSee( 'HTTP Method', '#crontrol_form' ); + $I->dontSee( 'PHP Code', '#crontrol_form th' ); + $I->dontSee( 'URL', '#crontrol_form th' ); + $I->dontSee( 'HTTP Method', '#crontrol_form th' ); $I->selectOption( 'input[name="crontrol_action"]', 'Request a URL' ); - $I->dontSee( 'PHP Code', '#crontrol_form' ); - $I->see( 'URL', '#crontrol_form' ); - $I->see( 'HTTP Method', '#crontrol_form' ); - $I->fillField( 'URL', 'https://example.org/' ); + $I->dontSee( 'PHP Code', '#crontrol_form th' ); + $I->see( 'URL', '#crontrol_form th' ); + $I->see( 'HTTP Method', '#crontrol_form th' ); + $I->fillField( '#crontrol_url', 'https://example.org/' ); $I->click( 'Add Event' ); $I->see( 'Cron Events', 'h1' ); $I->seeAdminSuccessNotice( 'Created the cron event URL Cron.' ); @@ -50,13 +50,13 @@ public function AddingANewURLEvent( AcceptanceTester $I ) { public function AddingANewPHPEvent( AcceptanceTester $I ) { $I->amOnCronEventListingPage(); $I->click( 'Add New Cron Event', '#wpbody' ); - $I->dontSee( 'PHP Code', '#crontrol_form' ); - $I->dontSee( 'URL', '#crontrol_form' ); - $I->dontSee( 'HTTP Method', '#crontrol_form' ); + $I->dontSee( 'PHP Code', '#crontrol_form th' ); + $I->dontSee( 'URL', '#crontrol_form th' ); + $I->dontSee( 'HTTP Method', '#crontrol_form th' ); $I->selectOption( 'input[name="crontrol_action"]', 'PHP cron event' ); - $I->see( 'PHP Code', '#crontrol_form' ); - $I->dontSee( 'URL', '#crontrol_form' ); - $I->dontSee( 'HTTP Method', '#crontrol_form' ); + $I->see( 'PHP Code', '#crontrol_form th' ); + $I->dontSee( 'URL', '#crontrol_form th' ); + $I->dontSee( 'HTTP Method', '#crontrol_form th' ); $I->fillPHPEditorField( 'amazing();' ); $I->click( 'Add Event' ); $I->see( 'Cron Events', 'h1' ); From 74da28ea97396cab26406085b560cbd489725361 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Thu, 13 Jun 2024 18:33:02 +0100 Subject: [PATCH 14/17] This only accepts one parameter now. --- src/bootstrap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bootstrap.php b/src/bootstrap.php index 9424bfd..04eef80 100644 --- a/src/bootstrap.php +++ b/src/bootstrap.php @@ -36,7 +36,7 @@ function init_hooks() { add_filter( 'cron_schedules', __NAMESPACE__ . '\filter_cron_schedules' ); add_action( 'crontrol_cron_job', __NAMESPACE__ . '\action_php_cron_event' ); - add_action( 'crontrol_url_cron_job', __NAMESPACE__ . '\action_url_cron_event', 10, 2 ); + add_action( 'crontrol_url_cron_job', __NAMESPACE__ . '\action_url_cron_event' ); add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\enqueue_assets' ); add_action( 'crontrol/tab-header', __NAMESPACE__ . '\show_cron_status', 20 ); add_action( 'activated_plugin', __NAMESPACE__ . '\flush_status_cache', 10, 0 ); From f7bdd6270189ee8751aa9690cd4e129e8c99f7e0 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Fri, 14 Jun 2024 15:25:33 +0100 Subject: [PATCH 15/17] UI improvements for the request method. --- src/bootstrap.php | 83 +++++++++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 42 deletions(-) diff --git a/src/bootstrap.php b/src/bootstrap.php index 0f94362..70d80ea 100644 --- a/src/bootstrap.php +++ b/src/bootstrap.php @@ -1640,49 +1640,48 @@ function show_cron_form( $editing ) { - - - - - - - - - - - - - - -

-

-

-

- - - - - - - - - - - - + + + + + + + + + + + + + + + @@ -2483,7 +2482,7 @@ function json_output( $input, $pretty = true ) { * @phpstan-param array{ * url: string, * name: string, - * method: 'GET'|'POST', + * method: string, * } $args */ function action_url_cron_event( array $args ): void { From ef1bb918e1b6ac45c74b8273278c55c35d207020 Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Fri, 14 Jun 2024 15:25:47 +0100 Subject: [PATCH 16/17] Don't show the PHP cron event option when the user can't add one. --- src/bootstrap.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/bootstrap.php b/src/bootstrap.php index 70d80ea..f27b91a 100644 --- a/src/bootstrap.php +++ b/src/bootstrap.php @@ -1628,12 +1628,14 @@ function show_cron_form( $editing ) {

-

- -

+ +

+ +

+ From 9a109521d42ba4762f200eba11ffe9150516f50c Mon Sep 17 00:00:00 2001 From: John Blackbourn Date: Fri, 14 Jun 2024 15:27:46 +0100 Subject: [PATCH 17/17] Use Yoda condition checks, you may or may not. --- phpcs.xml.dist | 3 +++ 1 file changed, 3 insertions(+) diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 04e5fb2..a4e1963 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -40,6 +40,9 @@ + + +