Skip to content
Draft
71 changes: 71 additions & 0 deletions features/cron-event.feature
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,77 @@ Feature: Manage WP Cron events
Debug: Arguments:
"""

Scenario: List cron events with actions field
Given a wp-content/mu-plugins/test-cron-actions.php file:
"""
<?php
add_action( 'wp_cli_test_hook', 'wp_cli_test_function' );
add_action( 'wp_cli_test_hook', array( 'MyTestClass', 'my_method' ) );
add_action( 'wp_cli_test_hook_closure', function() {
// Test closure
} );

function wp_cli_test_function() {
// Test function
}

class MyTestClass {
public static function my_method() {
// Test method
}
}
"""

When I run `wp cron event schedule wp_cli_test_hook now`
Then STDOUT should contain:
"""
Success: Scheduled event with hook 'wp_cli_test_hook'
"""

When I run `wp cron event list --fields=hook,actions --format=csv`
Then STDOUT should contain:
"""
wp_cli_test_function
"""
And STDOUT should contain:
"""
MyTestClass::my_method
"""

When I run `wp cron event list --hook=wp_cli_test_hook --fields=hook,actions --format=json`
Then STDOUT should be JSON containing:
"""
[{"hook":"wp_cli_test_hook"}]
"""
And STDOUT should contain:
"""
wp_cli_test_function
"""

When I run `wp cron event schedule wp_cli_test_hook_closure now`
Then STDOUT should contain:
"""
Success: Scheduled event with hook 'wp_cli_test_hook_closure'
"""

When I run `wp cron event list --hook=wp_cli_test_hook_closure --fields=hook,actions --format=csv`
Then STDOUT should contain:
"""
Closure
"""

When I run `wp cron event schedule wp_cli_test_hook_no_actions now`
Then STDOUT should contain:
"""
Success: Scheduled event with hook 'wp_cli_test_hook_no_actions'
"""

When I run `wp cron event list --hook=wp_cli_test_hook_no_actions --fields=hook,actions --format=csv`
Then STDOUT should contain:
"""
None
"""

Scenario: Confirm that cron event run in debug mode shows the start of events
When I try `wp cron event run wp_version_check --debug=cron`
Then STDOUT should contain:
Expand Down
83 changes: 83 additions & 0 deletions src/Cron_Event_Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
* * schedule
* * interval
* * next_run
* * actions
*
* ## EXAMPLES
*
Expand Down Expand Up @@ -113,6 +114,14 @@
$events = array();
}

// Populate actions field only if requested
$requested_fields = $formatter->fields;
if ( ! empty( $requested_fields ) && in_array( 'actions', $requested_fields, true ) ) {
foreach ( $events as $event ) {
$event->actions = self::get_hook_actions( $event->hook );
}
}

foreach ( $events as $key => $event ) {
foreach ( $this->fields as $field ) {
if ( ! empty( $assoc_args[ $field ] ) && $event->{$field} !== $assoc_args[ $field ] ) {
Expand Down Expand Up @@ -399,6 +408,7 @@
$event->next_run = get_date_from_gmt( date( 'Y-m-d H:i:s', $event->time ), self::$time_format ); //phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
$event->next_run_gmt = date( self::$time_format, $event->time ); //phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
$event->next_run_relative = self::interval( $event->time - time() );
$event->actions = '';

return $event;
}
Expand Down Expand Up @@ -607,4 +617,77 @@
private function get_formatter( &$assoc_args ) {
return new \WP_CLI\Formatter( $assoc_args, $this->fields, 'event' );
}

/**
* Gets the actions (callbacks) registered for a specific hook.
*
* @param string $hook_name The name of the hook.
* @return string A comma-separated list of action callbacks, or 'None' if no actions are registered.
*/
protected static function get_hook_actions( $hook_name ) {
global $wp_filter;

if ( ! isset( $wp_filter[ $hook_name ] ) ) {
return 'None';
}

$hook = $wp_filter[ $hook_name ];

// Get callbacks from the WP_Hook object (WordPress 4.7+)
if ( $hook instanceof \WP_Hook ) {
$callbacks = $hook->callbacks;
} else {
// Fallback for older WordPress versions
$callbacks = $hook;
}

if ( empty( $callbacks ) ) {
return 'None';
}

$actions = array();

// Iterate through all priorities
foreach ( $callbacks as $priority => $priority_callbacks ) {
foreach ( $priority_callbacks as $callback_info ) {
if ( ! isset( $callback_info['function'] ) ) {
continue;
}
$callback = $callback_info['function'];
$actions[] = self::format_callback( $callback );
}
}

return implode( ', ', $actions );
}

/**
* Formats a callback into a readable string.
*
* @param callable $callback The callback to format.
* @return string A formatted string representing the callback.
*/
protected static function format_callback( $callback ) {
if ( is_string( $callback ) ) {
return $callback;
} elseif ( is_array( $callback ) && count( $callback ) === 2 ) {
$class = $callback[0];
$method = $callback[1];

if ( is_object( $class ) ) {
$class_name = get_class( $class );
} else {
$class_name = $class;
}

return $class_name . '::' . $method;

Check failure on line 683 in src/Cron_Event_Command.php

View workflow job for this annotation

GitHub Actions / code-quality / PHPStan

Binary operation "." between non-falsy-string and mixed results in an error.

Check failure on line 683 in src/Cron_Event_Command.php

View workflow job for this annotation

GitHub Actions / code-quality / PHPStan

Binary operation "." between mixed and '::' results in an error.
} elseif ( $callback instanceof \Closure ) {
return 'Closure';
} elseif ( is_object( $callback ) ) {
return get_class( $callback ) . '::__invoke';
}

// Fallback for unknown callback types
return 'Unknown';
}
}
Loading