@@ -9,39 +9,39 @@ |
||
| 9 | 9 | */ |
| 10 | 10 | class ActionScheduler_DBStoreMigrator extends ActionScheduler_DBStore { |
| 11 | 11 | |
| 12 | - /** |
|
| 13 | - * Save an action with optional last attempt date. |
|
| 14 | - * |
|
| 15 | - * Normally, saving an action sets its attempted date to 0000-00-00 00:00:00 because when an action is first saved, |
|
| 16 | - * it can't have been attempted yet, but migrated completed actions will have an attempted date, so we need to save |
|
| 17 | - * that when first saving the action. |
|
| 18 | - * |
|
| 19 | - * @param ActionScheduler_Action $action |
|
| 20 | - * @param \DateTime $scheduled_date Optional date of the first instance to store. |
|
| 21 | - * @param \DateTime $last_attempt_date Optional date the action was last attempted. |
|
| 22 | - * |
|
| 23 | - * @return string The action ID |
|
| 24 | - * @throws \RuntimeException When the action is not saved. |
|
| 25 | - */ |
|
| 26 | - public function save_action( ActionScheduler_Action $action, \DateTime $scheduled_date = null, \DateTime $last_attempt_date = null ){ |
|
| 27 | - try { |
|
| 28 | - /** @var \wpdb $wpdb */ |
|
| 29 | - global $wpdb; |
|
| 12 | + /** |
|
| 13 | + * Save an action with optional last attempt date. |
|
| 14 | + * |
|
| 15 | + * Normally, saving an action sets its attempted date to 0000-00-00 00:00:00 because when an action is first saved, |
|
| 16 | + * it can't have been attempted yet, but migrated completed actions will have an attempted date, so we need to save |
|
| 17 | + * that when first saving the action. |
|
| 18 | + * |
|
| 19 | + * @param ActionScheduler_Action $action |
|
| 20 | + * @param \DateTime $scheduled_date Optional date of the first instance to store. |
|
| 21 | + * @param \DateTime $last_attempt_date Optional date the action was last attempted. |
|
| 22 | + * |
|
| 23 | + * @return string The action ID |
|
| 24 | + * @throws \RuntimeException When the action is not saved. |
|
| 25 | + */ |
|
| 26 | + public function save_action( ActionScheduler_Action $action, \DateTime $scheduled_date = null, \DateTime $last_attempt_date = null ){ |
|
| 27 | + try { |
|
| 28 | + /** @var \wpdb $wpdb */ |
|
| 29 | + global $wpdb; |
|
| 30 | 30 | |
| 31 | - $action_id = parent::save_action( $action, $scheduled_date ); |
|
| 31 | + $action_id = parent::save_action( $action, $scheduled_date ); |
|
| 32 | 32 | |
| 33 | - if ( null !== $last_attempt_date ) { |
|
| 34 | - $data = [ |
|
| 35 | - 'last_attempt_gmt' => $this->get_scheduled_date_string( $action, $last_attempt_date ), |
|
| 36 | - 'last_attempt_local' => $this->get_scheduled_date_string_local( $action, $last_attempt_date ), |
|
| 37 | - ]; |
|
| 33 | + if ( null !== $last_attempt_date ) { |
|
| 34 | + $data = [ |
|
| 35 | + 'last_attempt_gmt' => $this->get_scheduled_date_string( $action, $last_attempt_date ), |
|
| 36 | + 'last_attempt_local' => $this->get_scheduled_date_string_local( $action, $last_attempt_date ), |
|
| 37 | + ]; |
|
| 38 | 38 | |
| 39 | - $wpdb->update( $wpdb->actionscheduler_actions, $data, array( 'action_id' => $action_id ), array( '%s', '%s' ), array( '%d' ) ); |
|
| 40 | - } |
|
| 39 | + $wpdb->update( $wpdb->actionscheduler_actions, $data, array( 'action_id' => $action_id ), array( '%s', '%s' ), array( '%d' ) ); |
|
| 40 | + } |
|
| 41 | 41 | |
| 42 | - return $action_id; |
|
| 43 | - } catch ( \Exception $e ) { |
|
| 44 | - throw new \RuntimeException( sprintf( __( 'Error saving action: %s', 'woocommerce' ), $e->getMessage() ), 0 ); |
|
| 45 | - } |
|
| 46 | - } |
|
| 42 | + return $action_id; |
|
| 43 | + } catch ( \Exception $e ) { |
|
| 44 | + throw new \RuntimeException( sprintf( __( 'Error saving action: %s', 'woocommerce' ), $e->getMessage() ), 0 ); |
|
| 45 | + } |
|
| 46 | + } |
|
| 47 | 47 | } |
@@ -11,13 +11,13 @@ |
||
| 11 | 11 | * @codeCoverageIgnore |
| 12 | 12 | */ |
| 13 | 13 | class DryRun_LogMigrator extends LogMigrator { |
| 14 | - /** |
|
| 15 | - * Simulate migrating an action log. |
|
| 16 | - * |
|
| 17 | - * @param int $source_action_id Source logger object. |
|
| 18 | - * @param int $destination_action_id Destination logger object. |
|
| 19 | - */ |
|
| 20 | - public function migrate( $source_action_id, $destination_action_id ) { |
|
| 21 | - // no-op |
|
| 22 | - } |
|
| 14 | + /** |
|
| 15 | + * Simulate migrating an action log. |
|
| 16 | + * |
|
| 17 | + * @param int $source_action_id Source logger object. |
|
| 18 | + * @param int $destination_action_id Destination logger object. |
|
| 19 | + */ |
|
| 20 | + public function migrate( $source_action_id, $destination_action_id ) { |
|
| 21 | + // no-op |
|
| 22 | + } |
|
| 23 | 23 | } |
| 24 | 24 | \ No newline at end of file |
@@ -6,638 +6,638 @@ |
||
| 6 | 6 | */ |
| 7 | 7 | class ActionScheduler_ListTable extends ActionScheduler_Abstract_ListTable { |
| 8 | 8 | |
| 9 | - /** |
|
| 10 | - * The package name. |
|
| 11 | - * |
|
| 12 | - * @var string |
|
| 13 | - */ |
|
| 14 | - protected $package = 'action-scheduler'; |
|
| 15 | - |
|
| 16 | - /** |
|
| 17 | - * Columns to show (name => label). |
|
| 18 | - * |
|
| 19 | - * @var array |
|
| 20 | - */ |
|
| 21 | - protected $columns = array(); |
|
| 22 | - |
|
| 23 | - /** |
|
| 24 | - * Actions (name => label). |
|
| 25 | - * |
|
| 26 | - * @var array |
|
| 27 | - */ |
|
| 28 | - protected $row_actions = array(); |
|
| 29 | - |
|
| 30 | - /** |
|
| 31 | - * The active data stores |
|
| 32 | - * |
|
| 33 | - * @var ActionScheduler_Store |
|
| 34 | - */ |
|
| 35 | - protected $store; |
|
| 36 | - |
|
| 37 | - /** |
|
| 38 | - * A logger to use for getting action logs to display |
|
| 39 | - * |
|
| 40 | - * @var ActionScheduler_Logger |
|
| 41 | - */ |
|
| 42 | - protected $logger; |
|
| 43 | - |
|
| 44 | - /** |
|
| 45 | - * A ActionScheduler_QueueRunner runner instance (or child class) |
|
| 46 | - * |
|
| 47 | - * @var ActionScheduler_QueueRunner |
|
| 48 | - */ |
|
| 49 | - protected $runner; |
|
| 50 | - |
|
| 51 | - /** |
|
| 52 | - * Bulk actions. The key of the array is the method name of the implementation: |
|
| 53 | - * |
|
| 54 | - * bulk_<key>(array $ids, string $sql_in). |
|
| 55 | - * |
|
| 56 | - * See the comments in the parent class for further details |
|
| 57 | - * |
|
| 58 | - * @var array |
|
| 59 | - */ |
|
| 60 | - protected $bulk_actions = array(); |
|
| 61 | - |
|
| 62 | - /** |
|
| 63 | - * Flag variable to render our notifications, if any, once. |
|
| 64 | - * |
|
| 65 | - * @var bool |
|
| 66 | - */ |
|
| 67 | - protected static $did_notification = false; |
|
| 68 | - |
|
| 69 | - /** |
|
| 70 | - * Array of seconds for common time periods, like week or month, alongside an internationalised string representation, i.e. "Day" or "Days" |
|
| 71 | - * |
|
| 72 | - * @var array |
|
| 73 | - */ |
|
| 74 | - private static $time_periods; |
|
| 75 | - |
|
| 76 | - /** |
|
| 77 | - * Sets the current data store object into `store->action` and initialises the object. |
|
| 78 | - * |
|
| 79 | - * @param ActionScheduler_Store $store |
|
| 80 | - * @param ActionScheduler_Logger $logger |
|
| 81 | - * @param ActionScheduler_QueueRunner $runner |
|
| 82 | - */ |
|
| 83 | - public function __construct( ActionScheduler_Store $store, ActionScheduler_Logger $logger, ActionScheduler_QueueRunner $runner ) { |
|
| 84 | - |
|
| 85 | - $this->store = $store; |
|
| 86 | - $this->logger = $logger; |
|
| 87 | - $this->runner = $runner; |
|
| 88 | - |
|
| 89 | - $this->table_header = __( 'Scheduled Actions', 'woocommerce' ); |
|
| 90 | - |
|
| 91 | - $this->bulk_actions = array( |
|
| 92 | - 'delete' => __( 'Delete', 'woocommerce' ), |
|
| 93 | - ); |
|
| 94 | - |
|
| 95 | - $this->columns = array( |
|
| 96 | - 'hook' => __( 'Hook', 'woocommerce' ), |
|
| 97 | - 'status' => __( 'Status', 'woocommerce' ), |
|
| 98 | - 'args' => __( 'Arguments', 'woocommerce' ), |
|
| 99 | - 'group' => __( 'Group', 'woocommerce' ), |
|
| 100 | - 'recurrence' => __( 'Recurrence', 'woocommerce' ), |
|
| 101 | - 'schedule' => __( 'Scheduled Date', 'woocommerce' ), |
|
| 102 | - 'log_entries' => __( 'Log', 'woocommerce' ), |
|
| 103 | - ); |
|
| 104 | - |
|
| 105 | - $this->sort_by = array( |
|
| 106 | - 'schedule', |
|
| 107 | - 'hook', |
|
| 108 | - 'group', |
|
| 109 | - ); |
|
| 110 | - |
|
| 111 | - $this->search_by = array( |
|
| 112 | - 'hook', |
|
| 113 | - 'args', |
|
| 114 | - 'claim_id', |
|
| 115 | - ); |
|
| 116 | - |
|
| 117 | - $request_status = $this->get_request_status(); |
|
| 118 | - |
|
| 119 | - if ( empty( $request_status ) ) { |
|
| 120 | - $this->sort_by[] = 'status'; |
|
| 121 | - } elseif ( in_array( $request_status, array( 'in-progress', 'failed' ) ) ) { |
|
| 122 | - $this->columns += array( 'claim_id' => __( 'Claim ID', 'woocommerce' ) ); |
|
| 123 | - $this->sort_by[] = 'claim_id'; |
|
| 124 | - } |
|
| 125 | - |
|
| 126 | - $this->row_actions = array( |
|
| 127 | - 'hook' => array( |
|
| 128 | - 'run' => array( |
|
| 129 | - 'name' => __( 'Run', 'woocommerce' ), |
|
| 130 | - 'desc' => __( 'Process the action now as if it were run as part of a queue', 'woocommerce' ), |
|
| 131 | - ), |
|
| 132 | - 'cancel' => array( |
|
| 133 | - 'name' => __( 'Cancel', 'woocommerce' ), |
|
| 134 | - 'desc' => __( 'Cancel the action now to avoid it being run in future', 'woocommerce' ), |
|
| 135 | - 'class' => 'cancel trash', |
|
| 136 | - ), |
|
| 137 | - ), |
|
| 138 | - ); |
|
| 139 | - |
|
| 140 | - self::$time_periods = array( |
|
| 141 | - array( |
|
| 142 | - 'seconds' => YEAR_IN_SECONDS, |
|
| 143 | - /* translators: %s: amount of time */ |
|
| 144 | - 'names' => _n_noop( '%s year', '%s years', 'woocommerce' ), |
|
| 145 | - ), |
|
| 146 | - array( |
|
| 147 | - 'seconds' => MONTH_IN_SECONDS, |
|
| 148 | - /* translators: %s: amount of time */ |
|
| 149 | - 'names' => _n_noop( '%s month', '%s months', 'woocommerce' ), |
|
| 150 | - ), |
|
| 151 | - array( |
|
| 152 | - 'seconds' => WEEK_IN_SECONDS, |
|
| 153 | - /* translators: %s: amount of time */ |
|
| 154 | - 'names' => _n_noop( '%s week', '%s weeks', 'woocommerce' ), |
|
| 155 | - ), |
|
| 156 | - array( |
|
| 157 | - 'seconds' => DAY_IN_SECONDS, |
|
| 158 | - /* translators: %s: amount of time */ |
|
| 159 | - 'names' => _n_noop( '%s day', '%s days', 'woocommerce' ), |
|
| 160 | - ), |
|
| 161 | - array( |
|
| 162 | - 'seconds' => HOUR_IN_SECONDS, |
|
| 163 | - /* translators: %s: amount of time */ |
|
| 164 | - 'names' => _n_noop( '%s hour', '%s hours', 'woocommerce' ), |
|
| 165 | - ), |
|
| 166 | - array( |
|
| 167 | - 'seconds' => MINUTE_IN_SECONDS, |
|
| 168 | - /* translators: %s: amount of time */ |
|
| 169 | - 'names' => _n_noop( '%s minute', '%s minutes', 'woocommerce' ), |
|
| 170 | - ), |
|
| 171 | - array( |
|
| 172 | - 'seconds' => 1, |
|
| 173 | - /* translators: %s: amount of time */ |
|
| 174 | - 'names' => _n_noop( '%s second', '%s seconds', 'woocommerce' ), |
|
| 175 | - ), |
|
| 176 | - ); |
|
| 177 | - |
|
| 178 | - parent::__construct( |
|
| 179 | - array( |
|
| 180 | - 'singular' => 'action-scheduler', |
|
| 181 | - 'plural' => 'action-scheduler', |
|
| 182 | - 'ajax' => false, |
|
| 183 | - ) |
|
| 184 | - ); |
|
| 185 | - |
|
| 186 | - add_screen_option( |
|
| 187 | - 'per_page', |
|
| 188 | - array( |
|
| 189 | - 'default' => $this->items_per_page, |
|
| 190 | - ) |
|
| 191 | - ); |
|
| 192 | - |
|
| 193 | - add_filter( 'set_screen_option_' . $this->get_per_page_option_name(), array( $this, 'set_items_per_page_option' ), 10, 3 ); |
|
| 194 | - set_screen_options(); |
|
| 195 | - } |
|
| 196 | - |
|
| 197 | - /** |
|
| 198 | - * Handles setting the items_per_page option for this screen. |
|
| 199 | - * |
|
| 200 | - * @param mixed $status Default false (to skip saving the current option). |
|
| 201 | - * @param string $option Screen option name. |
|
| 202 | - * @param int $value Screen option value. |
|
| 203 | - * @return int |
|
| 204 | - */ |
|
| 205 | - public function set_items_per_page_option( $status, $option, $value ) { |
|
| 206 | - return $value; |
|
| 207 | - } |
|
| 208 | - /** |
|
| 209 | - * Convert an interval of seconds into a two part human friendly string. |
|
| 210 | - * |
|
| 211 | - * The WordPress human_time_diff() function only calculates the time difference to one degree, meaning |
|
| 212 | - * even if an action is 1 day and 11 hours away, it will display "1 day". This function goes one step |
|
| 213 | - * further to display two degrees of accuracy. |
|
| 214 | - * |
|
| 215 | - * Inspired by the Crontrol::interval() function by Edward Dale: https://wordpress.org/plugins/wp-crontrol/ |
|
| 216 | - * |
|
| 217 | - * @param int $interval A interval in seconds. |
|
| 218 | - * @param int $periods_to_include Depth of time periods to include, e.g. for an interval of 70, and $periods_to_include of 2, both minutes and seconds would be included. With a value of 1, only minutes would be included. |
|
| 219 | - * @return string A human friendly string representation of the interval. |
|
| 220 | - */ |
|
| 221 | - private static function human_interval( $interval, $periods_to_include = 2 ) { |
|
| 222 | - |
|
| 223 | - if ( $interval <= 0 ) { |
|
| 224 | - return __( 'Now!', 'woocommerce' ); |
|
| 225 | - } |
|
| 226 | - |
|
| 227 | - $output = ''; |
|
| 228 | - |
|
| 229 | - for ( $time_period_index = 0, $periods_included = 0, $seconds_remaining = $interval; $time_period_index < count( self::$time_periods ) && $seconds_remaining > 0 && $periods_included < $periods_to_include; $time_period_index++ ) { |
|
| 230 | - |
|
| 231 | - $periods_in_interval = floor( $seconds_remaining / self::$time_periods[ $time_period_index ]['seconds'] ); |
|
| 232 | - |
|
| 233 | - if ( $periods_in_interval > 0 ) { |
|
| 234 | - if ( ! empty( $output ) ) { |
|
| 235 | - $output .= ' '; |
|
| 236 | - } |
|
| 237 | - $output .= sprintf( _n( self::$time_periods[ $time_period_index ]['names'][0], self::$time_periods[ $time_period_index ]['names'][1], $periods_in_interval, 'woocommerce' ), $periods_in_interval ); |
|
| 238 | - $seconds_remaining -= $periods_in_interval * self::$time_periods[ $time_period_index ]['seconds']; |
|
| 239 | - $periods_included++; |
|
| 240 | - } |
|
| 241 | - } |
|
| 242 | - |
|
| 243 | - return $output; |
|
| 244 | - } |
|
| 245 | - |
|
| 246 | - /** |
|
| 247 | - * Returns the recurrence of an action or 'Non-repeating'. The output is human readable. |
|
| 248 | - * |
|
| 249 | - * @param ActionScheduler_Action $action |
|
| 250 | - * |
|
| 251 | - * @return string |
|
| 252 | - */ |
|
| 253 | - protected function get_recurrence( $action ) { |
|
| 254 | - $schedule = $action->get_schedule(); |
|
| 255 | - if ( $schedule->is_recurring() ) { |
|
| 256 | - $recurrence = $schedule->get_recurrence(); |
|
| 257 | - |
|
| 258 | - if ( is_numeric( $recurrence ) ) { |
|
| 259 | - /* translators: %s: time interval */ |
|
| 260 | - return sprintf( __( 'Every %s', 'woocommerce' ), self::human_interval( $recurrence ) ); |
|
| 261 | - } else { |
|
| 262 | - return $recurrence; |
|
| 263 | - } |
|
| 264 | - } |
|
| 265 | - |
|
| 266 | - return __( 'Non-repeating', 'woocommerce' ); |
|
| 267 | - } |
|
| 268 | - |
|
| 269 | - /** |
|
| 270 | - * Serializes the argument of an action to render it in a human friendly format. |
|
| 271 | - * |
|
| 272 | - * @param array $row The array representation of the current row of the table |
|
| 273 | - * |
|
| 274 | - * @return string |
|
| 275 | - */ |
|
| 276 | - public function column_args( array $row ) { |
|
| 277 | - if ( empty( $row['args'] ) ) { |
|
| 278 | - return apply_filters( 'action_scheduler_list_table_column_args', '', $row ); |
|
| 279 | - } |
|
| 280 | - |
|
| 281 | - $row_html = '<ul>'; |
|
| 282 | - foreach ( $row['args'] as $key => $value ) { |
|
| 283 | - $row_html .= sprintf( '<li><code>%s => %s</code></li>', esc_html( var_export( $key, true ) ), esc_html( var_export( $value, true ) ) ); |
|
| 284 | - } |
|
| 285 | - $row_html .= '</ul>'; |
|
| 286 | - |
|
| 287 | - return apply_filters( 'action_scheduler_list_table_column_args', $row_html, $row ); |
|
| 288 | - } |
|
| 289 | - |
|
| 290 | - /** |
|
| 291 | - * Prints the logs entries inline. We do so to avoid loading Javascript and other hacks to show it in a modal. |
|
| 292 | - * |
|
| 293 | - * @param array $row Action array. |
|
| 294 | - * @return string |
|
| 295 | - */ |
|
| 296 | - public function column_log_entries( array $row ) { |
|
| 297 | - |
|
| 298 | - $log_entries_html = '<ol>'; |
|
| 299 | - |
|
| 300 | - $timezone = new DateTimezone( 'UTC' ); |
|
| 301 | - |
|
| 302 | - foreach ( $row['log_entries'] as $log_entry ) { |
|
| 303 | - $log_entries_html .= $this->get_log_entry_html( $log_entry, $timezone ); |
|
| 304 | - } |
|
| 305 | - |
|
| 306 | - $log_entries_html .= '</ol>'; |
|
| 307 | - |
|
| 308 | - return $log_entries_html; |
|
| 309 | - } |
|
| 310 | - |
|
| 311 | - /** |
|
| 312 | - * Prints the logs entries inline. We do so to avoid loading Javascript and other hacks to show it in a modal. |
|
| 313 | - * |
|
| 314 | - * @param ActionScheduler_LogEntry $log_entry |
|
| 315 | - * @param DateTimezone $timezone |
|
| 316 | - * @return string |
|
| 317 | - */ |
|
| 318 | - protected function get_log_entry_html( ActionScheduler_LogEntry $log_entry, DateTimezone $timezone ) { |
|
| 319 | - $date = $log_entry->get_date(); |
|
| 320 | - $date->setTimezone( $timezone ); |
|
| 321 | - return sprintf( '<li><strong>%s</strong><br/>%s</li>', esc_html( $date->format( 'Y-m-d H:i:s O' ) ), esc_html( $log_entry->get_message() ) ); |
|
| 322 | - } |
|
| 323 | - |
|
| 324 | - /** |
|
| 325 | - * Only display row actions for pending actions. |
|
| 326 | - * |
|
| 327 | - * @param array $row Row to render |
|
| 328 | - * @param string $column_name Current row |
|
| 329 | - * |
|
| 330 | - * @return string |
|
| 331 | - */ |
|
| 332 | - protected function maybe_render_actions( $row, $column_name ) { |
|
| 333 | - if ( 'pending' === strtolower( $row[ 'status_name' ] ) ) { |
|
| 334 | - return parent::maybe_render_actions( $row, $column_name ); |
|
| 335 | - } |
|
| 336 | - |
|
| 337 | - return ''; |
|
| 338 | - } |
|
| 339 | - |
|
| 340 | - /** |
|
| 341 | - * Renders admin notifications |
|
| 342 | - * |
|
| 343 | - * Notifications: |
|
| 344 | - * 1. When the maximum number of tasks are being executed simultaneously. |
|
| 345 | - * 2. Notifications when a task is manually executed. |
|
| 346 | - * 3. Tables are missing. |
|
| 347 | - */ |
|
| 348 | - public function display_admin_notices() { |
|
| 349 | - global $wpdb; |
|
| 350 | - |
|
| 351 | - if ( ( is_a( $this->store, 'ActionScheduler_HybridStore' ) || is_a( $this->store, 'ActionScheduler_DBStore' ) ) && apply_filters( 'action_scheduler_enable_recreate_data_store', true ) ) { |
|
| 352 | - $table_list = array( |
|
| 353 | - 'actionscheduler_actions', |
|
| 354 | - 'actionscheduler_logs', |
|
| 355 | - 'actionscheduler_groups', |
|
| 356 | - 'actionscheduler_claims', |
|
| 357 | - ); |
|
| 358 | - |
|
| 359 | - $found_tables = $wpdb->get_col( "SHOW TABLES LIKE '{$wpdb->prefix}actionscheduler%'" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared |
|
| 360 | - foreach ( $table_list as $table_name ) { |
|
| 361 | - if ( ! in_array( $wpdb->prefix . $table_name, $found_tables ) ) { |
|
| 362 | - $this->admin_notices[] = array( |
|
| 363 | - 'class' => 'error', |
|
| 364 | - 'message' => __( 'It appears one or more database tables were missing. Attempting to re-create the missing table(s).' , 'woocommerce' ), |
|
| 365 | - ); |
|
| 366 | - $this->recreate_tables(); |
|
| 367 | - parent::display_admin_notices(); |
|
| 368 | - |
|
| 369 | - return; |
|
| 370 | - } |
|
| 371 | - } |
|
| 372 | - } |
|
| 373 | - |
|
| 374 | - if ( $this->runner->has_maximum_concurrent_batches() ) { |
|
| 375 | - $claim_count = $this->store->get_claim_count(); |
|
| 376 | - $this->admin_notices[] = array( |
|
| 377 | - 'class' => 'updated', |
|
| 378 | - 'message' => sprintf( |
|
| 379 | - /* translators: %s: amount of claims */ |
|
| 380 | - _n( |
|
| 381 | - 'Maximum simultaneous queues already in progress (%s queue). No additional queues will begin processing until the current queues are complete.', |
|
| 382 | - 'Maximum simultaneous queues already in progress (%s queues). No additional queues will begin processing until the current queues are complete.', |
|
| 383 | - $claim_count, |
|
| 384 | - 'woocommerce' |
|
| 385 | - ), |
|
| 386 | - $claim_count |
|
| 387 | - ), |
|
| 388 | - ); |
|
| 389 | - } elseif ( $this->store->has_pending_actions_due() ) { |
|
| 390 | - |
|
| 391 | - $async_request_lock_expiration = ActionScheduler::lock()->get_expiration( 'async-request-runner' ); |
|
| 392 | - |
|
| 393 | - // No lock set or lock expired |
|
| 394 | - if ( false === $async_request_lock_expiration || $async_request_lock_expiration < time() ) { |
|
| 395 | - $in_progress_url = add_query_arg( 'status', 'in-progress', remove_query_arg( 'status' ) ); |
|
| 396 | - /* translators: %s: process URL */ |
|
| 397 | - $async_request_message = sprintf( __( 'A new queue has begun processing. <a href="%s">View actions in-progress »</a>', 'woocommerce' ), esc_url( $in_progress_url ) ); |
|
| 398 | - } else { |
|
| 399 | - /* translators: %d: seconds */ |
|
| 400 | - $async_request_message = sprintf( __( 'The next queue will begin processing in approximately %d seconds.', 'woocommerce' ), $async_request_lock_expiration - time() ); |
|
| 401 | - } |
|
| 402 | - |
|
| 403 | - $this->admin_notices[] = array( |
|
| 404 | - 'class' => 'notice notice-info', |
|
| 405 | - 'message' => $async_request_message, |
|
| 406 | - ); |
|
| 407 | - } |
|
| 408 | - |
|
| 409 | - $notification = get_transient( 'action_scheduler_admin_notice' ); |
|
| 410 | - |
|
| 411 | - if ( is_array( $notification ) ) { |
|
| 412 | - delete_transient( 'action_scheduler_admin_notice' ); |
|
| 413 | - |
|
| 414 | - $action = $this->store->fetch_action( $notification['action_id'] ); |
|
| 415 | - $action_hook_html = '<strong><code>' . $action->get_hook() . '</code></strong>'; |
|
| 416 | - if ( 1 == $notification['success'] ) { |
|
| 417 | - $class = 'updated'; |
|
| 418 | - switch ( $notification['row_action_type'] ) { |
|
| 419 | - case 'run' : |
|
| 420 | - /* translators: %s: action HTML */ |
|
| 421 | - $action_message_html = sprintf( __( 'Successfully executed action: %s', 'woocommerce' ), $action_hook_html ); |
|
| 422 | - break; |
|
| 423 | - case 'cancel' : |
|
| 424 | - /* translators: %s: action HTML */ |
|
| 425 | - $action_message_html = sprintf( __( 'Successfully canceled action: %s', 'woocommerce' ), $action_hook_html ); |
|
| 426 | - break; |
|
| 427 | - default : |
|
| 428 | - /* translators: %s: action HTML */ |
|
| 429 | - $action_message_html = sprintf( __( 'Successfully processed change for action: %s', 'woocommerce' ), $action_hook_html ); |
|
| 430 | - break; |
|
| 431 | - } |
|
| 432 | - } else { |
|
| 433 | - $class = 'error'; |
|
| 434 | - /* translators: 1: action HTML 2: action ID 3: error message */ |
|
| 435 | - $action_message_html = sprintf( __( 'Could not process change for action: "%1$s" (ID: %2$d). Error: %3$s', 'woocommerce' ), $action_hook_html, esc_html( $notification['action_id'] ), esc_html( $notification['error_message'] ) ); |
|
| 436 | - } |
|
| 437 | - |
|
| 438 | - $action_message_html = apply_filters( 'action_scheduler_admin_notice_html', $action_message_html, $action, $notification ); |
|
| 439 | - |
|
| 440 | - $this->admin_notices[] = array( |
|
| 441 | - 'class' => $class, |
|
| 442 | - 'message' => $action_message_html, |
|
| 443 | - ); |
|
| 444 | - } |
|
| 445 | - |
|
| 446 | - parent::display_admin_notices(); |
|
| 447 | - } |
|
| 448 | - |
|
| 449 | - /** |
|
| 450 | - * Prints the scheduled date in a human friendly format. |
|
| 451 | - * |
|
| 452 | - * @param array $row The array representation of the current row of the table |
|
| 453 | - * |
|
| 454 | - * @return string |
|
| 455 | - */ |
|
| 456 | - public function column_schedule( $row ) { |
|
| 457 | - return $this->get_schedule_display_string( $row['schedule'] ); |
|
| 458 | - } |
|
| 459 | - |
|
| 460 | - /** |
|
| 461 | - * Get the scheduled date in a human friendly format. |
|
| 462 | - * |
|
| 463 | - * @param ActionScheduler_Schedule $schedule |
|
| 464 | - * @return string |
|
| 465 | - */ |
|
| 466 | - protected function get_schedule_display_string( ActionScheduler_Schedule $schedule ) { |
|
| 467 | - |
|
| 468 | - $schedule_display_string = ''; |
|
| 469 | - |
|
| 470 | - if ( ! $schedule->get_date() ) { |
|
| 471 | - return '0000-00-00 00:00:00'; |
|
| 472 | - } |
|
| 473 | - |
|
| 474 | - $next_timestamp = $schedule->get_date()->getTimestamp(); |
|
| 475 | - |
|
| 476 | - $schedule_display_string .= $schedule->get_date()->format( 'Y-m-d H:i:s O' ); |
|
| 477 | - $schedule_display_string .= '<br/>'; |
|
| 478 | - |
|
| 479 | - if ( gmdate( 'U' ) > $next_timestamp ) { |
|
| 480 | - /* translators: %s: date interval */ |
|
| 481 | - $schedule_display_string .= sprintf( __( ' (%s ago)', 'woocommerce' ), self::human_interval( gmdate( 'U' ) - $next_timestamp ) ); |
|
| 482 | - } else { |
|
| 483 | - /* translators: %s: date interval */ |
|
| 484 | - $schedule_display_string .= sprintf( __( ' (%s)', 'woocommerce' ), self::human_interval( $next_timestamp - gmdate( 'U' ) ) ); |
|
| 485 | - } |
|
| 486 | - |
|
| 487 | - return $schedule_display_string; |
|
| 488 | - } |
|
| 489 | - |
|
| 490 | - /** |
|
| 491 | - * Bulk delete |
|
| 492 | - * |
|
| 493 | - * Deletes actions based on their ID. This is the handler for the bulk delete. It assumes the data |
|
| 494 | - * properly validated by the callee and it will delete the actions without any extra validation. |
|
| 495 | - * |
|
| 496 | - * @param array $ids |
|
| 497 | - * @param string $ids_sql Inherited and unused |
|
| 498 | - */ |
|
| 499 | - protected function bulk_delete( array $ids, $ids_sql ) { |
|
| 500 | - foreach ( $ids as $id ) { |
|
| 501 | - $this->store->delete_action( $id ); |
|
| 502 | - } |
|
| 503 | - } |
|
| 504 | - |
|
| 505 | - /** |
|
| 506 | - * Implements the logic behind running an action. ActionScheduler_Abstract_ListTable validates the request and their |
|
| 507 | - * parameters are valid. |
|
| 508 | - * |
|
| 509 | - * @param int $action_id |
|
| 510 | - */ |
|
| 511 | - protected function row_action_cancel( $action_id ) { |
|
| 512 | - $this->process_row_action( $action_id, 'cancel' ); |
|
| 513 | - } |
|
| 514 | - |
|
| 515 | - /** |
|
| 516 | - * Implements the logic behind running an action. ActionScheduler_Abstract_ListTable validates the request and their |
|
| 517 | - * parameters are valid. |
|
| 518 | - * |
|
| 519 | - * @param int $action_id |
|
| 520 | - */ |
|
| 521 | - protected function row_action_run( $action_id ) { |
|
| 522 | - $this->process_row_action( $action_id, 'run' ); |
|
| 523 | - } |
|
| 524 | - |
|
| 525 | - /** |
|
| 526 | - * Force the data store schema updates. |
|
| 527 | - */ |
|
| 528 | - protected function recreate_tables() { |
|
| 529 | - if ( is_a( $this->store, 'ActionScheduler_HybridStore' ) ) { |
|
| 530 | - $store = $this->store; |
|
| 531 | - } else { |
|
| 532 | - $store = new ActionScheduler_HybridStore(); |
|
| 533 | - } |
|
| 534 | - add_action( 'action_scheduler/created_table', array( $store, 'set_autoincrement' ), 10, 2 ); |
|
| 535 | - |
|
| 536 | - $store_schema = new ActionScheduler_StoreSchema(); |
|
| 537 | - $logger_schema = new ActionScheduler_LoggerSchema(); |
|
| 538 | - $store_schema->register_tables( true ); |
|
| 539 | - $logger_schema->register_tables( true ); |
|
| 540 | - |
|
| 541 | - remove_action( 'action_scheduler/created_table', array( $store, 'set_autoincrement' ), 10 ); |
|
| 542 | - } |
|
| 543 | - /** |
|
| 544 | - * Implements the logic behind processing an action once an action link is clicked on the list table. |
|
| 545 | - * |
|
| 546 | - * @param int $action_id |
|
| 547 | - * @param string $row_action_type The type of action to perform on the action. |
|
| 548 | - */ |
|
| 549 | - protected function process_row_action( $action_id, $row_action_type ) { |
|
| 550 | - try { |
|
| 551 | - switch ( $row_action_type ) { |
|
| 552 | - case 'run' : |
|
| 553 | - $this->runner->process_action( $action_id, 'Admin List Table' ); |
|
| 554 | - break; |
|
| 555 | - case 'cancel' : |
|
| 556 | - $this->store->cancel_action( $action_id ); |
|
| 557 | - break; |
|
| 558 | - } |
|
| 559 | - $success = 1; |
|
| 560 | - $error_message = ''; |
|
| 561 | - } catch ( Exception $e ) { |
|
| 562 | - $success = 0; |
|
| 563 | - $error_message = $e->getMessage(); |
|
| 564 | - } |
|
| 565 | - |
|
| 566 | - set_transient( 'action_scheduler_admin_notice', compact( 'action_id', 'success', 'error_message', 'row_action_type' ), 30 ); |
|
| 567 | - } |
|
| 568 | - |
|
| 569 | - /** |
|
| 570 | - * {@inheritDoc} |
|
| 571 | - */ |
|
| 572 | - public function prepare_items() { |
|
| 573 | - $this->prepare_column_headers(); |
|
| 574 | - |
|
| 575 | - $per_page = $this->get_items_per_page( $this->get_per_page_option_name(), $this->items_per_page ); |
|
| 576 | - |
|
| 577 | - $query = array( |
|
| 578 | - 'per_page' => $per_page, |
|
| 579 | - 'offset' => $this->get_items_offset(), |
|
| 580 | - 'status' => $this->get_request_status(), |
|
| 581 | - 'orderby' => $this->get_request_orderby(), |
|
| 582 | - 'order' => $this->get_request_order(), |
|
| 583 | - 'search' => $this->get_request_search_query(), |
|
| 584 | - ); |
|
| 585 | - |
|
| 586 | - $this->items = array(); |
|
| 587 | - |
|
| 588 | - $total_items = $this->store->query_actions( $query, 'count' ); |
|
| 589 | - |
|
| 590 | - $status_labels = $this->store->get_status_labels(); |
|
| 591 | - |
|
| 592 | - foreach ( $this->store->query_actions( $query ) as $action_id ) { |
|
| 593 | - try { |
|
| 594 | - $action = $this->store->fetch_action( $action_id ); |
|
| 595 | - } catch ( Exception $e ) { |
|
| 596 | - continue; |
|
| 597 | - } |
|
| 598 | - if ( is_a( $action, 'ActionScheduler_NullAction' ) ) { |
|
| 599 | - continue; |
|
| 600 | - } |
|
| 601 | - $this->items[ $action_id ] = array( |
|
| 602 | - 'ID' => $action_id, |
|
| 603 | - 'hook' => $action->get_hook(), |
|
| 604 | - 'status_name' => $this->store->get_status( $action_id ), |
|
| 605 | - 'status' => $status_labels[ $this->store->get_status( $action_id ) ], |
|
| 606 | - 'args' => $action->get_args(), |
|
| 607 | - 'group' => $action->get_group(), |
|
| 608 | - 'log_entries' => $this->logger->get_logs( $action_id ), |
|
| 609 | - 'claim_id' => $this->store->get_claim_id( $action_id ), |
|
| 610 | - 'recurrence' => $this->get_recurrence( $action ), |
|
| 611 | - 'schedule' => $action->get_schedule(), |
|
| 612 | - ); |
|
| 613 | - } |
|
| 614 | - |
|
| 615 | - $this->set_pagination_args( array( |
|
| 616 | - 'total_items' => $total_items, |
|
| 617 | - 'per_page' => $per_page, |
|
| 618 | - 'total_pages' => ceil( $total_items / $per_page ), |
|
| 619 | - ) ); |
|
| 620 | - } |
|
| 621 | - |
|
| 622 | - /** |
|
| 623 | - * Prints the available statuses so the user can click to filter. |
|
| 624 | - */ |
|
| 625 | - protected function display_filter_by_status() { |
|
| 626 | - $this->status_counts = $this->store->action_counts(); |
|
| 627 | - parent::display_filter_by_status(); |
|
| 628 | - } |
|
| 629 | - |
|
| 630 | - /** |
|
| 631 | - * Get the text to display in the search box on the list table. |
|
| 632 | - */ |
|
| 633 | - protected function get_search_box_button_text() { |
|
| 634 | - return __( 'Search hook, args and claim ID', 'woocommerce' ); |
|
| 635 | - } |
|
| 636 | - |
|
| 637 | - /** |
|
| 638 | - * {@inheritDoc} |
|
| 639 | - */ |
|
| 640 | - protected function get_per_page_option_name() { |
|
| 641 | - return str_replace( '-', '_', $this->screen->id ) . '_per_page'; |
|
| 642 | - } |
|
| 9 | + /** |
|
| 10 | + * The package name. |
|
| 11 | + * |
|
| 12 | + * @var string |
|
| 13 | + */ |
|
| 14 | + protected $package = 'action-scheduler'; |
|
| 15 | + |
|
| 16 | + /** |
|
| 17 | + * Columns to show (name => label). |
|
| 18 | + * |
|
| 19 | + * @var array |
|
| 20 | + */ |
|
| 21 | + protected $columns = array(); |
|
| 22 | + |
|
| 23 | + /** |
|
| 24 | + * Actions (name => label). |
|
| 25 | + * |
|
| 26 | + * @var array |
|
| 27 | + */ |
|
| 28 | + protected $row_actions = array(); |
|
| 29 | + |
|
| 30 | + /** |
|
| 31 | + * The active data stores |
|
| 32 | + * |
|
| 33 | + * @var ActionScheduler_Store |
|
| 34 | + */ |
|
| 35 | + protected $store; |
|
| 36 | + |
|
| 37 | + /** |
|
| 38 | + * A logger to use for getting action logs to display |
|
| 39 | + * |
|
| 40 | + * @var ActionScheduler_Logger |
|
| 41 | + */ |
|
| 42 | + protected $logger; |
|
| 43 | + |
|
| 44 | + /** |
|
| 45 | + * A ActionScheduler_QueueRunner runner instance (or child class) |
|
| 46 | + * |
|
| 47 | + * @var ActionScheduler_QueueRunner |
|
| 48 | + */ |
|
| 49 | + protected $runner; |
|
| 50 | + |
|
| 51 | + /** |
|
| 52 | + * Bulk actions. The key of the array is the method name of the implementation: |
|
| 53 | + * |
|
| 54 | + * bulk_<key>(array $ids, string $sql_in). |
|
| 55 | + * |
|
| 56 | + * See the comments in the parent class for further details |
|
| 57 | + * |
|
| 58 | + * @var array |
|
| 59 | + */ |
|
| 60 | + protected $bulk_actions = array(); |
|
| 61 | + |
|
| 62 | + /** |
|
| 63 | + * Flag variable to render our notifications, if any, once. |
|
| 64 | + * |
|
| 65 | + * @var bool |
|
| 66 | + */ |
|
| 67 | + protected static $did_notification = false; |
|
| 68 | + |
|
| 69 | + /** |
|
| 70 | + * Array of seconds for common time periods, like week or month, alongside an internationalised string representation, i.e. "Day" or "Days" |
|
| 71 | + * |
|
| 72 | + * @var array |
|
| 73 | + */ |
|
| 74 | + private static $time_periods; |
|
| 75 | + |
|
| 76 | + /** |
|
| 77 | + * Sets the current data store object into `store->action` and initialises the object. |
|
| 78 | + * |
|
| 79 | + * @param ActionScheduler_Store $store |
|
| 80 | + * @param ActionScheduler_Logger $logger |
|
| 81 | + * @param ActionScheduler_QueueRunner $runner |
|
| 82 | + */ |
|
| 83 | + public function __construct( ActionScheduler_Store $store, ActionScheduler_Logger $logger, ActionScheduler_QueueRunner $runner ) { |
|
| 84 | + |
|
| 85 | + $this->store = $store; |
|
| 86 | + $this->logger = $logger; |
|
| 87 | + $this->runner = $runner; |
|
| 88 | + |
|
| 89 | + $this->table_header = __( 'Scheduled Actions', 'woocommerce' ); |
|
| 90 | + |
|
| 91 | + $this->bulk_actions = array( |
|
| 92 | + 'delete' => __( 'Delete', 'woocommerce' ), |
|
| 93 | + ); |
|
| 94 | + |
|
| 95 | + $this->columns = array( |
|
| 96 | + 'hook' => __( 'Hook', 'woocommerce' ), |
|
| 97 | + 'status' => __( 'Status', 'woocommerce' ), |
|
| 98 | + 'args' => __( 'Arguments', 'woocommerce' ), |
|
| 99 | + 'group' => __( 'Group', 'woocommerce' ), |
|
| 100 | + 'recurrence' => __( 'Recurrence', 'woocommerce' ), |
|
| 101 | + 'schedule' => __( 'Scheduled Date', 'woocommerce' ), |
|
| 102 | + 'log_entries' => __( 'Log', 'woocommerce' ), |
|
| 103 | + ); |
|
| 104 | + |
|
| 105 | + $this->sort_by = array( |
|
| 106 | + 'schedule', |
|
| 107 | + 'hook', |
|
| 108 | + 'group', |
|
| 109 | + ); |
|
| 110 | + |
|
| 111 | + $this->search_by = array( |
|
| 112 | + 'hook', |
|
| 113 | + 'args', |
|
| 114 | + 'claim_id', |
|
| 115 | + ); |
|
| 116 | + |
|
| 117 | + $request_status = $this->get_request_status(); |
|
| 118 | + |
|
| 119 | + if ( empty( $request_status ) ) { |
|
| 120 | + $this->sort_by[] = 'status'; |
|
| 121 | + } elseif ( in_array( $request_status, array( 'in-progress', 'failed' ) ) ) { |
|
| 122 | + $this->columns += array( 'claim_id' => __( 'Claim ID', 'woocommerce' ) ); |
|
| 123 | + $this->sort_by[] = 'claim_id'; |
|
| 124 | + } |
|
| 125 | + |
|
| 126 | + $this->row_actions = array( |
|
| 127 | + 'hook' => array( |
|
| 128 | + 'run' => array( |
|
| 129 | + 'name' => __( 'Run', 'woocommerce' ), |
|
| 130 | + 'desc' => __( 'Process the action now as if it were run as part of a queue', 'woocommerce' ), |
|
| 131 | + ), |
|
| 132 | + 'cancel' => array( |
|
| 133 | + 'name' => __( 'Cancel', 'woocommerce' ), |
|
| 134 | + 'desc' => __( 'Cancel the action now to avoid it being run in future', 'woocommerce' ), |
|
| 135 | + 'class' => 'cancel trash', |
|
| 136 | + ), |
|
| 137 | + ), |
|
| 138 | + ); |
|
| 139 | + |
|
| 140 | + self::$time_periods = array( |
|
| 141 | + array( |
|
| 142 | + 'seconds' => YEAR_IN_SECONDS, |
|
| 143 | + /* translators: %s: amount of time */ |
|
| 144 | + 'names' => _n_noop( '%s year', '%s years', 'woocommerce' ), |
|
| 145 | + ), |
|
| 146 | + array( |
|
| 147 | + 'seconds' => MONTH_IN_SECONDS, |
|
| 148 | + /* translators: %s: amount of time */ |
|
| 149 | + 'names' => _n_noop( '%s month', '%s months', 'woocommerce' ), |
|
| 150 | + ), |
|
| 151 | + array( |
|
| 152 | + 'seconds' => WEEK_IN_SECONDS, |
|
| 153 | + /* translators: %s: amount of time */ |
|
| 154 | + 'names' => _n_noop( '%s week', '%s weeks', 'woocommerce' ), |
|
| 155 | + ), |
|
| 156 | + array( |
|
| 157 | + 'seconds' => DAY_IN_SECONDS, |
|
| 158 | + /* translators: %s: amount of time */ |
|
| 159 | + 'names' => _n_noop( '%s day', '%s days', 'woocommerce' ), |
|
| 160 | + ), |
|
| 161 | + array( |
|
| 162 | + 'seconds' => HOUR_IN_SECONDS, |
|
| 163 | + /* translators: %s: amount of time */ |
|
| 164 | + 'names' => _n_noop( '%s hour', '%s hours', 'woocommerce' ), |
|
| 165 | + ), |
|
| 166 | + array( |
|
| 167 | + 'seconds' => MINUTE_IN_SECONDS, |
|
| 168 | + /* translators: %s: amount of time */ |
|
| 169 | + 'names' => _n_noop( '%s minute', '%s minutes', 'woocommerce' ), |
|
| 170 | + ), |
|
| 171 | + array( |
|
| 172 | + 'seconds' => 1, |
|
| 173 | + /* translators: %s: amount of time */ |
|
| 174 | + 'names' => _n_noop( '%s second', '%s seconds', 'woocommerce' ), |
|
| 175 | + ), |
|
| 176 | + ); |
|
| 177 | + |
|
| 178 | + parent::__construct( |
|
| 179 | + array( |
|
| 180 | + 'singular' => 'action-scheduler', |
|
| 181 | + 'plural' => 'action-scheduler', |
|
| 182 | + 'ajax' => false, |
|
| 183 | + ) |
|
| 184 | + ); |
|
| 185 | + |
|
| 186 | + add_screen_option( |
|
| 187 | + 'per_page', |
|
| 188 | + array( |
|
| 189 | + 'default' => $this->items_per_page, |
|
| 190 | + ) |
|
| 191 | + ); |
|
| 192 | + |
|
| 193 | + add_filter( 'set_screen_option_' . $this->get_per_page_option_name(), array( $this, 'set_items_per_page_option' ), 10, 3 ); |
|
| 194 | + set_screen_options(); |
|
| 195 | + } |
|
| 196 | + |
|
| 197 | + /** |
|
| 198 | + * Handles setting the items_per_page option for this screen. |
|
| 199 | + * |
|
| 200 | + * @param mixed $status Default false (to skip saving the current option). |
|
| 201 | + * @param string $option Screen option name. |
|
| 202 | + * @param int $value Screen option value. |
|
| 203 | + * @return int |
|
| 204 | + */ |
|
| 205 | + public function set_items_per_page_option( $status, $option, $value ) { |
|
| 206 | + return $value; |
|
| 207 | + } |
|
| 208 | + /** |
|
| 209 | + * Convert an interval of seconds into a two part human friendly string. |
|
| 210 | + * |
|
| 211 | + * The WordPress human_time_diff() function only calculates the time difference to one degree, meaning |
|
| 212 | + * even if an action is 1 day and 11 hours away, it will display "1 day". This function goes one step |
|
| 213 | + * further to display two degrees of accuracy. |
|
| 214 | + * |
|
| 215 | + * Inspired by the Crontrol::interval() function by Edward Dale: https://wordpress.org/plugins/wp-crontrol/ |
|
| 216 | + * |
|
| 217 | + * @param int $interval A interval in seconds. |
|
| 218 | + * @param int $periods_to_include Depth of time periods to include, e.g. for an interval of 70, and $periods_to_include of 2, both minutes and seconds would be included. With a value of 1, only minutes would be included. |
|
| 219 | + * @return string A human friendly string representation of the interval. |
|
| 220 | + */ |
|
| 221 | + private static function human_interval( $interval, $periods_to_include = 2 ) { |
|
| 222 | + |
|
| 223 | + if ( $interval <= 0 ) { |
|
| 224 | + return __( 'Now!', 'woocommerce' ); |
|
| 225 | + } |
|
| 226 | + |
|
| 227 | + $output = ''; |
|
| 228 | + |
|
| 229 | + for ( $time_period_index = 0, $periods_included = 0, $seconds_remaining = $interval; $time_period_index < count( self::$time_periods ) && $seconds_remaining > 0 && $periods_included < $periods_to_include; $time_period_index++ ) { |
|
| 230 | + |
|
| 231 | + $periods_in_interval = floor( $seconds_remaining / self::$time_periods[ $time_period_index ]['seconds'] ); |
|
| 232 | + |
|
| 233 | + if ( $periods_in_interval > 0 ) { |
|
| 234 | + if ( ! empty( $output ) ) { |
|
| 235 | + $output .= ' '; |
|
| 236 | + } |
|
| 237 | + $output .= sprintf( _n( self::$time_periods[ $time_period_index ]['names'][0], self::$time_periods[ $time_period_index ]['names'][1], $periods_in_interval, 'woocommerce' ), $periods_in_interval ); |
|
| 238 | + $seconds_remaining -= $periods_in_interval * self::$time_periods[ $time_period_index ]['seconds']; |
|
| 239 | + $periods_included++; |
|
| 240 | + } |
|
| 241 | + } |
|
| 242 | + |
|
| 243 | + return $output; |
|
| 244 | + } |
|
| 245 | + |
|
| 246 | + /** |
|
| 247 | + * Returns the recurrence of an action or 'Non-repeating'. The output is human readable. |
|
| 248 | + * |
|
| 249 | + * @param ActionScheduler_Action $action |
|
| 250 | + * |
|
| 251 | + * @return string |
|
| 252 | + */ |
|
| 253 | + protected function get_recurrence( $action ) { |
|
| 254 | + $schedule = $action->get_schedule(); |
|
| 255 | + if ( $schedule->is_recurring() ) { |
|
| 256 | + $recurrence = $schedule->get_recurrence(); |
|
| 257 | + |
|
| 258 | + if ( is_numeric( $recurrence ) ) { |
|
| 259 | + /* translators: %s: time interval */ |
|
| 260 | + return sprintf( __( 'Every %s', 'woocommerce' ), self::human_interval( $recurrence ) ); |
|
| 261 | + } else { |
|
| 262 | + return $recurrence; |
|
| 263 | + } |
|
| 264 | + } |
|
| 265 | + |
|
| 266 | + return __( 'Non-repeating', 'woocommerce' ); |
|
| 267 | + } |
|
| 268 | + |
|
| 269 | + /** |
|
| 270 | + * Serializes the argument of an action to render it in a human friendly format. |
|
| 271 | + * |
|
| 272 | + * @param array $row The array representation of the current row of the table |
|
| 273 | + * |
|
| 274 | + * @return string |
|
| 275 | + */ |
|
| 276 | + public function column_args( array $row ) { |
|
| 277 | + if ( empty( $row['args'] ) ) { |
|
| 278 | + return apply_filters( 'action_scheduler_list_table_column_args', '', $row ); |
|
| 279 | + } |
|
| 280 | + |
|
| 281 | + $row_html = '<ul>'; |
|
| 282 | + foreach ( $row['args'] as $key => $value ) { |
|
| 283 | + $row_html .= sprintf( '<li><code>%s => %s</code></li>', esc_html( var_export( $key, true ) ), esc_html( var_export( $value, true ) ) ); |
|
| 284 | + } |
|
| 285 | + $row_html .= '</ul>'; |
|
| 286 | + |
|
| 287 | + return apply_filters( 'action_scheduler_list_table_column_args', $row_html, $row ); |
|
| 288 | + } |
|
| 289 | + |
|
| 290 | + /** |
|
| 291 | + * Prints the logs entries inline. We do so to avoid loading Javascript and other hacks to show it in a modal. |
|
| 292 | + * |
|
| 293 | + * @param array $row Action array. |
|
| 294 | + * @return string |
|
| 295 | + */ |
|
| 296 | + public function column_log_entries( array $row ) { |
|
| 297 | + |
|
| 298 | + $log_entries_html = '<ol>'; |
|
| 299 | + |
|
| 300 | + $timezone = new DateTimezone( 'UTC' ); |
|
| 301 | + |
|
| 302 | + foreach ( $row['log_entries'] as $log_entry ) { |
|
| 303 | + $log_entries_html .= $this->get_log_entry_html( $log_entry, $timezone ); |
|
| 304 | + } |
|
| 305 | + |
|
| 306 | + $log_entries_html .= '</ol>'; |
|
| 307 | + |
|
| 308 | + return $log_entries_html; |
|
| 309 | + } |
|
| 310 | + |
|
| 311 | + /** |
|
| 312 | + * Prints the logs entries inline. We do so to avoid loading Javascript and other hacks to show it in a modal. |
|
| 313 | + * |
|
| 314 | + * @param ActionScheduler_LogEntry $log_entry |
|
| 315 | + * @param DateTimezone $timezone |
|
| 316 | + * @return string |
|
| 317 | + */ |
|
| 318 | + protected function get_log_entry_html( ActionScheduler_LogEntry $log_entry, DateTimezone $timezone ) { |
|
| 319 | + $date = $log_entry->get_date(); |
|
| 320 | + $date->setTimezone( $timezone ); |
|
| 321 | + return sprintf( '<li><strong>%s</strong><br/>%s</li>', esc_html( $date->format( 'Y-m-d H:i:s O' ) ), esc_html( $log_entry->get_message() ) ); |
|
| 322 | + } |
|
| 323 | + |
|
| 324 | + /** |
|
| 325 | + * Only display row actions for pending actions. |
|
| 326 | + * |
|
| 327 | + * @param array $row Row to render |
|
| 328 | + * @param string $column_name Current row |
|
| 329 | + * |
|
| 330 | + * @return string |
|
| 331 | + */ |
|
| 332 | + protected function maybe_render_actions( $row, $column_name ) { |
|
| 333 | + if ( 'pending' === strtolower( $row[ 'status_name' ] ) ) { |
|
| 334 | + return parent::maybe_render_actions( $row, $column_name ); |
|
| 335 | + } |
|
| 336 | + |
|
| 337 | + return ''; |
|
| 338 | + } |
|
| 339 | + |
|
| 340 | + /** |
|
| 341 | + * Renders admin notifications |
|
| 342 | + * |
|
| 343 | + * Notifications: |
|
| 344 | + * 1. When the maximum number of tasks are being executed simultaneously. |
|
| 345 | + * 2. Notifications when a task is manually executed. |
|
| 346 | + * 3. Tables are missing. |
|
| 347 | + */ |
|
| 348 | + public function display_admin_notices() { |
|
| 349 | + global $wpdb; |
|
| 350 | + |
|
| 351 | + if ( ( is_a( $this->store, 'ActionScheduler_HybridStore' ) || is_a( $this->store, 'ActionScheduler_DBStore' ) ) && apply_filters( 'action_scheduler_enable_recreate_data_store', true ) ) { |
|
| 352 | + $table_list = array( |
|
| 353 | + 'actionscheduler_actions', |
|
| 354 | + 'actionscheduler_logs', |
|
| 355 | + 'actionscheduler_groups', |
|
| 356 | + 'actionscheduler_claims', |
|
| 357 | + ); |
|
| 358 | + |
|
| 359 | + $found_tables = $wpdb->get_col( "SHOW TABLES LIKE '{$wpdb->prefix}actionscheduler%'" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared |
|
| 360 | + foreach ( $table_list as $table_name ) { |
|
| 361 | + if ( ! in_array( $wpdb->prefix . $table_name, $found_tables ) ) { |
|
| 362 | + $this->admin_notices[] = array( |
|
| 363 | + 'class' => 'error', |
|
| 364 | + 'message' => __( 'It appears one or more database tables were missing. Attempting to re-create the missing table(s).' , 'woocommerce' ), |
|
| 365 | + ); |
|
| 366 | + $this->recreate_tables(); |
|
| 367 | + parent::display_admin_notices(); |
|
| 368 | + |
|
| 369 | + return; |
|
| 370 | + } |
|
| 371 | + } |
|
| 372 | + } |
|
| 373 | + |
|
| 374 | + if ( $this->runner->has_maximum_concurrent_batches() ) { |
|
| 375 | + $claim_count = $this->store->get_claim_count(); |
|
| 376 | + $this->admin_notices[] = array( |
|
| 377 | + 'class' => 'updated', |
|
| 378 | + 'message' => sprintf( |
|
| 379 | + /* translators: %s: amount of claims */ |
|
| 380 | + _n( |
|
| 381 | + 'Maximum simultaneous queues already in progress (%s queue). No additional queues will begin processing until the current queues are complete.', |
|
| 382 | + 'Maximum simultaneous queues already in progress (%s queues). No additional queues will begin processing until the current queues are complete.', |
|
| 383 | + $claim_count, |
|
| 384 | + 'woocommerce' |
|
| 385 | + ), |
|
| 386 | + $claim_count |
|
| 387 | + ), |
|
| 388 | + ); |
|
| 389 | + } elseif ( $this->store->has_pending_actions_due() ) { |
|
| 390 | + |
|
| 391 | + $async_request_lock_expiration = ActionScheduler::lock()->get_expiration( 'async-request-runner' ); |
|
| 392 | + |
|
| 393 | + // No lock set or lock expired |
|
| 394 | + if ( false === $async_request_lock_expiration || $async_request_lock_expiration < time() ) { |
|
| 395 | + $in_progress_url = add_query_arg( 'status', 'in-progress', remove_query_arg( 'status' ) ); |
|
| 396 | + /* translators: %s: process URL */ |
|
| 397 | + $async_request_message = sprintf( __( 'A new queue has begun processing. <a href="%s">View actions in-progress »</a>', 'woocommerce' ), esc_url( $in_progress_url ) ); |
|
| 398 | + } else { |
|
| 399 | + /* translators: %d: seconds */ |
|
| 400 | + $async_request_message = sprintf( __( 'The next queue will begin processing in approximately %d seconds.', 'woocommerce' ), $async_request_lock_expiration - time() ); |
|
| 401 | + } |
|
| 402 | + |
|
| 403 | + $this->admin_notices[] = array( |
|
| 404 | + 'class' => 'notice notice-info', |
|
| 405 | + 'message' => $async_request_message, |
|
| 406 | + ); |
|
| 407 | + } |
|
| 408 | + |
|
| 409 | + $notification = get_transient( 'action_scheduler_admin_notice' ); |
|
| 410 | + |
|
| 411 | + if ( is_array( $notification ) ) { |
|
| 412 | + delete_transient( 'action_scheduler_admin_notice' ); |
|
| 413 | + |
|
| 414 | + $action = $this->store->fetch_action( $notification['action_id'] ); |
|
| 415 | + $action_hook_html = '<strong><code>' . $action->get_hook() . '</code></strong>'; |
|
| 416 | + if ( 1 == $notification['success'] ) { |
|
| 417 | + $class = 'updated'; |
|
| 418 | + switch ( $notification['row_action_type'] ) { |
|
| 419 | + case 'run' : |
|
| 420 | + /* translators: %s: action HTML */ |
|
| 421 | + $action_message_html = sprintf( __( 'Successfully executed action: %s', 'woocommerce' ), $action_hook_html ); |
|
| 422 | + break; |
|
| 423 | + case 'cancel' : |
|
| 424 | + /* translators: %s: action HTML */ |
|
| 425 | + $action_message_html = sprintf( __( 'Successfully canceled action: %s', 'woocommerce' ), $action_hook_html ); |
|
| 426 | + break; |
|
| 427 | + default : |
|
| 428 | + /* translators: %s: action HTML */ |
|
| 429 | + $action_message_html = sprintf( __( 'Successfully processed change for action: %s', 'woocommerce' ), $action_hook_html ); |
|
| 430 | + break; |
|
| 431 | + } |
|
| 432 | + } else { |
|
| 433 | + $class = 'error'; |
|
| 434 | + /* translators: 1: action HTML 2: action ID 3: error message */ |
|
| 435 | + $action_message_html = sprintf( __( 'Could not process change for action: "%1$s" (ID: %2$d). Error: %3$s', 'woocommerce' ), $action_hook_html, esc_html( $notification['action_id'] ), esc_html( $notification['error_message'] ) ); |
|
| 436 | + } |
|
| 437 | + |
|
| 438 | + $action_message_html = apply_filters( 'action_scheduler_admin_notice_html', $action_message_html, $action, $notification ); |
|
| 439 | + |
|
| 440 | + $this->admin_notices[] = array( |
|
| 441 | + 'class' => $class, |
|
| 442 | + 'message' => $action_message_html, |
|
| 443 | + ); |
|
| 444 | + } |
|
| 445 | + |
|
| 446 | + parent::display_admin_notices(); |
|
| 447 | + } |
|
| 448 | + |
|
| 449 | + /** |
|
| 450 | + * Prints the scheduled date in a human friendly format. |
|
| 451 | + * |
|
| 452 | + * @param array $row The array representation of the current row of the table |
|
| 453 | + * |
|
| 454 | + * @return string |
|
| 455 | + */ |
|
| 456 | + public function column_schedule( $row ) { |
|
| 457 | + return $this->get_schedule_display_string( $row['schedule'] ); |
|
| 458 | + } |
|
| 459 | + |
|
| 460 | + /** |
|
| 461 | + * Get the scheduled date in a human friendly format. |
|
| 462 | + * |
|
| 463 | + * @param ActionScheduler_Schedule $schedule |
|
| 464 | + * @return string |
|
| 465 | + */ |
|
| 466 | + protected function get_schedule_display_string( ActionScheduler_Schedule $schedule ) { |
|
| 467 | + |
|
| 468 | + $schedule_display_string = ''; |
|
| 469 | + |
|
| 470 | + if ( ! $schedule->get_date() ) { |
|
| 471 | + return '0000-00-00 00:00:00'; |
|
| 472 | + } |
|
| 473 | + |
|
| 474 | + $next_timestamp = $schedule->get_date()->getTimestamp(); |
|
| 475 | + |
|
| 476 | + $schedule_display_string .= $schedule->get_date()->format( 'Y-m-d H:i:s O' ); |
|
| 477 | + $schedule_display_string .= '<br/>'; |
|
| 478 | + |
|
| 479 | + if ( gmdate( 'U' ) > $next_timestamp ) { |
|
| 480 | + /* translators: %s: date interval */ |
|
| 481 | + $schedule_display_string .= sprintf( __( ' (%s ago)', 'woocommerce' ), self::human_interval( gmdate( 'U' ) - $next_timestamp ) ); |
|
| 482 | + } else { |
|
| 483 | + /* translators: %s: date interval */ |
|
| 484 | + $schedule_display_string .= sprintf( __( ' (%s)', 'woocommerce' ), self::human_interval( $next_timestamp - gmdate( 'U' ) ) ); |
|
| 485 | + } |
|
| 486 | + |
|
| 487 | + return $schedule_display_string; |
|
| 488 | + } |
|
| 489 | + |
|
| 490 | + /** |
|
| 491 | + * Bulk delete |
|
| 492 | + * |
|
| 493 | + * Deletes actions based on their ID. This is the handler for the bulk delete. It assumes the data |
|
| 494 | + * properly validated by the callee and it will delete the actions without any extra validation. |
|
| 495 | + * |
|
| 496 | + * @param array $ids |
|
| 497 | + * @param string $ids_sql Inherited and unused |
|
| 498 | + */ |
|
| 499 | + protected function bulk_delete( array $ids, $ids_sql ) { |
|
| 500 | + foreach ( $ids as $id ) { |
|
| 501 | + $this->store->delete_action( $id ); |
|
| 502 | + } |
|
| 503 | + } |
|
| 504 | + |
|
| 505 | + /** |
|
| 506 | + * Implements the logic behind running an action. ActionScheduler_Abstract_ListTable validates the request and their |
|
| 507 | + * parameters are valid. |
|
| 508 | + * |
|
| 509 | + * @param int $action_id |
|
| 510 | + */ |
|
| 511 | + protected function row_action_cancel( $action_id ) { |
|
| 512 | + $this->process_row_action( $action_id, 'cancel' ); |
|
| 513 | + } |
|
| 514 | + |
|
| 515 | + /** |
|
| 516 | + * Implements the logic behind running an action. ActionScheduler_Abstract_ListTable validates the request and their |
|
| 517 | + * parameters are valid. |
|
| 518 | + * |
|
| 519 | + * @param int $action_id |
|
| 520 | + */ |
|
| 521 | + protected function row_action_run( $action_id ) { |
|
| 522 | + $this->process_row_action( $action_id, 'run' ); |
|
| 523 | + } |
|
| 524 | + |
|
| 525 | + /** |
|
| 526 | + * Force the data store schema updates. |
|
| 527 | + */ |
|
| 528 | + protected function recreate_tables() { |
|
| 529 | + if ( is_a( $this->store, 'ActionScheduler_HybridStore' ) ) { |
|
| 530 | + $store = $this->store; |
|
| 531 | + } else { |
|
| 532 | + $store = new ActionScheduler_HybridStore(); |
|
| 533 | + } |
|
| 534 | + add_action( 'action_scheduler/created_table', array( $store, 'set_autoincrement' ), 10, 2 ); |
|
| 535 | + |
|
| 536 | + $store_schema = new ActionScheduler_StoreSchema(); |
|
| 537 | + $logger_schema = new ActionScheduler_LoggerSchema(); |
|
| 538 | + $store_schema->register_tables( true ); |
|
| 539 | + $logger_schema->register_tables( true ); |
|
| 540 | + |
|
| 541 | + remove_action( 'action_scheduler/created_table', array( $store, 'set_autoincrement' ), 10 ); |
|
| 542 | + } |
|
| 543 | + /** |
|
| 544 | + * Implements the logic behind processing an action once an action link is clicked on the list table. |
|
| 545 | + * |
|
| 546 | + * @param int $action_id |
|
| 547 | + * @param string $row_action_type The type of action to perform on the action. |
|
| 548 | + */ |
|
| 549 | + protected function process_row_action( $action_id, $row_action_type ) { |
|
| 550 | + try { |
|
| 551 | + switch ( $row_action_type ) { |
|
| 552 | + case 'run' : |
|
| 553 | + $this->runner->process_action( $action_id, 'Admin List Table' ); |
|
| 554 | + break; |
|
| 555 | + case 'cancel' : |
|
| 556 | + $this->store->cancel_action( $action_id ); |
|
| 557 | + break; |
|
| 558 | + } |
|
| 559 | + $success = 1; |
|
| 560 | + $error_message = ''; |
|
| 561 | + } catch ( Exception $e ) { |
|
| 562 | + $success = 0; |
|
| 563 | + $error_message = $e->getMessage(); |
|
| 564 | + } |
|
| 565 | + |
|
| 566 | + set_transient( 'action_scheduler_admin_notice', compact( 'action_id', 'success', 'error_message', 'row_action_type' ), 30 ); |
|
| 567 | + } |
|
| 568 | + |
|
| 569 | + /** |
|
| 570 | + * {@inheritDoc} |
|
| 571 | + */ |
|
| 572 | + public function prepare_items() { |
|
| 573 | + $this->prepare_column_headers(); |
|
| 574 | + |
|
| 575 | + $per_page = $this->get_items_per_page( $this->get_per_page_option_name(), $this->items_per_page ); |
|
| 576 | + |
|
| 577 | + $query = array( |
|
| 578 | + 'per_page' => $per_page, |
|
| 579 | + 'offset' => $this->get_items_offset(), |
|
| 580 | + 'status' => $this->get_request_status(), |
|
| 581 | + 'orderby' => $this->get_request_orderby(), |
|
| 582 | + 'order' => $this->get_request_order(), |
|
| 583 | + 'search' => $this->get_request_search_query(), |
|
| 584 | + ); |
|
| 585 | + |
|
| 586 | + $this->items = array(); |
|
| 587 | + |
|
| 588 | + $total_items = $this->store->query_actions( $query, 'count' ); |
|
| 589 | + |
|
| 590 | + $status_labels = $this->store->get_status_labels(); |
|
| 591 | + |
|
| 592 | + foreach ( $this->store->query_actions( $query ) as $action_id ) { |
|
| 593 | + try { |
|
| 594 | + $action = $this->store->fetch_action( $action_id ); |
|
| 595 | + } catch ( Exception $e ) { |
|
| 596 | + continue; |
|
| 597 | + } |
|
| 598 | + if ( is_a( $action, 'ActionScheduler_NullAction' ) ) { |
|
| 599 | + continue; |
|
| 600 | + } |
|
| 601 | + $this->items[ $action_id ] = array( |
|
| 602 | + 'ID' => $action_id, |
|
| 603 | + 'hook' => $action->get_hook(), |
|
| 604 | + 'status_name' => $this->store->get_status( $action_id ), |
|
| 605 | + 'status' => $status_labels[ $this->store->get_status( $action_id ) ], |
|
| 606 | + 'args' => $action->get_args(), |
|
| 607 | + 'group' => $action->get_group(), |
|
| 608 | + 'log_entries' => $this->logger->get_logs( $action_id ), |
|
| 609 | + 'claim_id' => $this->store->get_claim_id( $action_id ), |
|
| 610 | + 'recurrence' => $this->get_recurrence( $action ), |
|
| 611 | + 'schedule' => $action->get_schedule(), |
|
| 612 | + ); |
|
| 613 | + } |
|
| 614 | + |
|
| 615 | + $this->set_pagination_args( array( |
|
| 616 | + 'total_items' => $total_items, |
|
| 617 | + 'per_page' => $per_page, |
|
| 618 | + 'total_pages' => ceil( $total_items / $per_page ), |
|
| 619 | + ) ); |
|
| 620 | + } |
|
| 621 | + |
|
| 622 | + /** |
|
| 623 | + * Prints the available statuses so the user can click to filter. |
|
| 624 | + */ |
|
| 625 | + protected function display_filter_by_status() { |
|
| 626 | + $this->status_counts = $this->store->action_counts(); |
|
| 627 | + parent::display_filter_by_status(); |
|
| 628 | + } |
|
| 629 | + |
|
| 630 | + /** |
|
| 631 | + * Get the text to display in the search box on the list table. |
|
| 632 | + */ |
|
| 633 | + protected function get_search_box_button_text() { |
|
| 634 | + return __( 'Search hook, args and claim ID', 'woocommerce' ); |
|
| 635 | + } |
|
| 636 | + |
|
| 637 | + /** |
|
| 638 | + * {@inheritDoc} |
|
| 639 | + */ |
|
| 640 | + protected function get_per_page_option_name() { |
|
| 641 | + return str_replace( '-', '_', $this->screen->id ) . '_per_page'; |
|
| 642 | + } |
|
| 643 | 643 | } |
@@ -5,54 +5,54 @@ |
||
| 5 | 5 | * @codeCoverageIgnore |
| 6 | 6 | */ |
| 7 | 7 | class ActionScheduler_wpPostStore_PostStatusRegistrar { |
| 8 | - public function register() { |
|
| 9 | - register_post_status( ActionScheduler_Store::STATUS_RUNNING, array_merge( $this->post_status_args(), $this->post_status_running_labels() ) ); |
|
| 10 | - register_post_status( ActionScheduler_Store::STATUS_FAILED, array_merge( $this->post_status_args(), $this->post_status_failed_labels() ) ); |
|
| 11 | - } |
|
| 8 | + public function register() { |
|
| 9 | + register_post_status( ActionScheduler_Store::STATUS_RUNNING, array_merge( $this->post_status_args(), $this->post_status_running_labels() ) ); |
|
| 10 | + register_post_status( ActionScheduler_Store::STATUS_FAILED, array_merge( $this->post_status_args(), $this->post_status_failed_labels() ) ); |
|
| 11 | + } |
|
| 12 | 12 | |
| 13 | - /** |
|
| 14 | - * Build the args array for the post type definition |
|
| 15 | - * |
|
| 16 | - * @return array |
|
| 17 | - */ |
|
| 18 | - protected function post_status_args() { |
|
| 19 | - $args = array( |
|
| 20 | - 'public' => false, |
|
| 21 | - 'exclude_from_search' => false, |
|
| 22 | - 'show_in_admin_all_list' => true, |
|
| 23 | - 'show_in_admin_status_list' => true, |
|
| 24 | - ); |
|
| 13 | + /** |
|
| 14 | + * Build the args array for the post type definition |
|
| 15 | + * |
|
| 16 | + * @return array |
|
| 17 | + */ |
|
| 18 | + protected function post_status_args() { |
|
| 19 | + $args = array( |
|
| 20 | + 'public' => false, |
|
| 21 | + 'exclude_from_search' => false, |
|
| 22 | + 'show_in_admin_all_list' => true, |
|
| 23 | + 'show_in_admin_status_list' => true, |
|
| 24 | + ); |
|
| 25 | 25 | |
| 26 | - return apply_filters( 'action_scheduler_post_status_args', $args ); |
|
| 27 | - } |
|
| 26 | + return apply_filters( 'action_scheduler_post_status_args', $args ); |
|
| 27 | + } |
|
| 28 | 28 | |
| 29 | - /** |
|
| 30 | - * Build the args array for the post type definition |
|
| 31 | - * |
|
| 32 | - * @return array |
|
| 33 | - */ |
|
| 34 | - protected function post_status_failed_labels() { |
|
| 35 | - $labels = array( |
|
| 36 | - 'label' => _x( 'Failed', 'post', 'woocommerce' ), |
|
| 37 | - /* translators: %s: count */ |
|
| 38 | - 'label_count' => _n_noop( 'Failed <span class="count">(%s)</span>', 'Failed <span class="count">(%s)</span>', 'woocommerce' ), |
|
| 39 | - ); |
|
| 29 | + /** |
|
| 30 | + * Build the args array for the post type definition |
|
| 31 | + * |
|
| 32 | + * @return array |
|
| 33 | + */ |
|
| 34 | + protected function post_status_failed_labels() { |
|
| 35 | + $labels = array( |
|
| 36 | + 'label' => _x( 'Failed', 'post', 'woocommerce' ), |
|
| 37 | + /* translators: %s: count */ |
|
| 38 | + 'label_count' => _n_noop( 'Failed <span class="count">(%s)</span>', 'Failed <span class="count">(%s)</span>', 'woocommerce' ), |
|
| 39 | + ); |
|
| 40 | 40 | |
| 41 | - return apply_filters( 'action_scheduler_post_status_failed_labels', $labels ); |
|
| 42 | - } |
|
| 41 | + return apply_filters( 'action_scheduler_post_status_failed_labels', $labels ); |
|
| 42 | + } |
|
| 43 | 43 | |
| 44 | - /** |
|
| 45 | - * Build the args array for the post type definition |
|
| 46 | - * |
|
| 47 | - * @return array |
|
| 48 | - */ |
|
| 49 | - protected function post_status_running_labels() { |
|
| 50 | - $labels = array( |
|
| 51 | - 'label' => _x( 'In-Progress', 'post', 'woocommerce' ), |
|
| 52 | - /* translators: %s: count */ |
|
| 53 | - 'label_count' => _n_noop( 'In-Progress <span class="count">(%s)</span>', 'In-Progress <span class="count">(%s)</span>', 'woocommerce' ), |
|
| 54 | - ); |
|
| 44 | + /** |
|
| 45 | + * Build the args array for the post type definition |
|
| 46 | + * |
|
| 47 | + * @return array |
|
| 48 | + */ |
|
| 49 | + protected function post_status_running_labels() { |
|
| 50 | + $labels = array( |
|
| 51 | + 'label' => _x( 'In-Progress', 'post', 'woocommerce' ), |
|
| 52 | + /* translators: %s: count */ |
|
| 53 | + 'label_count' => _n_noop( 'In-Progress <span class="count">(%s)</span>', 'In-Progress <span class="count">(%s)</span>', 'woocommerce' ), |
|
| 54 | + ); |
|
| 55 | 55 | |
| 56 | - return apply_filters( 'action_scheduler_post_status_running_labels', $labels ); |
|
| 57 | - } |
|
| 56 | + return apply_filters( 'action_scheduler_post_status_running_labels', $labels ); |
|
| 57 | + } |
|
| 58 | 58 | } |
@@ -13,414 +13,414 @@ |
||
| 13 | 13 | * @since 3.0.0 |
| 14 | 14 | */ |
| 15 | 15 | class ActionScheduler_HybridStore extends Store { |
| 16 | - const DEMARKATION_OPTION = 'action_scheduler_hybrid_store_demarkation'; |
|
| 17 | - |
|
| 18 | - private $primary_store; |
|
| 19 | - private $secondary_store; |
|
| 20 | - private $migration_runner; |
|
| 21 | - |
|
| 22 | - /** |
|
| 23 | - * @var int The dividing line between IDs of actions created |
|
| 24 | - * by the primary and secondary stores. |
|
| 25 | - * |
|
| 26 | - * Methods that accept an action ID will compare the ID against |
|
| 27 | - * this to determine which store will contain that ID. In almost |
|
| 28 | - * all cases, the ID should come from the primary store, but if |
|
| 29 | - * client code is bypassing the API functions and fetching IDs |
|
| 30 | - * from elsewhere, then there is a chance that an unmigrated ID |
|
| 31 | - * might be requested. |
|
| 32 | - */ |
|
| 33 | - private $demarkation_id = 0; |
|
| 34 | - |
|
| 35 | - /** |
|
| 36 | - * ActionScheduler_HybridStore constructor. |
|
| 37 | - * |
|
| 38 | - * @param Config $config Migration config object. |
|
| 39 | - */ |
|
| 40 | - public function __construct( Config $config = null ) { |
|
| 41 | - $this->demarkation_id = (int) get_option( self::DEMARKATION_OPTION, 0 ); |
|
| 42 | - if ( empty( $config ) ) { |
|
| 43 | - $config = Controller::instance()->get_migration_config_object(); |
|
| 44 | - } |
|
| 45 | - $this->primary_store = $config->get_destination_store(); |
|
| 46 | - $this->secondary_store = $config->get_source_store(); |
|
| 47 | - $this->migration_runner = new Runner( $config ); |
|
| 48 | - } |
|
| 49 | - |
|
| 50 | - /** |
|
| 51 | - * Initialize the table data store tables. |
|
| 52 | - * |
|
| 53 | - * @codeCoverageIgnore |
|
| 54 | - */ |
|
| 55 | - public function init() { |
|
| 56 | - add_action( 'action_scheduler/created_table', [ $this, 'set_autoincrement' ], 10, 2 ); |
|
| 57 | - $this->primary_store->init(); |
|
| 58 | - $this->secondary_store->init(); |
|
| 59 | - remove_action( 'action_scheduler/created_table', [ $this, 'set_autoincrement' ], 10 ); |
|
| 60 | - } |
|
| 61 | - |
|
| 62 | - /** |
|
| 63 | - * When the actions table is created, set its autoincrement |
|
| 64 | - * value to be one higher than the posts table to ensure that |
|
| 65 | - * there are no ID collisions. |
|
| 66 | - * |
|
| 67 | - * @param string $table_name |
|
| 68 | - * @param string $table_suffix |
|
| 69 | - * |
|
| 70 | - * @return void |
|
| 71 | - * @codeCoverageIgnore |
|
| 72 | - */ |
|
| 73 | - public function set_autoincrement( $table_name, $table_suffix ) { |
|
| 74 | - if ( ActionScheduler_StoreSchema::ACTIONS_TABLE === $table_suffix ) { |
|
| 75 | - if ( empty( $this->demarkation_id ) ) { |
|
| 76 | - $this->demarkation_id = $this->set_demarkation_id(); |
|
| 77 | - } |
|
| 78 | - /** @var \wpdb $wpdb */ |
|
| 79 | - global $wpdb; |
|
| 80 | - /** |
|
| 81 | - * A default date of '0000-00-00 00:00:00' is invalid in MySQL 5.7 when configured with |
|
| 82 | - * sql_mode including both STRICT_TRANS_TABLES and NO_ZERO_DATE. |
|
| 83 | - */ |
|
| 84 | - $default_date = new DateTime( 'tomorrow' ); |
|
| 85 | - $null_action = new ActionScheduler_NullAction(); |
|
| 86 | - $date_gmt = $this->get_scheduled_date_string( $null_action, $default_date ); |
|
| 87 | - $date_local = $this->get_scheduled_date_string_local( $null_action, $default_date ); |
|
| 88 | - |
|
| 89 | - $row_count = $wpdb->insert( |
|
| 90 | - $wpdb->{ActionScheduler_StoreSchema::ACTIONS_TABLE}, |
|
| 91 | - [ |
|
| 92 | - 'action_id' => $this->demarkation_id, |
|
| 93 | - 'hook' => '', |
|
| 94 | - 'status' => '', |
|
| 95 | - 'scheduled_date_gmt' => $date_gmt, |
|
| 96 | - 'scheduled_date_local' => $date_local, |
|
| 97 | - 'last_attempt_gmt' => $date_gmt, |
|
| 98 | - 'last_attempt_local' => $date_local, |
|
| 99 | - ] |
|
| 100 | - ); |
|
| 101 | - if ( $row_count > 0 ) { |
|
| 102 | - $wpdb->delete( |
|
| 103 | - $wpdb->{ActionScheduler_StoreSchema::ACTIONS_TABLE}, |
|
| 104 | - [ 'action_id' => $this->demarkation_id ] |
|
| 105 | - ); |
|
| 106 | - } |
|
| 107 | - } |
|
| 108 | - } |
|
| 109 | - |
|
| 110 | - /** |
|
| 111 | - * Store the demarkation id in WP options. |
|
| 112 | - * |
|
| 113 | - * @param int $id The ID to set as the demarkation point between the two stores |
|
| 114 | - * Leave null to use the next ID from the WP posts table. |
|
| 115 | - * |
|
| 116 | - * @return int The new ID. |
|
| 117 | - * |
|
| 118 | - * @codeCoverageIgnore |
|
| 119 | - */ |
|
| 120 | - private function set_demarkation_id( $id = null ) { |
|
| 121 | - if ( empty( $id ) ) { |
|
| 122 | - /** @var \wpdb $wpdb */ |
|
| 123 | - global $wpdb; |
|
| 124 | - $id = (int) $wpdb->get_var( "SELECT MAX(ID) FROM $wpdb->posts" ); |
|
| 125 | - $id ++; |
|
| 126 | - } |
|
| 127 | - update_option( self::DEMARKATION_OPTION, $id ); |
|
| 128 | - |
|
| 129 | - return $id; |
|
| 130 | - } |
|
| 131 | - |
|
| 132 | - /** |
|
| 133 | - * Find the first matching action from the secondary store. |
|
| 134 | - * If it exists, migrate it to the primary store immediately. |
|
| 135 | - * After it migrates, the secondary store will logically contain |
|
| 136 | - * the next matching action, so return the result thence. |
|
| 137 | - * |
|
| 138 | - * @param string $hook |
|
| 139 | - * @param array $params |
|
| 140 | - * |
|
| 141 | - * @return string |
|
| 142 | - */ |
|
| 143 | - public function find_action( $hook, $params = [] ) { |
|
| 144 | - $found_unmigrated_action = $this->secondary_store->find_action( $hook, $params ); |
|
| 145 | - if ( ! empty( $found_unmigrated_action ) ) { |
|
| 146 | - $this->migrate( [ $found_unmigrated_action ] ); |
|
| 147 | - } |
|
| 148 | - |
|
| 149 | - return $this->primary_store->find_action( $hook, $params ); |
|
| 150 | - } |
|
| 151 | - |
|
| 152 | - /** |
|
| 153 | - * Find actions matching the query in the secondary source first. |
|
| 154 | - * If any are found, migrate them immediately. Then the secondary |
|
| 155 | - * store will contain the canonical results. |
|
| 156 | - * |
|
| 157 | - * @param array $query |
|
| 158 | - * @param string $query_type Whether to select or count the results. Default, select. |
|
| 159 | - * |
|
| 160 | - * @return int[] |
|
| 161 | - */ |
|
| 162 | - public function query_actions( $query = [], $query_type = 'select' ) { |
|
| 163 | - $found_unmigrated_actions = $this->secondary_store->query_actions( $query, 'select' ); |
|
| 164 | - if ( ! empty( $found_unmigrated_actions ) ) { |
|
| 165 | - $this->migrate( $found_unmigrated_actions ); |
|
| 166 | - } |
|
| 167 | - |
|
| 168 | - return $this->primary_store->query_actions( $query, $query_type ); |
|
| 169 | - } |
|
| 170 | - |
|
| 171 | - /** |
|
| 172 | - * Get a count of all actions in the store, grouped by status |
|
| 173 | - * |
|
| 174 | - * @return array Set of 'status' => int $count pairs for statuses with 1 or more actions of that status. |
|
| 175 | - */ |
|
| 176 | - public function action_counts() { |
|
| 177 | - $unmigrated_actions_count = $this->secondary_store->action_counts(); |
|
| 178 | - $migrated_actions_count = $this->primary_store->action_counts(); |
|
| 179 | - $actions_count_by_status = array(); |
|
| 180 | - |
|
| 181 | - foreach ( $this->get_status_labels() as $status_key => $status_label ) { |
|
| 182 | - |
|
| 183 | - $count = 0; |
|
| 184 | - |
|
| 185 | - if ( isset( $unmigrated_actions_count[ $status_key ] ) ) { |
|
| 186 | - $count += $unmigrated_actions_count[ $status_key ]; |
|
| 187 | - } |
|
| 188 | - |
|
| 189 | - if ( isset( $migrated_actions_count[ $status_key ] ) ) { |
|
| 190 | - $count += $migrated_actions_count[ $status_key ]; |
|
| 191 | - } |
|
| 192 | - |
|
| 193 | - $actions_count_by_status[ $status_key ] = $count; |
|
| 194 | - } |
|
| 195 | - |
|
| 196 | - $actions_count_by_status = array_filter( $actions_count_by_status ); |
|
| 197 | - |
|
| 198 | - return $actions_count_by_status; |
|
| 199 | - } |
|
| 200 | - |
|
| 201 | - /** |
|
| 202 | - * If any actions would have been claimed by the secondary store, |
|
| 203 | - * migrate them immediately, then ask the primary store for the |
|
| 204 | - * canonical claim. |
|
| 205 | - * |
|
| 206 | - * @param int $max_actions |
|
| 207 | - * @param DateTime|null $before_date |
|
| 208 | - * |
|
| 209 | - * @return ActionScheduler_ActionClaim |
|
| 210 | - */ |
|
| 211 | - public function stake_claim( $max_actions = 10, DateTime $before_date = null, $hooks = array(), $group = '' ) { |
|
| 212 | - $claim = $this->secondary_store->stake_claim( $max_actions, $before_date, $hooks, $group ); |
|
| 213 | - |
|
| 214 | - $claimed_actions = $claim->get_actions(); |
|
| 215 | - if ( ! empty( $claimed_actions ) ) { |
|
| 216 | - $this->migrate( $claimed_actions ); |
|
| 217 | - } |
|
| 218 | - |
|
| 219 | - $this->secondary_store->release_claim( $claim ); |
|
| 220 | - |
|
| 221 | - return $this->primary_store->stake_claim( $max_actions, $before_date, $hooks, $group ); |
|
| 222 | - } |
|
| 223 | - |
|
| 224 | - /** |
|
| 225 | - * Migrate a list of actions to the table data store. |
|
| 226 | - * |
|
| 227 | - * @param array $action_ids List of action IDs. |
|
| 228 | - */ |
|
| 229 | - private function migrate( $action_ids ) { |
|
| 230 | - $this->migration_runner->migrate_actions( $action_ids ); |
|
| 231 | - } |
|
| 232 | - |
|
| 233 | - /** |
|
| 234 | - * Save an action to the primary store. |
|
| 235 | - * |
|
| 236 | - * @param ActionScheduler_Action $action Action object to be saved. |
|
| 237 | - * @param DateTime $date Optional. Schedule date. Default null. |
|
| 238 | - * |
|
| 239 | - * @return int The action ID |
|
| 240 | - */ |
|
| 241 | - public function save_action( ActionScheduler_Action $action, DateTime $date = null ) { |
|
| 242 | - return $this->primary_store->save_action( $action, $date ); |
|
| 243 | - } |
|
| 244 | - |
|
| 245 | - /** |
|
| 246 | - * Retrieve an existing action whether migrated or not. |
|
| 247 | - * |
|
| 248 | - * @param int $action_id Action ID. |
|
| 249 | - */ |
|
| 250 | - public function fetch_action( $action_id ) { |
|
| 251 | - $store = $this->get_store_from_action_id( $action_id, true ); |
|
| 252 | - if ( $store ) { |
|
| 253 | - return $store->fetch_action( $action_id ); |
|
| 254 | - } else { |
|
| 255 | - return new ActionScheduler_NullAction(); |
|
| 256 | - } |
|
| 257 | - } |
|
| 258 | - |
|
| 259 | - /** |
|
| 260 | - * Cancel an existing action whether migrated or not. |
|
| 261 | - * |
|
| 262 | - * @param int $action_id Action ID. |
|
| 263 | - */ |
|
| 264 | - public function cancel_action( $action_id ) { |
|
| 265 | - $store = $this->get_store_from_action_id( $action_id ); |
|
| 266 | - if ( $store ) { |
|
| 267 | - $store->cancel_action( $action_id ); |
|
| 268 | - } |
|
| 269 | - } |
|
| 270 | - |
|
| 271 | - /** |
|
| 272 | - * Delete an existing action whether migrated or not. |
|
| 273 | - * |
|
| 274 | - * @param int $action_id Action ID. |
|
| 275 | - */ |
|
| 276 | - public function delete_action( $action_id ) { |
|
| 277 | - $store = $this->get_store_from_action_id( $action_id ); |
|
| 278 | - if ( $store ) { |
|
| 279 | - $store->delete_action( $action_id ); |
|
| 280 | - } |
|
| 281 | - } |
|
| 282 | - |
|
| 283 | - /** |
|
| 284 | - * Get the schedule date an existing action whether migrated or not. |
|
| 285 | - * |
|
| 286 | - * @param int $action_id Action ID. |
|
| 287 | - */ |
|
| 288 | - public function get_date( $action_id ) { |
|
| 289 | - $store = $this->get_store_from_action_id( $action_id ); |
|
| 290 | - if ( $store ) { |
|
| 291 | - return $store->get_date( $action_id ); |
|
| 292 | - } else { |
|
| 293 | - return null; |
|
| 294 | - } |
|
| 295 | - } |
|
| 296 | - |
|
| 297 | - /** |
|
| 298 | - * Mark an existing action as failed whether migrated or not. |
|
| 299 | - * |
|
| 300 | - * @param int $action_id Action ID. |
|
| 301 | - */ |
|
| 302 | - public function mark_failure( $action_id ) { |
|
| 303 | - $store = $this->get_store_from_action_id( $action_id ); |
|
| 304 | - if ( $store ) { |
|
| 305 | - $store->mark_failure( $action_id ); |
|
| 306 | - } |
|
| 307 | - } |
|
| 308 | - |
|
| 309 | - /** |
|
| 310 | - * Log the execution of an existing action whether migrated or not. |
|
| 311 | - * |
|
| 312 | - * @param int $action_id Action ID. |
|
| 313 | - */ |
|
| 314 | - public function log_execution( $action_id ) { |
|
| 315 | - $store = $this->get_store_from_action_id( $action_id ); |
|
| 316 | - if ( $store ) { |
|
| 317 | - $store->log_execution( $action_id ); |
|
| 318 | - } |
|
| 319 | - } |
|
| 320 | - |
|
| 321 | - /** |
|
| 322 | - * Mark an existing action complete whether migrated or not. |
|
| 323 | - * |
|
| 324 | - * @param int $action_id Action ID. |
|
| 325 | - */ |
|
| 326 | - public function mark_complete( $action_id ) { |
|
| 327 | - $store = $this->get_store_from_action_id( $action_id ); |
|
| 328 | - if ( $store ) { |
|
| 329 | - $store->mark_complete( $action_id ); |
|
| 330 | - } |
|
| 331 | - } |
|
| 332 | - |
|
| 333 | - /** |
|
| 334 | - * Get an existing action status whether migrated or not. |
|
| 335 | - * |
|
| 336 | - * @param int $action_id Action ID. |
|
| 337 | - */ |
|
| 338 | - public function get_status( $action_id ) { |
|
| 339 | - $store = $this->get_store_from_action_id( $action_id ); |
|
| 340 | - if ( $store ) { |
|
| 341 | - return $store->get_status( $action_id ); |
|
| 342 | - } |
|
| 343 | - return null; |
|
| 344 | - } |
|
| 345 | - |
|
| 346 | - /** |
|
| 347 | - * Return which store an action is stored in. |
|
| 348 | - * |
|
| 349 | - * @param int $action_id ID of the action. |
|
| 350 | - * @param bool $primary_first Optional flag indicating search the primary store first. |
|
| 351 | - * @return ActionScheduler_Store |
|
| 352 | - */ |
|
| 353 | - protected function get_store_from_action_id( $action_id, $primary_first = false ) { |
|
| 354 | - if ( $primary_first ) { |
|
| 355 | - $stores = [ |
|
| 356 | - $this->primary_store, |
|
| 357 | - $this->secondary_store, |
|
| 358 | - ]; |
|
| 359 | - } elseif ( $action_id < $this->demarkation_id ) { |
|
| 360 | - $stores = [ |
|
| 361 | - $this->secondary_store, |
|
| 362 | - $this->primary_store, |
|
| 363 | - ]; |
|
| 364 | - } else { |
|
| 365 | - $stores = [ |
|
| 366 | - $this->primary_store, |
|
| 367 | - ]; |
|
| 368 | - } |
|
| 369 | - |
|
| 370 | - foreach ( $stores as $store ) { |
|
| 371 | - $action = $store->fetch_action( $action_id ); |
|
| 372 | - if ( ! is_a( $action, 'ActionScheduler_NullAction' ) ) { |
|
| 373 | - return $store; |
|
| 374 | - } |
|
| 375 | - } |
|
| 376 | - return null; |
|
| 377 | - } |
|
| 378 | - |
|
| 379 | - /* * * * * * * * * * * * * * * * * * * * * * * * * * * |
|
| 16 | + const DEMARKATION_OPTION = 'action_scheduler_hybrid_store_demarkation'; |
|
| 17 | + |
|
| 18 | + private $primary_store; |
|
| 19 | + private $secondary_store; |
|
| 20 | + private $migration_runner; |
|
| 21 | + |
|
| 22 | + /** |
|
| 23 | + * @var int The dividing line between IDs of actions created |
|
| 24 | + * by the primary and secondary stores. |
|
| 25 | + * |
|
| 26 | + * Methods that accept an action ID will compare the ID against |
|
| 27 | + * this to determine which store will contain that ID. In almost |
|
| 28 | + * all cases, the ID should come from the primary store, but if |
|
| 29 | + * client code is bypassing the API functions and fetching IDs |
|
| 30 | + * from elsewhere, then there is a chance that an unmigrated ID |
|
| 31 | + * might be requested. |
|
| 32 | + */ |
|
| 33 | + private $demarkation_id = 0; |
|
| 34 | + |
|
| 35 | + /** |
|
| 36 | + * ActionScheduler_HybridStore constructor. |
|
| 37 | + * |
|
| 38 | + * @param Config $config Migration config object. |
|
| 39 | + */ |
|
| 40 | + public function __construct( Config $config = null ) { |
|
| 41 | + $this->demarkation_id = (int) get_option( self::DEMARKATION_OPTION, 0 ); |
|
| 42 | + if ( empty( $config ) ) { |
|
| 43 | + $config = Controller::instance()->get_migration_config_object(); |
|
| 44 | + } |
|
| 45 | + $this->primary_store = $config->get_destination_store(); |
|
| 46 | + $this->secondary_store = $config->get_source_store(); |
|
| 47 | + $this->migration_runner = new Runner( $config ); |
|
| 48 | + } |
|
| 49 | + |
|
| 50 | + /** |
|
| 51 | + * Initialize the table data store tables. |
|
| 52 | + * |
|
| 53 | + * @codeCoverageIgnore |
|
| 54 | + */ |
|
| 55 | + public function init() { |
|
| 56 | + add_action( 'action_scheduler/created_table', [ $this, 'set_autoincrement' ], 10, 2 ); |
|
| 57 | + $this->primary_store->init(); |
|
| 58 | + $this->secondary_store->init(); |
|
| 59 | + remove_action( 'action_scheduler/created_table', [ $this, 'set_autoincrement' ], 10 ); |
|
| 60 | + } |
|
| 61 | + |
|
| 62 | + /** |
|
| 63 | + * When the actions table is created, set its autoincrement |
|
| 64 | + * value to be one higher than the posts table to ensure that |
|
| 65 | + * there are no ID collisions. |
|
| 66 | + * |
|
| 67 | + * @param string $table_name |
|
| 68 | + * @param string $table_suffix |
|
| 69 | + * |
|
| 70 | + * @return void |
|
| 71 | + * @codeCoverageIgnore |
|
| 72 | + */ |
|
| 73 | + public function set_autoincrement( $table_name, $table_suffix ) { |
|
| 74 | + if ( ActionScheduler_StoreSchema::ACTIONS_TABLE === $table_suffix ) { |
|
| 75 | + if ( empty( $this->demarkation_id ) ) { |
|
| 76 | + $this->demarkation_id = $this->set_demarkation_id(); |
|
| 77 | + } |
|
| 78 | + /** @var \wpdb $wpdb */ |
|
| 79 | + global $wpdb; |
|
| 80 | + /** |
|
| 81 | + * A default date of '0000-00-00 00:00:00' is invalid in MySQL 5.7 when configured with |
|
| 82 | + * sql_mode including both STRICT_TRANS_TABLES and NO_ZERO_DATE. |
|
| 83 | + */ |
|
| 84 | + $default_date = new DateTime( 'tomorrow' ); |
|
| 85 | + $null_action = new ActionScheduler_NullAction(); |
|
| 86 | + $date_gmt = $this->get_scheduled_date_string( $null_action, $default_date ); |
|
| 87 | + $date_local = $this->get_scheduled_date_string_local( $null_action, $default_date ); |
|
| 88 | + |
|
| 89 | + $row_count = $wpdb->insert( |
|
| 90 | + $wpdb->{ActionScheduler_StoreSchema::ACTIONS_TABLE}, |
|
| 91 | + [ |
|
| 92 | + 'action_id' => $this->demarkation_id, |
|
| 93 | + 'hook' => '', |
|
| 94 | + 'status' => '', |
|
| 95 | + 'scheduled_date_gmt' => $date_gmt, |
|
| 96 | + 'scheduled_date_local' => $date_local, |
|
| 97 | + 'last_attempt_gmt' => $date_gmt, |
|
| 98 | + 'last_attempt_local' => $date_local, |
|
| 99 | + ] |
|
| 100 | + ); |
|
| 101 | + if ( $row_count > 0 ) { |
|
| 102 | + $wpdb->delete( |
|
| 103 | + $wpdb->{ActionScheduler_StoreSchema::ACTIONS_TABLE}, |
|
| 104 | + [ 'action_id' => $this->demarkation_id ] |
|
| 105 | + ); |
|
| 106 | + } |
|
| 107 | + } |
|
| 108 | + } |
|
| 109 | + |
|
| 110 | + /** |
|
| 111 | + * Store the demarkation id in WP options. |
|
| 112 | + * |
|
| 113 | + * @param int $id The ID to set as the demarkation point between the two stores |
|
| 114 | + * Leave null to use the next ID from the WP posts table. |
|
| 115 | + * |
|
| 116 | + * @return int The new ID. |
|
| 117 | + * |
|
| 118 | + * @codeCoverageIgnore |
|
| 119 | + */ |
|
| 120 | + private function set_demarkation_id( $id = null ) { |
|
| 121 | + if ( empty( $id ) ) { |
|
| 122 | + /** @var \wpdb $wpdb */ |
|
| 123 | + global $wpdb; |
|
| 124 | + $id = (int) $wpdb->get_var( "SELECT MAX(ID) FROM $wpdb->posts" ); |
|
| 125 | + $id ++; |
|
| 126 | + } |
|
| 127 | + update_option( self::DEMARKATION_OPTION, $id ); |
|
| 128 | + |
|
| 129 | + return $id; |
|
| 130 | + } |
|
| 131 | + |
|
| 132 | + /** |
|
| 133 | + * Find the first matching action from the secondary store. |
|
| 134 | + * If it exists, migrate it to the primary store immediately. |
|
| 135 | + * After it migrates, the secondary store will logically contain |
|
| 136 | + * the next matching action, so return the result thence. |
|
| 137 | + * |
|
| 138 | + * @param string $hook |
|
| 139 | + * @param array $params |
|
| 140 | + * |
|
| 141 | + * @return string |
|
| 142 | + */ |
|
| 143 | + public function find_action( $hook, $params = [] ) { |
|
| 144 | + $found_unmigrated_action = $this->secondary_store->find_action( $hook, $params ); |
|
| 145 | + if ( ! empty( $found_unmigrated_action ) ) { |
|
| 146 | + $this->migrate( [ $found_unmigrated_action ] ); |
|
| 147 | + } |
|
| 148 | + |
|
| 149 | + return $this->primary_store->find_action( $hook, $params ); |
|
| 150 | + } |
|
| 151 | + |
|
| 152 | + /** |
|
| 153 | + * Find actions matching the query in the secondary source first. |
|
| 154 | + * If any are found, migrate them immediately. Then the secondary |
|
| 155 | + * store will contain the canonical results. |
|
| 156 | + * |
|
| 157 | + * @param array $query |
|
| 158 | + * @param string $query_type Whether to select or count the results. Default, select. |
|
| 159 | + * |
|
| 160 | + * @return int[] |
|
| 161 | + */ |
|
| 162 | + public function query_actions( $query = [], $query_type = 'select' ) { |
|
| 163 | + $found_unmigrated_actions = $this->secondary_store->query_actions( $query, 'select' ); |
|
| 164 | + if ( ! empty( $found_unmigrated_actions ) ) { |
|
| 165 | + $this->migrate( $found_unmigrated_actions ); |
|
| 166 | + } |
|
| 167 | + |
|
| 168 | + return $this->primary_store->query_actions( $query, $query_type ); |
|
| 169 | + } |
|
| 170 | + |
|
| 171 | + /** |
|
| 172 | + * Get a count of all actions in the store, grouped by status |
|
| 173 | + * |
|
| 174 | + * @return array Set of 'status' => int $count pairs for statuses with 1 or more actions of that status. |
|
| 175 | + */ |
|
| 176 | + public function action_counts() { |
|
| 177 | + $unmigrated_actions_count = $this->secondary_store->action_counts(); |
|
| 178 | + $migrated_actions_count = $this->primary_store->action_counts(); |
|
| 179 | + $actions_count_by_status = array(); |
|
| 180 | + |
|
| 181 | + foreach ( $this->get_status_labels() as $status_key => $status_label ) { |
|
| 182 | + |
|
| 183 | + $count = 0; |
|
| 184 | + |
|
| 185 | + if ( isset( $unmigrated_actions_count[ $status_key ] ) ) { |
|
| 186 | + $count += $unmigrated_actions_count[ $status_key ]; |
|
| 187 | + } |
|
| 188 | + |
|
| 189 | + if ( isset( $migrated_actions_count[ $status_key ] ) ) { |
|
| 190 | + $count += $migrated_actions_count[ $status_key ]; |
|
| 191 | + } |
|
| 192 | + |
|
| 193 | + $actions_count_by_status[ $status_key ] = $count; |
|
| 194 | + } |
|
| 195 | + |
|
| 196 | + $actions_count_by_status = array_filter( $actions_count_by_status ); |
|
| 197 | + |
|
| 198 | + return $actions_count_by_status; |
|
| 199 | + } |
|
| 200 | + |
|
| 201 | + /** |
|
| 202 | + * If any actions would have been claimed by the secondary store, |
|
| 203 | + * migrate them immediately, then ask the primary store for the |
|
| 204 | + * canonical claim. |
|
| 205 | + * |
|
| 206 | + * @param int $max_actions |
|
| 207 | + * @param DateTime|null $before_date |
|
| 208 | + * |
|
| 209 | + * @return ActionScheduler_ActionClaim |
|
| 210 | + */ |
|
| 211 | + public function stake_claim( $max_actions = 10, DateTime $before_date = null, $hooks = array(), $group = '' ) { |
|
| 212 | + $claim = $this->secondary_store->stake_claim( $max_actions, $before_date, $hooks, $group ); |
|
| 213 | + |
|
| 214 | + $claimed_actions = $claim->get_actions(); |
|
| 215 | + if ( ! empty( $claimed_actions ) ) { |
|
| 216 | + $this->migrate( $claimed_actions ); |
|
| 217 | + } |
|
| 218 | + |
|
| 219 | + $this->secondary_store->release_claim( $claim ); |
|
| 220 | + |
|
| 221 | + return $this->primary_store->stake_claim( $max_actions, $before_date, $hooks, $group ); |
|
| 222 | + } |
|
| 223 | + |
|
| 224 | + /** |
|
| 225 | + * Migrate a list of actions to the table data store. |
|
| 226 | + * |
|
| 227 | + * @param array $action_ids List of action IDs. |
|
| 228 | + */ |
|
| 229 | + private function migrate( $action_ids ) { |
|
| 230 | + $this->migration_runner->migrate_actions( $action_ids ); |
|
| 231 | + } |
|
| 232 | + |
|
| 233 | + /** |
|
| 234 | + * Save an action to the primary store. |
|
| 235 | + * |
|
| 236 | + * @param ActionScheduler_Action $action Action object to be saved. |
|
| 237 | + * @param DateTime $date Optional. Schedule date. Default null. |
|
| 238 | + * |
|
| 239 | + * @return int The action ID |
|
| 240 | + */ |
|
| 241 | + public function save_action( ActionScheduler_Action $action, DateTime $date = null ) { |
|
| 242 | + return $this->primary_store->save_action( $action, $date ); |
|
| 243 | + } |
|
| 244 | + |
|
| 245 | + /** |
|
| 246 | + * Retrieve an existing action whether migrated or not. |
|
| 247 | + * |
|
| 248 | + * @param int $action_id Action ID. |
|
| 249 | + */ |
|
| 250 | + public function fetch_action( $action_id ) { |
|
| 251 | + $store = $this->get_store_from_action_id( $action_id, true ); |
|
| 252 | + if ( $store ) { |
|
| 253 | + return $store->fetch_action( $action_id ); |
|
| 254 | + } else { |
|
| 255 | + return new ActionScheduler_NullAction(); |
|
| 256 | + } |
|
| 257 | + } |
|
| 258 | + |
|
| 259 | + /** |
|
| 260 | + * Cancel an existing action whether migrated or not. |
|
| 261 | + * |
|
| 262 | + * @param int $action_id Action ID. |
|
| 263 | + */ |
|
| 264 | + public function cancel_action( $action_id ) { |
|
| 265 | + $store = $this->get_store_from_action_id( $action_id ); |
|
| 266 | + if ( $store ) { |
|
| 267 | + $store->cancel_action( $action_id ); |
|
| 268 | + } |
|
| 269 | + } |
|
| 270 | + |
|
| 271 | + /** |
|
| 272 | + * Delete an existing action whether migrated or not. |
|
| 273 | + * |
|
| 274 | + * @param int $action_id Action ID. |
|
| 275 | + */ |
|
| 276 | + public function delete_action( $action_id ) { |
|
| 277 | + $store = $this->get_store_from_action_id( $action_id ); |
|
| 278 | + if ( $store ) { |
|
| 279 | + $store->delete_action( $action_id ); |
|
| 280 | + } |
|
| 281 | + } |
|
| 282 | + |
|
| 283 | + /** |
|
| 284 | + * Get the schedule date an existing action whether migrated or not. |
|
| 285 | + * |
|
| 286 | + * @param int $action_id Action ID. |
|
| 287 | + */ |
|
| 288 | + public function get_date( $action_id ) { |
|
| 289 | + $store = $this->get_store_from_action_id( $action_id ); |
|
| 290 | + if ( $store ) { |
|
| 291 | + return $store->get_date( $action_id ); |
|
| 292 | + } else { |
|
| 293 | + return null; |
|
| 294 | + } |
|
| 295 | + } |
|
| 296 | + |
|
| 297 | + /** |
|
| 298 | + * Mark an existing action as failed whether migrated or not. |
|
| 299 | + * |
|
| 300 | + * @param int $action_id Action ID. |
|
| 301 | + */ |
|
| 302 | + public function mark_failure( $action_id ) { |
|
| 303 | + $store = $this->get_store_from_action_id( $action_id ); |
|
| 304 | + if ( $store ) { |
|
| 305 | + $store->mark_failure( $action_id ); |
|
| 306 | + } |
|
| 307 | + } |
|
| 308 | + |
|
| 309 | + /** |
|
| 310 | + * Log the execution of an existing action whether migrated or not. |
|
| 311 | + * |
|
| 312 | + * @param int $action_id Action ID. |
|
| 313 | + */ |
|
| 314 | + public function log_execution( $action_id ) { |
|
| 315 | + $store = $this->get_store_from_action_id( $action_id ); |
|
| 316 | + if ( $store ) { |
|
| 317 | + $store->log_execution( $action_id ); |
|
| 318 | + } |
|
| 319 | + } |
|
| 320 | + |
|
| 321 | + /** |
|
| 322 | + * Mark an existing action complete whether migrated or not. |
|
| 323 | + * |
|
| 324 | + * @param int $action_id Action ID. |
|
| 325 | + */ |
|
| 326 | + public function mark_complete( $action_id ) { |
|
| 327 | + $store = $this->get_store_from_action_id( $action_id ); |
|
| 328 | + if ( $store ) { |
|
| 329 | + $store->mark_complete( $action_id ); |
|
| 330 | + } |
|
| 331 | + } |
|
| 332 | + |
|
| 333 | + /** |
|
| 334 | + * Get an existing action status whether migrated or not. |
|
| 335 | + * |
|
| 336 | + * @param int $action_id Action ID. |
|
| 337 | + */ |
|
| 338 | + public function get_status( $action_id ) { |
|
| 339 | + $store = $this->get_store_from_action_id( $action_id ); |
|
| 340 | + if ( $store ) { |
|
| 341 | + return $store->get_status( $action_id ); |
|
| 342 | + } |
|
| 343 | + return null; |
|
| 344 | + } |
|
| 345 | + |
|
| 346 | + /** |
|
| 347 | + * Return which store an action is stored in. |
|
| 348 | + * |
|
| 349 | + * @param int $action_id ID of the action. |
|
| 350 | + * @param bool $primary_first Optional flag indicating search the primary store first. |
|
| 351 | + * @return ActionScheduler_Store |
|
| 352 | + */ |
|
| 353 | + protected function get_store_from_action_id( $action_id, $primary_first = false ) { |
|
| 354 | + if ( $primary_first ) { |
|
| 355 | + $stores = [ |
|
| 356 | + $this->primary_store, |
|
| 357 | + $this->secondary_store, |
|
| 358 | + ]; |
|
| 359 | + } elseif ( $action_id < $this->demarkation_id ) { |
|
| 360 | + $stores = [ |
|
| 361 | + $this->secondary_store, |
|
| 362 | + $this->primary_store, |
|
| 363 | + ]; |
|
| 364 | + } else { |
|
| 365 | + $stores = [ |
|
| 366 | + $this->primary_store, |
|
| 367 | + ]; |
|
| 368 | + } |
|
| 369 | + |
|
| 370 | + foreach ( $stores as $store ) { |
|
| 371 | + $action = $store->fetch_action( $action_id ); |
|
| 372 | + if ( ! is_a( $action, 'ActionScheduler_NullAction' ) ) { |
|
| 373 | + return $store; |
|
| 374 | + } |
|
| 375 | + } |
|
| 376 | + return null; |
|
| 377 | + } |
|
| 378 | + |
|
| 379 | + /* * * * * * * * * * * * * * * * * * * * * * * * * * * |
|
| 380 | 380 | * All claim-related functions should operate solely |
| 381 | 381 | * on the primary store. |
| 382 | 382 | * * * * * * * * * * * * * * * * * * * * * * * * * * */ |
| 383 | 383 | |
| 384 | - /** |
|
| 385 | - * Get the claim count from the table data store. |
|
| 386 | - */ |
|
| 387 | - public function get_claim_count() { |
|
| 388 | - return $this->primary_store->get_claim_count(); |
|
| 389 | - } |
|
| 390 | - |
|
| 391 | - /** |
|
| 392 | - * Retrieve the claim ID for an action from the table data store. |
|
| 393 | - * |
|
| 394 | - * @param int $action_id Action ID. |
|
| 395 | - */ |
|
| 396 | - public function get_claim_id( $action_id ) { |
|
| 397 | - return $this->primary_store->get_claim_id( $action_id ); |
|
| 398 | - } |
|
| 399 | - |
|
| 400 | - /** |
|
| 401 | - * Release a claim in the table data store. |
|
| 402 | - * |
|
| 403 | - * @param ActionScheduler_ActionClaim $claim Claim object. |
|
| 404 | - */ |
|
| 405 | - public function release_claim( ActionScheduler_ActionClaim $claim ) { |
|
| 406 | - $this->primary_store->release_claim( $claim ); |
|
| 407 | - } |
|
| 408 | - |
|
| 409 | - /** |
|
| 410 | - * Release claims on an action in the table data store. |
|
| 411 | - * |
|
| 412 | - * @param int $action_id Action ID. |
|
| 413 | - */ |
|
| 414 | - public function unclaim_action( $action_id ) { |
|
| 415 | - $this->primary_store->unclaim_action( $action_id ); |
|
| 416 | - } |
|
| 417 | - |
|
| 418 | - /** |
|
| 419 | - * Retrieve a list of action IDs by claim. |
|
| 420 | - * |
|
| 421 | - * @param int $claim_id Claim ID. |
|
| 422 | - */ |
|
| 423 | - public function find_actions_by_claim_id( $claim_id ) { |
|
| 424 | - return $this->primary_store->find_actions_by_claim_id( $claim_id ); |
|
| 425 | - } |
|
| 384 | + /** |
|
| 385 | + * Get the claim count from the table data store. |
|
| 386 | + */ |
|
| 387 | + public function get_claim_count() { |
|
| 388 | + return $this->primary_store->get_claim_count(); |
|
| 389 | + } |
|
| 390 | + |
|
| 391 | + /** |
|
| 392 | + * Retrieve the claim ID for an action from the table data store. |
|
| 393 | + * |
|
| 394 | + * @param int $action_id Action ID. |
|
| 395 | + */ |
|
| 396 | + public function get_claim_id( $action_id ) { |
|
| 397 | + return $this->primary_store->get_claim_id( $action_id ); |
|
| 398 | + } |
|
| 399 | + |
|
| 400 | + /** |
|
| 401 | + * Release a claim in the table data store. |
|
| 402 | + * |
|
| 403 | + * @param ActionScheduler_ActionClaim $claim Claim object. |
|
| 404 | + */ |
|
| 405 | + public function release_claim( ActionScheduler_ActionClaim $claim ) { |
|
| 406 | + $this->primary_store->release_claim( $claim ); |
|
| 407 | + } |
|
| 408 | + |
|
| 409 | + /** |
|
| 410 | + * Release claims on an action in the table data store. |
|
| 411 | + * |
|
| 412 | + * @param int $action_id Action ID. |
|
| 413 | + */ |
|
| 414 | + public function unclaim_action( $action_id ) { |
|
| 415 | + $this->primary_store->unclaim_action( $action_id ); |
|
| 416 | + } |
|
| 417 | + |
|
| 418 | + /** |
|
| 419 | + * Retrieve a list of action IDs by claim. |
|
| 420 | + * |
|
| 421 | + * @param int $claim_id Claim ID. |
|
| 422 | + */ |
|
| 423 | + public function find_actions_by_claim_id( $claim_id ) { |
|
| 424 | + return $this->primary_store->find_actions_by_claim_id( $claim_id ); |
|
| 425 | + } |
|
| 426 | 426 | } |
@@ -4,237 +4,237 @@ |
||
| 4 | 4 | * Class ActionScheduler_wpCommentLogger |
| 5 | 5 | */ |
| 6 | 6 | class ActionScheduler_wpCommentLogger extends ActionScheduler_Logger { |
| 7 | - const AGENT = 'ActionScheduler'; |
|
| 8 | - const TYPE = 'action_log'; |
|
| 9 | - |
|
| 10 | - /** |
|
| 11 | - * @param string $action_id |
|
| 12 | - * @param string $message |
|
| 13 | - * @param DateTime $date |
|
| 14 | - * |
|
| 15 | - * @return string The log entry ID |
|
| 16 | - */ |
|
| 17 | - public function log( $action_id, $message, DateTime $date = NULL ) { |
|
| 18 | - if ( empty($date) ) { |
|
| 19 | - $date = as_get_datetime_object(); |
|
| 20 | - } else { |
|
| 21 | - $date = as_get_datetime_object( clone $date ); |
|
| 22 | - } |
|
| 23 | - $comment_id = $this->create_wp_comment( $action_id, $message, $date ); |
|
| 24 | - return $comment_id; |
|
| 25 | - } |
|
| 26 | - |
|
| 27 | - protected function create_wp_comment( $action_id, $message, DateTime $date ) { |
|
| 28 | - |
|
| 29 | - $comment_date_gmt = $date->format('Y-m-d H:i:s'); |
|
| 30 | - ActionScheduler_TimezoneHelper::set_local_timezone( $date ); |
|
| 31 | - $comment_data = array( |
|
| 32 | - 'comment_post_ID' => $action_id, |
|
| 33 | - 'comment_date' => $date->format('Y-m-d H:i:s'), |
|
| 34 | - 'comment_date_gmt' => $comment_date_gmt, |
|
| 35 | - 'comment_author' => self::AGENT, |
|
| 36 | - 'comment_content' => $message, |
|
| 37 | - 'comment_agent' => self::AGENT, |
|
| 38 | - 'comment_type' => self::TYPE, |
|
| 39 | - ); |
|
| 40 | - return wp_insert_comment($comment_data); |
|
| 41 | - } |
|
| 42 | - |
|
| 43 | - /** |
|
| 44 | - * @param string $entry_id |
|
| 45 | - * |
|
| 46 | - * @return ActionScheduler_LogEntry |
|
| 47 | - */ |
|
| 48 | - public function get_entry( $entry_id ) { |
|
| 49 | - $comment = $this->get_comment( $entry_id ); |
|
| 50 | - if ( empty($comment) || $comment->comment_type != self::TYPE ) { |
|
| 51 | - return new ActionScheduler_NullLogEntry(); |
|
| 52 | - } |
|
| 53 | - |
|
| 54 | - $date = as_get_datetime_object( $comment->comment_date_gmt ); |
|
| 55 | - ActionScheduler_TimezoneHelper::set_local_timezone( $date ); |
|
| 56 | - return new ActionScheduler_LogEntry( $comment->comment_post_ID, $comment->comment_content, $date ); |
|
| 57 | - } |
|
| 58 | - |
|
| 59 | - /** |
|
| 60 | - * @param string $action_id |
|
| 61 | - * |
|
| 62 | - * @return ActionScheduler_LogEntry[] |
|
| 63 | - */ |
|
| 64 | - public function get_logs( $action_id ) { |
|
| 65 | - $status = 'all'; |
|
| 66 | - if ( get_post_status($action_id) == 'trash' ) { |
|
| 67 | - $status = 'post-trashed'; |
|
| 68 | - } |
|
| 69 | - $comments = get_comments(array( |
|
| 70 | - 'post_id' => $action_id, |
|
| 71 | - 'orderby' => 'comment_date_gmt', |
|
| 72 | - 'order' => 'ASC', |
|
| 73 | - 'type' => self::TYPE, |
|
| 74 | - 'status' => $status, |
|
| 75 | - )); |
|
| 76 | - $logs = array(); |
|
| 77 | - foreach ( $comments as $c ) { |
|
| 78 | - $entry = $this->get_entry( $c ); |
|
| 79 | - if ( !empty($entry) ) { |
|
| 80 | - $logs[] = $entry; |
|
| 81 | - } |
|
| 82 | - } |
|
| 83 | - return $logs; |
|
| 84 | - } |
|
| 85 | - |
|
| 86 | - protected function get_comment( $comment_id ) { |
|
| 87 | - return get_comment( $comment_id ); |
|
| 88 | - } |
|
| 89 | - |
|
| 90 | - |
|
| 91 | - |
|
| 92 | - /** |
|
| 93 | - * @param WP_Comment_Query $query |
|
| 94 | - */ |
|
| 95 | - public function filter_comment_queries( $query ) { |
|
| 96 | - foreach ( array('ID', 'parent', 'post_author', 'post_name', 'post_parent', 'type', 'post_type', 'post_id', 'post_ID') as $key ) { |
|
| 97 | - if ( !empty($query->query_vars[$key]) ) { |
|
| 98 | - return; // don't slow down queries that wouldn't include action_log comments anyway |
|
| 99 | - } |
|
| 100 | - } |
|
| 101 | - $query->query_vars['action_log_filter'] = TRUE; |
|
| 102 | - add_filter( 'comments_clauses', array( $this, 'filter_comment_query_clauses' ), 10, 2 ); |
|
| 103 | - } |
|
| 104 | - |
|
| 105 | - /** |
|
| 106 | - * @param array $clauses |
|
| 107 | - * @param WP_Comment_Query $query |
|
| 108 | - * |
|
| 109 | - * @return array |
|
| 110 | - */ |
|
| 111 | - public function filter_comment_query_clauses( $clauses, $query ) { |
|
| 112 | - if ( !empty($query->query_vars['action_log_filter']) ) { |
|
| 113 | - $clauses['where'] .= $this->get_where_clause(); |
|
| 114 | - } |
|
| 115 | - return $clauses; |
|
| 116 | - } |
|
| 117 | - |
|
| 118 | - /** |
|
| 119 | - * Make sure Action Scheduler logs are excluded from comment feeds, which use WP_Query, not |
|
| 120 | - * the WP_Comment_Query class handled by @see self::filter_comment_queries(). |
|
| 121 | - * |
|
| 122 | - * @param string $where |
|
| 123 | - * @param WP_Query $query |
|
| 124 | - * |
|
| 125 | - * @return string |
|
| 126 | - */ |
|
| 127 | - public function filter_comment_feed( $where, $query ) { |
|
| 128 | - if ( is_comment_feed() ) { |
|
| 129 | - $where .= $this->get_where_clause(); |
|
| 130 | - } |
|
| 131 | - return $where; |
|
| 132 | - } |
|
| 133 | - |
|
| 134 | - /** |
|
| 135 | - * Return a SQL clause to exclude Action Scheduler comments. |
|
| 136 | - * |
|
| 137 | - * @return string |
|
| 138 | - */ |
|
| 139 | - protected function get_where_clause() { |
|
| 140 | - global $wpdb; |
|
| 141 | - return sprintf( " AND {$wpdb->comments}.comment_type != '%s'", self::TYPE ); |
|
| 142 | - } |
|
| 143 | - |
|
| 144 | - /** |
|
| 145 | - * Remove action log entries from wp_count_comments() |
|
| 146 | - * |
|
| 147 | - * @param array $stats |
|
| 148 | - * @param int $post_id |
|
| 149 | - * |
|
| 150 | - * @return object |
|
| 151 | - */ |
|
| 152 | - public function filter_comment_count( $stats, $post_id ) { |
|
| 153 | - global $wpdb; |
|
| 154 | - |
|
| 155 | - if ( 0 === $post_id ) { |
|
| 156 | - $stats = $this->get_comment_count(); |
|
| 157 | - } |
|
| 158 | - |
|
| 159 | - return $stats; |
|
| 160 | - } |
|
| 161 | - |
|
| 162 | - /** |
|
| 163 | - * Retrieve the comment counts from our cache, or the database if the cached version isn't set. |
|
| 164 | - * |
|
| 165 | - * @return object |
|
| 166 | - */ |
|
| 167 | - protected function get_comment_count() { |
|
| 168 | - global $wpdb; |
|
| 169 | - |
|
| 170 | - $stats = get_transient( 'as_comment_count' ); |
|
| 171 | - |
|
| 172 | - if ( ! $stats ) { |
|
| 173 | - $stats = array(); |
|
| 174 | - |
|
| 175 | - $count = $wpdb->get_results( "SELECT comment_approved, COUNT( * ) AS num_comments FROM {$wpdb->comments} WHERE comment_type NOT IN('order_note','action_log') GROUP BY comment_approved", ARRAY_A ); |
|
| 176 | - |
|
| 177 | - $total = 0; |
|
| 178 | - $stats = array(); |
|
| 179 | - $approved = array( '0' => 'moderated', '1' => 'approved', 'spam' => 'spam', 'trash' => 'trash', 'post-trashed' => 'post-trashed' ); |
|
| 180 | - |
|
| 181 | - foreach ( (array) $count as $row ) { |
|
| 182 | - // Don't count post-trashed toward totals |
|
| 183 | - if ( 'post-trashed' != $row['comment_approved'] && 'trash' != $row['comment_approved'] ) { |
|
| 184 | - $total += $row['num_comments']; |
|
| 185 | - } |
|
| 186 | - if ( isset( $approved[ $row['comment_approved'] ] ) ) { |
|
| 187 | - $stats[ $approved[ $row['comment_approved'] ] ] = $row['num_comments']; |
|
| 188 | - } |
|
| 189 | - } |
|
| 190 | - |
|
| 191 | - $stats['total_comments'] = $total; |
|
| 192 | - $stats['all'] = $total; |
|
| 193 | - |
|
| 194 | - foreach ( $approved as $key ) { |
|
| 195 | - if ( empty( $stats[ $key ] ) ) { |
|
| 196 | - $stats[ $key ] = 0; |
|
| 197 | - } |
|
| 198 | - } |
|
| 199 | - |
|
| 200 | - $stats = (object) $stats; |
|
| 201 | - set_transient( 'as_comment_count', $stats ); |
|
| 202 | - } |
|
| 203 | - |
|
| 204 | - return $stats; |
|
| 205 | - } |
|
| 206 | - |
|
| 207 | - /** |
|
| 208 | - * Delete comment count cache whenever there is new comment or the status of a comment changes. Cache |
|
| 209 | - * will be regenerated next time ActionScheduler_wpCommentLogger::filter_comment_count() is called. |
|
| 210 | - */ |
|
| 211 | - public function delete_comment_count_cache() { |
|
| 212 | - delete_transient( 'as_comment_count' ); |
|
| 213 | - } |
|
| 214 | - |
|
| 215 | - /** |
|
| 216 | - * @codeCoverageIgnore |
|
| 217 | - */ |
|
| 218 | - public function init() { |
|
| 219 | - add_action( 'action_scheduler_before_process_queue', array( $this, 'disable_comment_counting' ), 10, 0 ); |
|
| 220 | - add_action( 'action_scheduler_after_process_queue', array( $this, 'enable_comment_counting' ), 10, 0 ); |
|
| 221 | - |
|
| 222 | - parent::init(); |
|
| 223 | - |
|
| 224 | - add_action( 'pre_get_comments', array( $this, 'filter_comment_queries' ), 10, 1 ); |
|
| 225 | - add_action( 'wp_count_comments', array( $this, 'filter_comment_count' ), 20, 2 ); // run after WC_Comments::wp_count_comments() to make sure we exclude order notes and action logs |
|
| 226 | - add_action( 'comment_feed_where', array( $this, 'filter_comment_feed' ), 10, 2 ); |
|
| 227 | - |
|
| 228 | - // Delete comments count cache whenever there is a new comment or a comment status changes |
|
| 229 | - add_action( 'wp_insert_comment', array( $this, 'delete_comment_count_cache' ) ); |
|
| 230 | - add_action( 'wp_set_comment_status', array( $this, 'delete_comment_count_cache' ) ); |
|
| 231 | - } |
|
| 232 | - |
|
| 233 | - public function disable_comment_counting() { |
|
| 234 | - wp_defer_comment_counting(true); |
|
| 235 | - } |
|
| 236 | - public function enable_comment_counting() { |
|
| 237 | - wp_defer_comment_counting(false); |
|
| 238 | - } |
|
| 7 | + const AGENT = 'ActionScheduler'; |
|
| 8 | + const TYPE = 'action_log'; |
|
| 9 | + |
|
| 10 | + /** |
|
| 11 | + * @param string $action_id |
|
| 12 | + * @param string $message |
|
| 13 | + * @param DateTime $date |
|
| 14 | + * |
|
| 15 | + * @return string The log entry ID |
|
| 16 | + */ |
|
| 17 | + public function log( $action_id, $message, DateTime $date = NULL ) { |
|
| 18 | + if ( empty($date) ) { |
|
| 19 | + $date = as_get_datetime_object(); |
|
| 20 | + } else { |
|
| 21 | + $date = as_get_datetime_object( clone $date ); |
|
| 22 | + } |
|
| 23 | + $comment_id = $this->create_wp_comment( $action_id, $message, $date ); |
|
| 24 | + return $comment_id; |
|
| 25 | + } |
|
| 26 | + |
|
| 27 | + protected function create_wp_comment( $action_id, $message, DateTime $date ) { |
|
| 28 | + |
|
| 29 | + $comment_date_gmt = $date->format('Y-m-d H:i:s'); |
|
| 30 | + ActionScheduler_TimezoneHelper::set_local_timezone( $date ); |
|
| 31 | + $comment_data = array( |
|
| 32 | + 'comment_post_ID' => $action_id, |
|
| 33 | + 'comment_date' => $date->format('Y-m-d H:i:s'), |
|
| 34 | + 'comment_date_gmt' => $comment_date_gmt, |
|
| 35 | + 'comment_author' => self::AGENT, |
|
| 36 | + 'comment_content' => $message, |
|
| 37 | + 'comment_agent' => self::AGENT, |
|
| 38 | + 'comment_type' => self::TYPE, |
|
| 39 | + ); |
|
| 40 | + return wp_insert_comment($comment_data); |
|
| 41 | + } |
|
| 42 | + |
|
| 43 | + /** |
|
| 44 | + * @param string $entry_id |
|
| 45 | + * |
|
| 46 | + * @return ActionScheduler_LogEntry |
|
| 47 | + */ |
|
| 48 | + public function get_entry( $entry_id ) { |
|
| 49 | + $comment = $this->get_comment( $entry_id ); |
|
| 50 | + if ( empty($comment) || $comment->comment_type != self::TYPE ) { |
|
| 51 | + return new ActionScheduler_NullLogEntry(); |
|
| 52 | + } |
|
| 53 | + |
|
| 54 | + $date = as_get_datetime_object( $comment->comment_date_gmt ); |
|
| 55 | + ActionScheduler_TimezoneHelper::set_local_timezone( $date ); |
|
| 56 | + return new ActionScheduler_LogEntry( $comment->comment_post_ID, $comment->comment_content, $date ); |
|
| 57 | + } |
|
| 58 | + |
|
| 59 | + /** |
|
| 60 | + * @param string $action_id |
|
| 61 | + * |
|
| 62 | + * @return ActionScheduler_LogEntry[] |
|
| 63 | + */ |
|
| 64 | + public function get_logs( $action_id ) { |
|
| 65 | + $status = 'all'; |
|
| 66 | + if ( get_post_status($action_id) == 'trash' ) { |
|
| 67 | + $status = 'post-trashed'; |
|
| 68 | + } |
|
| 69 | + $comments = get_comments(array( |
|
| 70 | + 'post_id' => $action_id, |
|
| 71 | + 'orderby' => 'comment_date_gmt', |
|
| 72 | + 'order' => 'ASC', |
|
| 73 | + 'type' => self::TYPE, |
|
| 74 | + 'status' => $status, |
|
| 75 | + )); |
|
| 76 | + $logs = array(); |
|
| 77 | + foreach ( $comments as $c ) { |
|
| 78 | + $entry = $this->get_entry( $c ); |
|
| 79 | + if ( !empty($entry) ) { |
|
| 80 | + $logs[] = $entry; |
|
| 81 | + } |
|
| 82 | + } |
|
| 83 | + return $logs; |
|
| 84 | + } |
|
| 85 | + |
|
| 86 | + protected function get_comment( $comment_id ) { |
|
| 87 | + return get_comment( $comment_id ); |
|
| 88 | + } |
|
| 89 | + |
|
| 90 | + |
|
| 91 | + |
|
| 92 | + /** |
|
| 93 | + * @param WP_Comment_Query $query |
|
| 94 | + */ |
|
| 95 | + public function filter_comment_queries( $query ) { |
|
| 96 | + foreach ( array('ID', 'parent', 'post_author', 'post_name', 'post_parent', 'type', 'post_type', 'post_id', 'post_ID') as $key ) { |
|
| 97 | + if ( !empty($query->query_vars[$key]) ) { |
|
| 98 | + return; // don't slow down queries that wouldn't include action_log comments anyway |
|
| 99 | + } |
|
| 100 | + } |
|
| 101 | + $query->query_vars['action_log_filter'] = TRUE; |
|
| 102 | + add_filter( 'comments_clauses', array( $this, 'filter_comment_query_clauses' ), 10, 2 ); |
|
| 103 | + } |
|
| 104 | + |
|
| 105 | + /** |
|
| 106 | + * @param array $clauses |
|
| 107 | + * @param WP_Comment_Query $query |
|
| 108 | + * |
|
| 109 | + * @return array |
|
| 110 | + */ |
|
| 111 | + public function filter_comment_query_clauses( $clauses, $query ) { |
|
| 112 | + if ( !empty($query->query_vars['action_log_filter']) ) { |
|
| 113 | + $clauses['where'] .= $this->get_where_clause(); |
|
| 114 | + } |
|
| 115 | + return $clauses; |
|
| 116 | + } |
|
| 117 | + |
|
| 118 | + /** |
|
| 119 | + * Make sure Action Scheduler logs are excluded from comment feeds, which use WP_Query, not |
|
| 120 | + * the WP_Comment_Query class handled by @see self::filter_comment_queries(). |
|
| 121 | + * |
|
| 122 | + * @param string $where |
|
| 123 | + * @param WP_Query $query |
|
| 124 | + * |
|
| 125 | + * @return string |
|
| 126 | + */ |
|
| 127 | + public function filter_comment_feed( $where, $query ) { |
|
| 128 | + if ( is_comment_feed() ) { |
|
| 129 | + $where .= $this->get_where_clause(); |
|
| 130 | + } |
|
| 131 | + return $where; |
|
| 132 | + } |
|
| 133 | + |
|
| 134 | + /** |
|
| 135 | + * Return a SQL clause to exclude Action Scheduler comments. |
|
| 136 | + * |
|
| 137 | + * @return string |
|
| 138 | + */ |
|
| 139 | + protected function get_where_clause() { |
|
| 140 | + global $wpdb; |
|
| 141 | + return sprintf( " AND {$wpdb->comments}.comment_type != '%s'", self::TYPE ); |
|
| 142 | + } |
|
| 143 | + |
|
| 144 | + /** |
|
| 145 | + * Remove action log entries from wp_count_comments() |
|
| 146 | + * |
|
| 147 | + * @param array $stats |
|
| 148 | + * @param int $post_id |
|
| 149 | + * |
|
| 150 | + * @return object |
|
| 151 | + */ |
|
| 152 | + public function filter_comment_count( $stats, $post_id ) { |
|
| 153 | + global $wpdb; |
|
| 154 | + |
|
| 155 | + if ( 0 === $post_id ) { |
|
| 156 | + $stats = $this->get_comment_count(); |
|
| 157 | + } |
|
| 158 | + |
|
| 159 | + return $stats; |
|
| 160 | + } |
|
| 161 | + |
|
| 162 | + /** |
|
| 163 | + * Retrieve the comment counts from our cache, or the database if the cached version isn't set. |
|
| 164 | + * |
|
| 165 | + * @return object |
|
| 166 | + */ |
|
| 167 | + protected function get_comment_count() { |
|
| 168 | + global $wpdb; |
|
| 169 | + |
|
| 170 | + $stats = get_transient( 'as_comment_count' ); |
|
| 171 | + |
|
| 172 | + if ( ! $stats ) { |
|
| 173 | + $stats = array(); |
|
| 174 | + |
|
| 175 | + $count = $wpdb->get_results( "SELECT comment_approved, COUNT( * ) AS num_comments FROM {$wpdb->comments} WHERE comment_type NOT IN('order_note','action_log') GROUP BY comment_approved", ARRAY_A ); |
|
| 176 | + |
|
| 177 | + $total = 0; |
|
| 178 | + $stats = array(); |
|
| 179 | + $approved = array( '0' => 'moderated', '1' => 'approved', 'spam' => 'spam', 'trash' => 'trash', 'post-trashed' => 'post-trashed' ); |
|
| 180 | + |
|
| 181 | + foreach ( (array) $count as $row ) { |
|
| 182 | + // Don't count post-trashed toward totals |
|
| 183 | + if ( 'post-trashed' != $row['comment_approved'] && 'trash' != $row['comment_approved'] ) { |
|
| 184 | + $total += $row['num_comments']; |
|
| 185 | + } |
|
| 186 | + if ( isset( $approved[ $row['comment_approved'] ] ) ) { |
|
| 187 | + $stats[ $approved[ $row['comment_approved'] ] ] = $row['num_comments']; |
|
| 188 | + } |
|
| 189 | + } |
|
| 190 | + |
|
| 191 | + $stats['total_comments'] = $total; |
|
| 192 | + $stats['all'] = $total; |
|
| 193 | + |
|
| 194 | + foreach ( $approved as $key ) { |
|
| 195 | + if ( empty( $stats[ $key ] ) ) { |
|
| 196 | + $stats[ $key ] = 0; |
|
| 197 | + } |
|
| 198 | + } |
|
| 199 | + |
|
| 200 | + $stats = (object) $stats; |
|
| 201 | + set_transient( 'as_comment_count', $stats ); |
|
| 202 | + } |
|
| 203 | + |
|
| 204 | + return $stats; |
|
| 205 | + } |
|
| 206 | + |
|
| 207 | + /** |
|
| 208 | + * Delete comment count cache whenever there is new comment or the status of a comment changes. Cache |
|
| 209 | + * will be regenerated next time ActionScheduler_wpCommentLogger::filter_comment_count() is called. |
|
| 210 | + */ |
|
| 211 | + public function delete_comment_count_cache() { |
|
| 212 | + delete_transient( 'as_comment_count' ); |
|
| 213 | + } |
|
| 214 | + |
|
| 215 | + /** |
|
| 216 | + * @codeCoverageIgnore |
|
| 217 | + */ |
|
| 218 | + public function init() { |
|
| 219 | + add_action( 'action_scheduler_before_process_queue', array( $this, 'disable_comment_counting' ), 10, 0 ); |
|
| 220 | + add_action( 'action_scheduler_after_process_queue', array( $this, 'enable_comment_counting' ), 10, 0 ); |
|
| 221 | + |
|
| 222 | + parent::init(); |
|
| 223 | + |
|
| 224 | + add_action( 'pre_get_comments', array( $this, 'filter_comment_queries' ), 10, 1 ); |
|
| 225 | + add_action( 'wp_count_comments', array( $this, 'filter_comment_count' ), 20, 2 ); // run after WC_Comments::wp_count_comments() to make sure we exclude order notes and action logs |
|
| 226 | + add_action( 'comment_feed_where', array( $this, 'filter_comment_feed' ), 10, 2 ); |
|
| 227 | + |
|
| 228 | + // Delete comments count cache whenever there is a new comment or a comment status changes |
|
| 229 | + add_action( 'wp_insert_comment', array( $this, 'delete_comment_count_cache' ) ); |
|
| 230 | + add_action( 'wp_set_comment_status', array( $this, 'delete_comment_count_cache' ) ); |
|
| 231 | + } |
|
| 232 | + |
|
| 233 | + public function disable_comment_counting() { |
|
| 234 | + wp_defer_comment_counting(true); |
|
| 235 | + } |
|
| 236 | + public function enable_comment_counting() { |
|
| 237 | + wp_defer_comment_counting(false); |
|
| 238 | + } |
|
| 239 | 239 | |
| 240 | 240 | } |
@@ -9,869 +9,869 @@ |
||
| 9 | 9 | */ |
| 10 | 10 | class ActionScheduler_DBStore extends ActionScheduler_Store { |
| 11 | 11 | |
| 12 | - /** |
|
| 13 | - * Used to share information about the before_date property of claims internally. |
|
| 14 | - * |
|
| 15 | - * This is used in preference to passing the same information as a method param |
|
| 16 | - * for backwards-compatibility reasons. |
|
| 17 | - * |
|
| 18 | - * @var DateTime|null |
|
| 19 | - */ |
|
| 20 | - private $claim_before_date = null; |
|
| 21 | - |
|
| 22 | - /** @var int */ |
|
| 23 | - protected static $max_args_length = 8000; |
|
| 24 | - |
|
| 25 | - /** @var int */ |
|
| 26 | - protected static $max_index_length = 191; |
|
| 27 | - |
|
| 28 | - /** |
|
| 29 | - * Initialize the data store |
|
| 30 | - * |
|
| 31 | - * @codeCoverageIgnore |
|
| 32 | - */ |
|
| 33 | - public function init() { |
|
| 34 | - $table_maker = new ActionScheduler_StoreSchema(); |
|
| 35 | - $table_maker->init(); |
|
| 36 | - $table_maker->register_tables(); |
|
| 37 | - } |
|
| 38 | - |
|
| 39 | - /** |
|
| 40 | - * Save an action. |
|
| 41 | - * |
|
| 42 | - * @param ActionScheduler_Action $action Action object. |
|
| 43 | - * @param DateTime $date Optional schedule date. Default null. |
|
| 44 | - * |
|
| 45 | - * @return int Action ID. |
|
| 46 | - * @throws RuntimeException Throws exception when saving the action fails. |
|
| 47 | - */ |
|
| 48 | - public function save_action( ActionScheduler_Action $action, \DateTime $date = null ) { |
|
| 49 | - try { |
|
| 50 | - |
|
| 51 | - $this->validate_action( $action ); |
|
| 52 | - |
|
| 53 | - /** @var \wpdb $wpdb */ |
|
| 54 | - global $wpdb; |
|
| 55 | - $data = array( |
|
| 56 | - 'hook' => $action->get_hook(), |
|
| 57 | - 'status' => ( $action->is_finished() ? self::STATUS_COMPLETE : self::STATUS_PENDING ), |
|
| 58 | - 'scheduled_date_gmt' => $this->get_scheduled_date_string( $action, $date ), |
|
| 59 | - 'scheduled_date_local' => $this->get_scheduled_date_string_local( $action, $date ), |
|
| 60 | - 'schedule' => serialize( $action->get_schedule() ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize |
|
| 61 | - 'group_id' => $this->get_group_id( $action->get_group() ), |
|
| 62 | - ); |
|
| 63 | - $args = wp_json_encode( $action->get_args() ); |
|
| 64 | - if ( strlen( $args ) <= static::$max_index_length ) { |
|
| 65 | - $data['args'] = $args; |
|
| 66 | - } else { |
|
| 67 | - $data['args'] = $this->hash_args( $args ); |
|
| 68 | - $data['extended_args'] = $args; |
|
| 69 | - } |
|
| 70 | - |
|
| 71 | - $table_name = ! empty( $wpdb->actionscheduler_actions ) ? $wpdb->actionscheduler_actions : $wpdb->prefix . 'actionscheduler_actions'; |
|
| 72 | - $wpdb->insert( $table_name, $data ); |
|
| 73 | - $action_id = $wpdb->insert_id; |
|
| 74 | - |
|
| 75 | - if ( is_wp_error( $action_id ) ) { |
|
| 76 | - throw new \RuntimeException( $action_id->get_error_message() ); |
|
| 77 | - } elseif ( empty( $action_id ) ) { |
|
| 78 | - throw new \RuntimeException( $wpdb->last_error ? $wpdb->last_error : __( 'Database error.', 'woocommerce' ) ); |
|
| 79 | - } |
|
| 80 | - |
|
| 81 | - do_action( 'action_scheduler_stored_action', $action_id ); |
|
| 82 | - |
|
| 83 | - return $action_id; |
|
| 84 | - } catch ( \Exception $e ) { |
|
| 85 | - /* translators: %s: error message */ |
|
| 86 | - throw new \RuntimeException( sprintf( __( 'Error saving action: %s', 'woocommerce' ), $e->getMessage() ), 0 ); |
|
| 87 | - } |
|
| 88 | - } |
|
| 89 | - |
|
| 90 | - /** |
|
| 91 | - * Generate a hash from json_encoded $args using MD5 as this isn't for security. |
|
| 92 | - * |
|
| 93 | - * @param string $args JSON encoded action args. |
|
| 94 | - * @return string |
|
| 95 | - */ |
|
| 96 | - protected function hash_args( $args ) { |
|
| 97 | - return md5( $args ); |
|
| 98 | - } |
|
| 99 | - |
|
| 100 | - /** |
|
| 101 | - * Get action args query param value from action args. |
|
| 102 | - * |
|
| 103 | - * @param array $args Action args. |
|
| 104 | - * @return string |
|
| 105 | - */ |
|
| 106 | - protected function get_args_for_query( $args ) { |
|
| 107 | - $encoded = wp_json_encode( $args ); |
|
| 108 | - if ( strlen( $encoded ) <= static::$max_index_length ) { |
|
| 109 | - return $encoded; |
|
| 110 | - } |
|
| 111 | - return $this->hash_args( $encoded ); |
|
| 112 | - } |
|
| 113 | - /** |
|
| 114 | - * Get a group's ID based on its name/slug. |
|
| 115 | - * |
|
| 116 | - * @param string $slug The string name of a group. |
|
| 117 | - * @param bool $create_if_not_exists Whether to create the group if it does not already exist. Default, true - create the group. |
|
| 118 | - * |
|
| 119 | - * @return int The group's ID, if it exists or is created, or 0 if it does not exist and is not created. |
|
| 120 | - */ |
|
| 121 | - protected function get_group_id( $slug, $create_if_not_exists = true ) { |
|
| 122 | - if ( empty( $slug ) ) { |
|
| 123 | - return 0; |
|
| 124 | - } |
|
| 125 | - /** @var \wpdb $wpdb */ |
|
| 126 | - global $wpdb; |
|
| 127 | - $group_id = (int) $wpdb->get_var( $wpdb->prepare( "SELECT group_id FROM {$wpdb->actionscheduler_groups} WHERE slug=%s", $slug ) ); |
|
| 128 | - if ( empty( $group_id ) && $create_if_not_exists ) { |
|
| 129 | - $group_id = $this->create_group( $slug ); |
|
| 130 | - } |
|
| 131 | - |
|
| 132 | - return $group_id; |
|
| 133 | - } |
|
| 134 | - |
|
| 135 | - /** |
|
| 136 | - * Create an action group. |
|
| 137 | - * |
|
| 138 | - * @param string $slug Group slug. |
|
| 139 | - * |
|
| 140 | - * @return int Group ID. |
|
| 141 | - */ |
|
| 142 | - protected function create_group( $slug ) { |
|
| 143 | - /** @var \wpdb $wpdb */ |
|
| 144 | - global $wpdb; |
|
| 145 | - $wpdb->insert( $wpdb->actionscheduler_groups, array( 'slug' => $slug ) ); |
|
| 146 | - |
|
| 147 | - return (int) $wpdb->insert_id; |
|
| 148 | - } |
|
| 149 | - |
|
| 150 | - /** |
|
| 151 | - * Retrieve an action. |
|
| 152 | - * |
|
| 153 | - * @param int $action_id Action ID. |
|
| 154 | - * |
|
| 155 | - * @return ActionScheduler_Action |
|
| 156 | - */ |
|
| 157 | - public function fetch_action( $action_id ) { |
|
| 158 | - /** @var \wpdb $wpdb */ |
|
| 159 | - global $wpdb; |
|
| 160 | - $data = $wpdb->get_row( |
|
| 161 | - $wpdb->prepare( |
|
| 162 | - "SELECT a.*, g.slug AS `group` FROM {$wpdb->actionscheduler_actions} a LEFT JOIN {$wpdb->actionscheduler_groups} g ON a.group_id=g.group_id WHERE a.action_id=%d", |
|
| 163 | - $action_id |
|
| 164 | - ) |
|
| 165 | - ); |
|
| 166 | - |
|
| 167 | - if ( empty( $data ) ) { |
|
| 168 | - return $this->get_null_action(); |
|
| 169 | - } |
|
| 170 | - |
|
| 171 | - if ( ! empty( $data->extended_args ) ) { |
|
| 172 | - $data->args = $data->extended_args; |
|
| 173 | - unset( $data->extended_args ); |
|
| 174 | - } |
|
| 175 | - |
|
| 176 | - // Convert NULL dates to zero dates. |
|
| 177 | - $date_fields = array( |
|
| 178 | - 'scheduled_date_gmt', |
|
| 179 | - 'scheduled_date_local', |
|
| 180 | - 'last_attempt_gmt', |
|
| 181 | - 'last_attempt_gmt', |
|
| 182 | - ); |
|
| 183 | - foreach ( $date_fields as $date_field ) { |
|
| 184 | - if ( is_null( $data->$date_field ) ) { |
|
| 185 | - $data->$date_field = ActionScheduler_StoreSchema::DEFAULT_DATE; |
|
| 186 | - } |
|
| 187 | - } |
|
| 188 | - |
|
| 189 | - try { |
|
| 190 | - $action = $this->make_action_from_db_record( $data ); |
|
| 191 | - } catch ( ActionScheduler_InvalidActionException $exception ) { |
|
| 192 | - do_action( 'action_scheduler_failed_fetch_action', $action_id, $exception ); |
|
| 193 | - return $this->get_null_action(); |
|
| 194 | - } |
|
| 195 | - |
|
| 196 | - return $action; |
|
| 197 | - } |
|
| 198 | - |
|
| 199 | - /** |
|
| 200 | - * Create a null action. |
|
| 201 | - * |
|
| 202 | - * @return ActionScheduler_NullAction |
|
| 203 | - */ |
|
| 204 | - protected function get_null_action() { |
|
| 205 | - return new ActionScheduler_NullAction(); |
|
| 206 | - } |
|
| 207 | - |
|
| 208 | - /** |
|
| 209 | - * Create an action from a database record. |
|
| 210 | - * |
|
| 211 | - * @param object $data Action database record. |
|
| 212 | - * |
|
| 213 | - * @return ActionScheduler_Action|ActionScheduler_CanceledAction|ActionScheduler_FinishedAction |
|
| 214 | - */ |
|
| 215 | - protected function make_action_from_db_record( $data ) { |
|
| 216 | - |
|
| 217 | - $hook = $data->hook; |
|
| 218 | - $args = json_decode( $data->args, true ); |
|
| 219 | - $schedule = unserialize( $data->schedule ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize |
|
| 220 | - |
|
| 221 | - $this->validate_args( $args, $data->action_id ); |
|
| 222 | - $this->validate_schedule( $schedule, $data->action_id ); |
|
| 223 | - |
|
| 224 | - if ( empty( $schedule ) ) { |
|
| 225 | - $schedule = new ActionScheduler_NullSchedule(); |
|
| 226 | - } |
|
| 227 | - $group = $data->group ? $data->group : ''; |
|
| 228 | - |
|
| 229 | - return ActionScheduler::factory()->get_stored_action( $data->status, $data->hook, $args, $schedule, $group ); |
|
| 230 | - } |
|
| 231 | - |
|
| 232 | - /** |
|
| 233 | - * Returns the SQL statement to query (or count) actions. |
|
| 234 | - * |
|
| 235 | - * @since x.x.x $query['status'] accepts array of statuses instead of a single status. |
|
| 236 | - * |
|
| 237 | - * @param array $query Filtering options. |
|
| 238 | - * @param string $select_or_count Whether the SQL should select and return the IDs or just the row count. |
|
| 239 | - * |
|
| 240 | - * @return string SQL statement already properly escaped. |
|
| 241 | - * @throws InvalidArgumentException If the query is invalid. |
|
| 242 | - */ |
|
| 243 | - protected function get_query_actions_sql( array $query, $select_or_count = 'select' ) { |
|
| 244 | - |
|
| 245 | - if ( ! in_array( $select_or_count, array( 'select', 'count' ), true ) ) { |
|
| 246 | - throw new InvalidArgumentException( __( 'Invalid value for select or count parameter. Cannot query actions.', 'woocommerce' ) ); |
|
| 247 | - } |
|
| 248 | - |
|
| 249 | - $query = wp_parse_args( |
|
| 250 | - $query, |
|
| 251 | - array( |
|
| 252 | - 'hook' => '', |
|
| 253 | - 'args' => null, |
|
| 254 | - 'date' => null, |
|
| 255 | - 'date_compare' => '<=', |
|
| 256 | - 'modified' => null, |
|
| 257 | - 'modified_compare' => '<=', |
|
| 258 | - 'group' => '', |
|
| 259 | - 'status' => '', |
|
| 260 | - 'claimed' => null, |
|
| 261 | - 'per_page' => 5, |
|
| 262 | - 'offset' => 0, |
|
| 263 | - 'orderby' => 'date', |
|
| 264 | - 'order' => 'ASC', |
|
| 265 | - ) |
|
| 266 | - ); |
|
| 267 | - |
|
| 268 | - /** @var \wpdb $wpdb */ |
|
| 269 | - global $wpdb; |
|
| 270 | - $sql = ( 'count' === $select_or_count ) ? 'SELECT count(a.action_id)' : 'SELECT a.action_id'; |
|
| 271 | - $sql .= " FROM {$wpdb->actionscheduler_actions} a"; |
|
| 272 | - $sql_params = array(); |
|
| 273 | - |
|
| 274 | - if ( ! empty( $query['group'] ) || 'group' === $query['orderby'] ) { |
|
| 275 | - $sql .= " LEFT JOIN {$wpdb->actionscheduler_groups} g ON g.group_id=a.group_id"; |
|
| 276 | - } |
|
| 277 | - |
|
| 278 | - $sql .= ' WHERE 1=1'; |
|
| 279 | - |
|
| 280 | - if ( ! empty( $query['group'] ) ) { |
|
| 281 | - $sql .= ' AND g.slug=%s'; |
|
| 282 | - $sql_params[] = $query['group']; |
|
| 283 | - } |
|
| 284 | - |
|
| 285 | - if ( $query['hook'] ) { |
|
| 286 | - $sql .= ' AND a.hook=%s'; |
|
| 287 | - $sql_params[] = $query['hook']; |
|
| 288 | - } |
|
| 289 | - if ( ! is_null( $query['args'] ) ) { |
|
| 290 | - $sql .= ' AND a.args=%s'; |
|
| 291 | - $sql_params[] = $this->get_args_for_query( $query['args'] ); |
|
| 292 | - } |
|
| 293 | - |
|
| 294 | - if ( $query['status'] ) { |
|
| 295 | - $statuses = (array) $query['status']; |
|
| 296 | - $placeholders = array_fill( 0, count( $statuses ), '%s' ); |
|
| 297 | - $sql .= ' AND a.status IN (' . join( ', ', $placeholders ) . ')'; |
|
| 298 | - $sql_params = array_merge( $sql_params, array_values( $statuses ) ); |
|
| 299 | - } |
|
| 300 | - |
|
| 301 | - if ( $query['date'] instanceof \DateTime ) { |
|
| 302 | - $date = clone $query['date']; |
|
| 303 | - $date->setTimezone( new \DateTimeZone( 'UTC' ) ); |
|
| 304 | - $date_string = $date->format( 'Y-m-d H:i:s' ); |
|
| 305 | - $comparator = $this->validate_sql_comparator( $query['date_compare'] ); |
|
| 306 | - $sql .= " AND a.scheduled_date_gmt $comparator %s"; |
|
| 307 | - $sql_params[] = $date_string; |
|
| 308 | - } |
|
| 309 | - |
|
| 310 | - if ( $query['modified'] instanceof \DateTime ) { |
|
| 311 | - $modified = clone $query['modified']; |
|
| 312 | - $modified->setTimezone( new \DateTimeZone( 'UTC' ) ); |
|
| 313 | - $date_string = $modified->format( 'Y-m-d H:i:s' ); |
|
| 314 | - $comparator = $this->validate_sql_comparator( $query['modified_compare'] ); |
|
| 315 | - $sql .= " AND a.last_attempt_gmt $comparator %s"; |
|
| 316 | - $sql_params[] = $date_string; |
|
| 317 | - } |
|
| 318 | - |
|
| 319 | - if ( true === $query['claimed'] ) { |
|
| 320 | - $sql .= ' AND a.claim_id != 0'; |
|
| 321 | - } elseif ( false === $query['claimed'] ) { |
|
| 322 | - $sql .= ' AND a.claim_id = 0'; |
|
| 323 | - } elseif ( ! is_null( $query['claimed'] ) ) { |
|
| 324 | - $sql .= ' AND a.claim_id = %d'; |
|
| 325 | - $sql_params[] = $query['claimed']; |
|
| 326 | - } |
|
| 327 | - |
|
| 328 | - if ( ! empty( $query['search'] ) ) { |
|
| 329 | - $sql .= ' AND (a.hook LIKE %s OR (a.extended_args IS NULL AND a.args LIKE %s) OR a.extended_args LIKE %s'; |
|
| 330 | - for ( $i = 0; $i < 3; $i++ ) { |
|
| 331 | - $sql_params[] = sprintf( '%%%s%%', $query['search'] ); |
|
| 332 | - } |
|
| 333 | - |
|
| 334 | - $search_claim_id = (int) $query['search']; |
|
| 335 | - if ( $search_claim_id ) { |
|
| 336 | - $sql .= ' OR a.claim_id = %d'; |
|
| 337 | - $sql_params[] = $search_claim_id; |
|
| 338 | - } |
|
| 339 | - |
|
| 340 | - $sql .= ')'; |
|
| 341 | - } |
|
| 342 | - |
|
| 343 | - if ( 'select' === $select_or_count ) { |
|
| 344 | - if ( 'ASC' === strtoupper( $query['order'] ) ) { |
|
| 345 | - $order = 'ASC'; |
|
| 346 | - } else { |
|
| 347 | - $order = 'DESC'; |
|
| 348 | - } |
|
| 349 | - switch ( $query['orderby'] ) { |
|
| 350 | - case 'hook': |
|
| 351 | - $sql .= " ORDER BY a.hook $order"; |
|
| 352 | - break; |
|
| 353 | - case 'group': |
|
| 354 | - $sql .= " ORDER BY g.slug $order"; |
|
| 355 | - break; |
|
| 356 | - case 'modified': |
|
| 357 | - $sql .= " ORDER BY a.last_attempt_gmt $order"; |
|
| 358 | - break; |
|
| 359 | - case 'none': |
|
| 360 | - break; |
|
| 361 | - case 'action_id': |
|
| 362 | - $sql .= " ORDER BY a.action_id $order"; |
|
| 363 | - break; |
|
| 364 | - case 'date': |
|
| 365 | - default: |
|
| 366 | - $sql .= " ORDER BY a.scheduled_date_gmt $order"; |
|
| 367 | - break; |
|
| 368 | - } |
|
| 369 | - |
|
| 370 | - if ( $query['per_page'] > 0 ) { |
|
| 371 | - $sql .= ' LIMIT %d, %d'; |
|
| 372 | - $sql_params[] = $query['offset']; |
|
| 373 | - $sql_params[] = $query['per_page']; |
|
| 374 | - } |
|
| 375 | - } |
|
| 376 | - |
|
| 377 | - if ( ! empty( $sql_params ) ) { |
|
| 378 | - $sql = $wpdb->prepare( $sql, $sql_params ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
|
| 379 | - } |
|
| 380 | - |
|
| 381 | - return $sql; |
|
| 382 | - } |
|
| 383 | - |
|
| 384 | - /** |
|
| 385 | - * Query for action count or list of action IDs. |
|
| 386 | - * |
|
| 387 | - * @since x.x.x $query['status'] accepts array of statuses instead of a single status. |
|
| 388 | - * |
|
| 389 | - * @see ActionScheduler_Store::query_actions for $query arg usage. |
|
| 390 | - * |
|
| 391 | - * @param array $query Query filtering options. |
|
| 392 | - * @param string $query_type Whether to select or count the results. Defaults to select. |
|
| 393 | - * |
|
| 394 | - * @return string|array|null The IDs of actions matching the query. Null on failure. |
|
| 395 | - */ |
|
| 396 | - public function query_actions( $query = array(), $query_type = 'select' ) { |
|
| 397 | - /** @var wpdb $wpdb */ |
|
| 398 | - global $wpdb; |
|
| 399 | - |
|
| 400 | - $sql = $this->get_query_actions_sql( $query, $query_type ); |
|
| 401 | - |
|
| 402 | - return ( 'count' === $query_type ) ? $wpdb->get_var( $sql ) : $wpdb->get_col( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoSql, WordPress.DB.DirectDatabaseQuery.NoCaching |
|
| 403 | - } |
|
| 404 | - |
|
| 405 | - /** |
|
| 406 | - * Get a count of all actions in the store, grouped by status. |
|
| 407 | - * |
|
| 408 | - * @return array Set of 'status' => int $count pairs for statuses with 1 or more actions of that status. |
|
| 409 | - */ |
|
| 410 | - public function action_counts() { |
|
| 411 | - global $wpdb; |
|
| 412 | - |
|
| 413 | - $sql = "SELECT a.status, count(a.status) as 'count'"; |
|
| 414 | - $sql .= " FROM {$wpdb->actionscheduler_actions} a"; |
|
| 415 | - $sql .= ' GROUP BY a.status'; |
|
| 416 | - |
|
| 417 | - $actions_count_by_status = array(); |
|
| 418 | - $action_stati_and_labels = $this->get_status_labels(); |
|
| 419 | - |
|
| 420 | - foreach ( $wpdb->get_results( $sql ) as $action_data ) { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
|
| 421 | - // Ignore any actions with invalid status. |
|
| 422 | - if ( array_key_exists( $action_data->status, $action_stati_and_labels ) ) { |
|
| 423 | - $actions_count_by_status[ $action_data->status ] = $action_data->count; |
|
| 424 | - } |
|
| 425 | - } |
|
| 426 | - |
|
| 427 | - return $actions_count_by_status; |
|
| 428 | - } |
|
| 429 | - |
|
| 430 | - /** |
|
| 431 | - * Cancel an action. |
|
| 432 | - * |
|
| 433 | - * @param int $action_id Action ID. |
|
| 434 | - * |
|
| 435 | - * @return void |
|
| 436 | - * @throws \InvalidArgumentException If the action update failed. |
|
| 437 | - */ |
|
| 438 | - public function cancel_action( $action_id ) { |
|
| 439 | - /** @var \wpdb $wpdb */ |
|
| 440 | - global $wpdb; |
|
| 441 | - |
|
| 442 | - $updated = $wpdb->update( |
|
| 443 | - $wpdb->actionscheduler_actions, |
|
| 444 | - array( 'status' => self::STATUS_CANCELED ), |
|
| 445 | - array( 'action_id' => $action_id ), |
|
| 446 | - array( '%s' ), |
|
| 447 | - array( '%d' ) |
|
| 448 | - ); |
|
| 449 | - if ( false === $updated ) { |
|
| 450 | - /* translators: %s: action ID */ |
|
| 451 | - throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'woocommerce' ), $action_id ) ); |
|
| 452 | - } |
|
| 453 | - do_action( 'action_scheduler_canceled_action', $action_id ); |
|
| 454 | - } |
|
| 455 | - |
|
| 456 | - /** |
|
| 457 | - * Cancel pending actions by hook. |
|
| 458 | - * |
|
| 459 | - * @since 3.0.0 |
|
| 460 | - * |
|
| 461 | - * @param string $hook Hook name. |
|
| 462 | - * |
|
| 463 | - * @return void |
|
| 464 | - */ |
|
| 465 | - public function cancel_actions_by_hook( $hook ) { |
|
| 466 | - $this->bulk_cancel_actions( array( 'hook' => $hook ) ); |
|
| 467 | - } |
|
| 468 | - |
|
| 469 | - /** |
|
| 470 | - * Cancel pending actions by group. |
|
| 471 | - * |
|
| 472 | - * @param string $group Group slug. |
|
| 473 | - * |
|
| 474 | - * @return void |
|
| 475 | - */ |
|
| 476 | - public function cancel_actions_by_group( $group ) { |
|
| 477 | - $this->bulk_cancel_actions( array( 'group' => $group ) ); |
|
| 478 | - } |
|
| 479 | - |
|
| 480 | - /** |
|
| 481 | - * Bulk cancel actions. |
|
| 482 | - * |
|
| 483 | - * @since 3.0.0 |
|
| 484 | - * |
|
| 485 | - * @param array $query_args Query parameters. |
|
| 486 | - */ |
|
| 487 | - protected function bulk_cancel_actions( $query_args ) { |
|
| 488 | - /** @var \wpdb $wpdb */ |
|
| 489 | - global $wpdb; |
|
| 490 | - |
|
| 491 | - if ( ! is_array( $query_args ) ) { |
|
| 492 | - return; |
|
| 493 | - } |
|
| 494 | - |
|
| 495 | - // Don't cancel actions that are already canceled. |
|
| 496 | - if ( isset( $query_args['status'] ) && self::STATUS_CANCELED === $query_args['status'] ) { |
|
| 497 | - return; |
|
| 498 | - } |
|
| 499 | - |
|
| 500 | - $action_ids = true; |
|
| 501 | - $query_args = wp_parse_args( |
|
| 502 | - $query_args, |
|
| 503 | - array( |
|
| 504 | - 'per_page' => 1000, |
|
| 505 | - 'status' => self::STATUS_PENDING, |
|
| 506 | - 'orderby' => 'action_id', |
|
| 507 | - ) |
|
| 508 | - ); |
|
| 509 | - |
|
| 510 | - while ( $action_ids ) { |
|
| 511 | - $action_ids = $this->query_actions( $query_args ); |
|
| 512 | - if ( empty( $action_ids ) ) { |
|
| 513 | - break; |
|
| 514 | - } |
|
| 515 | - |
|
| 516 | - $format = array_fill( 0, count( $action_ids ), '%d' ); |
|
| 517 | - $query_in = '(' . implode( ',', $format ) . ')'; |
|
| 518 | - $parameters = $action_ids; |
|
| 519 | - array_unshift( $parameters, self::STATUS_CANCELED ); |
|
| 520 | - |
|
| 521 | - $wpdb->query( |
|
| 522 | - $wpdb->prepare( |
|
| 523 | - "UPDATE {$wpdb->actionscheduler_actions} SET status = %s WHERE action_id IN {$query_in}", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared |
|
| 524 | - $parameters |
|
| 525 | - ) |
|
| 526 | - ); |
|
| 527 | - |
|
| 528 | - do_action( 'action_scheduler_bulk_cancel_actions', $action_ids ); |
|
| 529 | - } |
|
| 530 | - } |
|
| 531 | - |
|
| 532 | - /** |
|
| 533 | - * Delete an action. |
|
| 534 | - * |
|
| 535 | - * @param int $action_id Action ID. |
|
| 536 | - * @throws \InvalidArgumentException If the action deletion failed. |
|
| 537 | - */ |
|
| 538 | - public function delete_action( $action_id ) { |
|
| 539 | - /** @var \wpdb $wpdb */ |
|
| 540 | - global $wpdb; |
|
| 541 | - $deleted = $wpdb->delete( $wpdb->actionscheduler_actions, array( 'action_id' => $action_id ), array( '%d' ) ); |
|
| 542 | - if ( empty( $deleted ) ) { |
|
| 543 | - throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'woocommerce' ), $action_id ) ); //phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment |
|
| 544 | - } |
|
| 545 | - do_action( 'action_scheduler_deleted_action', $action_id ); |
|
| 546 | - } |
|
| 547 | - |
|
| 548 | - /** |
|
| 549 | - * Get the schedule date for an action. |
|
| 550 | - * |
|
| 551 | - * @param string $action_id Action ID. |
|
| 552 | - * |
|
| 553 | - * @return \DateTime The local date the action is scheduled to run, or the date that it ran. |
|
| 554 | - */ |
|
| 555 | - public function get_date( $action_id ) { |
|
| 556 | - $date = $this->get_date_gmt( $action_id ); |
|
| 557 | - ActionScheduler_TimezoneHelper::set_local_timezone( $date ); |
|
| 558 | - return $date; |
|
| 559 | - } |
|
| 560 | - |
|
| 561 | - /** |
|
| 562 | - * Get the GMT schedule date for an action. |
|
| 563 | - * |
|
| 564 | - * @param int $action_id Action ID. |
|
| 565 | - * |
|
| 566 | - * @throws \InvalidArgumentException If action cannot be identified. |
|
| 567 | - * @return \DateTime The GMT date the action is scheduled to run, or the date that it ran. |
|
| 568 | - */ |
|
| 569 | - protected function get_date_gmt( $action_id ) { |
|
| 570 | - /** @var \wpdb $wpdb */ |
|
| 571 | - global $wpdb; |
|
| 572 | - $record = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->actionscheduler_actions} WHERE action_id=%d", $action_id ) ); |
|
| 573 | - if ( empty( $record ) ) { |
|
| 574 | - throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'woocommerce' ), $action_id ) ); //phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment |
|
| 575 | - } |
|
| 576 | - if ( self::STATUS_PENDING === $record->status ) { |
|
| 577 | - return as_get_datetime_object( $record->scheduled_date_gmt ); |
|
| 578 | - } else { |
|
| 579 | - return as_get_datetime_object( $record->last_attempt_gmt ); |
|
| 580 | - } |
|
| 581 | - } |
|
| 582 | - |
|
| 583 | - /** |
|
| 584 | - * Stake a claim on actions. |
|
| 585 | - * |
|
| 586 | - * @param int $max_actions Maximum number of action to include in claim. |
|
| 587 | - * @param \DateTime $before_date Jobs must be schedule before this date. Defaults to now. |
|
| 588 | - * @param array $hooks Hooks to filter for. |
|
| 589 | - * @param string $group Group to filter for. |
|
| 590 | - * |
|
| 591 | - * @return ActionScheduler_ActionClaim |
|
| 592 | - */ |
|
| 593 | - public function stake_claim( $max_actions = 10, \DateTime $before_date = null, $hooks = array(), $group = '' ) { |
|
| 594 | - $claim_id = $this->generate_claim_id(); |
|
| 595 | - |
|
| 596 | - $this->claim_before_date = $before_date; |
|
| 597 | - $this->claim_actions( $claim_id, $max_actions, $before_date, $hooks, $group ); |
|
| 598 | - $action_ids = $this->find_actions_by_claim_id( $claim_id ); |
|
| 599 | - $this->claim_before_date = null; |
|
| 600 | - |
|
| 601 | - return new ActionScheduler_ActionClaim( $claim_id, $action_ids ); |
|
| 602 | - } |
|
| 603 | - |
|
| 604 | - /** |
|
| 605 | - * Generate a new action claim. |
|
| 606 | - * |
|
| 607 | - * @return int Claim ID. |
|
| 608 | - */ |
|
| 609 | - protected function generate_claim_id() { |
|
| 610 | - /** @var \wpdb $wpdb */ |
|
| 611 | - global $wpdb; |
|
| 612 | - $now = as_get_datetime_object(); |
|
| 613 | - $wpdb->insert( $wpdb->actionscheduler_claims, array( 'date_created_gmt' => $now->format( 'Y-m-d H:i:s' ) ) ); |
|
| 614 | - |
|
| 615 | - return $wpdb->insert_id; |
|
| 616 | - } |
|
| 617 | - |
|
| 618 | - /** |
|
| 619 | - * Mark actions claimed. |
|
| 620 | - * |
|
| 621 | - * @param string $claim_id Claim Id. |
|
| 622 | - * @param int $limit Number of action to include in claim. |
|
| 623 | - * @param \DateTime $before_date Should use UTC timezone. |
|
| 624 | - * @param array $hooks Hooks to filter for. |
|
| 625 | - * @param string $group Group to filter for. |
|
| 626 | - * |
|
| 627 | - * @return int The number of actions that were claimed. |
|
| 628 | - * @throws \InvalidArgumentException Throws InvalidArgumentException if group doesn't exist. |
|
| 629 | - * @throws \RuntimeException Throws RuntimeException if unable to claim action. |
|
| 630 | - */ |
|
| 631 | - protected function claim_actions( $claim_id, $limit, \DateTime $before_date = null, $hooks = array(), $group = '' ) { |
|
| 632 | - /** @var \wpdb $wpdb */ |
|
| 633 | - global $wpdb; |
|
| 634 | - |
|
| 635 | - $now = as_get_datetime_object(); |
|
| 636 | - $date = is_null( $before_date ) ? $now : clone $before_date; |
|
| 637 | - |
|
| 638 | - // can't use $wpdb->update() because of the <= condition. |
|
| 639 | - $update = "UPDATE {$wpdb->actionscheduler_actions} SET claim_id=%d, last_attempt_gmt=%s, last_attempt_local=%s"; |
|
| 640 | - $params = array( |
|
| 641 | - $claim_id, |
|
| 642 | - $now->format( 'Y-m-d H:i:s' ), |
|
| 643 | - current_time( 'mysql' ), |
|
| 644 | - ); |
|
| 645 | - |
|
| 646 | - $where = 'WHERE claim_id = 0 AND scheduled_date_gmt <= %s AND status=%s'; |
|
| 647 | - $params[] = $date->format( 'Y-m-d H:i:s' ); |
|
| 648 | - $params[] = self::STATUS_PENDING; |
|
| 649 | - |
|
| 650 | - if ( ! empty( $hooks ) ) { |
|
| 651 | - $placeholders = array_fill( 0, count( $hooks ), '%s' ); |
|
| 652 | - $where .= ' AND hook IN (' . join( ', ', $placeholders ) . ')'; |
|
| 653 | - $params = array_merge( $params, array_values( $hooks ) ); |
|
| 654 | - } |
|
| 655 | - |
|
| 656 | - if ( ! empty( $group ) ) { |
|
| 657 | - |
|
| 658 | - $group_id = $this->get_group_id( $group, false ); |
|
| 659 | - |
|
| 660 | - // throw exception if no matching group found, this matches ActionScheduler_wpPostStore's behaviour. |
|
| 661 | - if ( empty( $group_id ) ) { |
|
| 662 | - /* translators: %s: group name */ |
|
| 663 | - throw new InvalidArgumentException( sprintf( __( 'The group "%s" does not exist.', 'woocommerce' ), $group ) ); |
|
| 664 | - } |
|
| 665 | - |
|
| 666 | - $where .= ' AND group_id = %d'; |
|
| 667 | - $params[] = $group_id; |
|
| 668 | - } |
|
| 669 | - |
|
| 670 | - /** |
|
| 671 | - * Sets the order-by clause used in the action claim query. |
|
| 672 | - * |
|
| 673 | - * @since x.x.x |
|
| 674 | - * |
|
| 675 | - * @param string $order_by_sql |
|
| 676 | - */ |
|
| 677 | - $order = apply_filters( 'action_scheduler_claim_actions_order_by', 'ORDER BY attempts ASC, scheduled_date_gmt ASC, action_id ASC' ); |
|
| 678 | - $params[] = $limit; |
|
| 679 | - |
|
| 680 | - $sql = $wpdb->prepare( "{$update} {$where} {$order} LIMIT %d", $params ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders |
|
| 681 | - $rows_affected = $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching |
|
| 682 | - if ( false === $rows_affected ) { |
|
| 683 | - throw new \RuntimeException( __( 'Unable to claim actions. Database error.', 'woocommerce' ) ); |
|
| 684 | - } |
|
| 685 | - |
|
| 686 | - return (int) $rows_affected; |
|
| 687 | - } |
|
| 688 | - |
|
| 689 | - /** |
|
| 690 | - * Get the number of active claims. |
|
| 691 | - * |
|
| 692 | - * @return int |
|
| 693 | - */ |
|
| 694 | - public function get_claim_count() { |
|
| 695 | - global $wpdb; |
|
| 696 | - |
|
| 697 | - $sql = "SELECT COUNT(DISTINCT claim_id) FROM {$wpdb->actionscheduler_actions} WHERE claim_id != 0 AND status IN ( %s, %s)"; |
|
| 698 | - $sql = $wpdb->prepare( $sql, array( self::STATUS_PENDING, self::STATUS_RUNNING ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
|
| 699 | - |
|
| 700 | - return (int) $wpdb->get_var( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
|
| 701 | - } |
|
| 702 | - |
|
| 703 | - /** |
|
| 704 | - * Return an action's claim ID, as stored in the claim_id column. |
|
| 705 | - * |
|
| 706 | - * @param string $action_id Action ID. |
|
| 707 | - * @return mixed |
|
| 708 | - */ |
|
| 709 | - public function get_claim_id( $action_id ) { |
|
| 710 | - /** @var \wpdb $wpdb */ |
|
| 711 | - global $wpdb; |
|
| 712 | - |
|
| 713 | - $sql = "SELECT claim_id FROM {$wpdb->actionscheduler_actions} WHERE action_id=%d"; |
|
| 714 | - $sql = $wpdb->prepare( $sql, $action_id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
|
| 715 | - |
|
| 716 | - return (int) $wpdb->get_var( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
|
| 717 | - } |
|
| 718 | - |
|
| 719 | - /** |
|
| 720 | - * Retrieve the action IDs of action in a claim. |
|
| 721 | - * |
|
| 722 | - * @param int $claim_id Claim ID. |
|
| 723 | - * @return int[] |
|
| 724 | - */ |
|
| 725 | - public function find_actions_by_claim_id( $claim_id ) { |
|
| 726 | - /** @var \wpdb $wpdb */ |
|
| 727 | - global $wpdb; |
|
| 728 | - |
|
| 729 | - $action_ids = array(); |
|
| 730 | - $before_date = isset( $this->claim_before_date ) ? $this->claim_before_date : as_get_datetime_object(); |
|
| 731 | - $cut_off = $before_date->format( 'Y-m-d H:i:s' ); |
|
| 732 | - |
|
| 733 | - $sql = $wpdb->prepare( |
|
| 734 | - "SELECT action_id, scheduled_date_gmt FROM {$wpdb->actionscheduler_actions} WHERE claim_id = %d", |
|
| 735 | - $claim_id |
|
| 736 | - ); |
|
| 737 | - |
|
| 738 | - // Verify that the scheduled date for each action is within the expected bounds (in some unusual |
|
| 739 | - // cases, we cannot depend on MySQL to honor all of the WHERE conditions we specify). |
|
| 740 | - foreach ( $wpdb->get_results( $sql ) as $claimed_action ) { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
|
| 741 | - if ( $claimed_action->scheduled_date_gmt <= $cut_off ) { |
|
| 742 | - $action_ids[] = absint( $claimed_action->action_id ); |
|
| 743 | - } |
|
| 744 | - } |
|
| 745 | - |
|
| 746 | - return $action_ids; |
|
| 747 | - } |
|
| 748 | - |
|
| 749 | - /** |
|
| 750 | - * Release actions from a claim and delete the claim. |
|
| 751 | - * |
|
| 752 | - * @param ActionScheduler_ActionClaim $claim Claim object. |
|
| 753 | - */ |
|
| 754 | - public function release_claim( ActionScheduler_ActionClaim $claim ) { |
|
| 755 | - /** @var \wpdb $wpdb */ |
|
| 756 | - global $wpdb; |
|
| 757 | - $wpdb->update( $wpdb->actionscheduler_actions, array( 'claim_id' => 0 ), array( 'claim_id' => $claim->get_id() ), array( '%d' ), array( '%d' ) ); |
|
| 758 | - $wpdb->delete( $wpdb->actionscheduler_claims, array( 'claim_id' => $claim->get_id() ), array( '%d' ) ); |
|
| 759 | - } |
|
| 760 | - |
|
| 761 | - /** |
|
| 762 | - * Remove the claim from an action. |
|
| 763 | - * |
|
| 764 | - * @param int $action_id Action ID. |
|
| 765 | - * |
|
| 766 | - * @return void |
|
| 767 | - */ |
|
| 768 | - public function unclaim_action( $action_id ) { |
|
| 769 | - /** @var \wpdb $wpdb */ |
|
| 770 | - global $wpdb; |
|
| 771 | - $wpdb->update( |
|
| 772 | - $wpdb->actionscheduler_actions, |
|
| 773 | - array( 'claim_id' => 0 ), |
|
| 774 | - array( 'action_id' => $action_id ), |
|
| 775 | - array( '%s' ), |
|
| 776 | - array( '%d' ) |
|
| 777 | - ); |
|
| 778 | - } |
|
| 779 | - |
|
| 780 | - /** |
|
| 781 | - * Mark an action as failed. |
|
| 782 | - * |
|
| 783 | - * @param int $action_id Action ID. |
|
| 784 | - * @throws \InvalidArgumentException Throw an exception if action was not updated. |
|
| 785 | - */ |
|
| 786 | - public function mark_failure( $action_id ) { |
|
| 787 | - /** @var \wpdb $wpdb */ |
|
| 788 | - global $wpdb; |
|
| 789 | - $updated = $wpdb->update( |
|
| 790 | - $wpdb->actionscheduler_actions, |
|
| 791 | - array( 'status' => self::STATUS_FAILED ), |
|
| 792 | - array( 'action_id' => $action_id ), |
|
| 793 | - array( '%s' ), |
|
| 794 | - array( '%d' ) |
|
| 795 | - ); |
|
| 796 | - if ( empty( $updated ) ) { |
|
| 797 | - throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'woocommerce' ), $action_id ) ); //phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment |
|
| 798 | - } |
|
| 799 | - } |
|
| 800 | - |
|
| 801 | - /** |
|
| 802 | - * Add execution message to action log. |
|
| 803 | - * |
|
| 804 | - * @param int $action_id Action ID. |
|
| 805 | - * |
|
| 806 | - * @return void |
|
| 807 | - */ |
|
| 808 | - public function log_execution( $action_id ) { |
|
| 809 | - /** @var \wpdb $wpdb */ |
|
| 810 | - global $wpdb; |
|
| 811 | - |
|
| 812 | - $sql = "UPDATE {$wpdb->actionscheduler_actions} SET attempts = attempts+1, status=%s, last_attempt_gmt = %s, last_attempt_local = %s WHERE action_id = %d"; |
|
| 813 | - $sql = $wpdb->prepare( $sql, self::STATUS_RUNNING, current_time( 'mysql', true ), current_time( 'mysql' ), $action_id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
|
| 814 | - $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
|
| 815 | - } |
|
| 816 | - |
|
| 817 | - /** |
|
| 818 | - * Mark an action as complete. |
|
| 819 | - * |
|
| 820 | - * @param int $action_id Action ID. |
|
| 821 | - * |
|
| 822 | - * @return void |
|
| 823 | - * @throws \InvalidArgumentException Throw an exception if action was not updated. |
|
| 824 | - */ |
|
| 825 | - public function mark_complete( $action_id ) { |
|
| 826 | - /** @var \wpdb $wpdb */ |
|
| 827 | - global $wpdb; |
|
| 828 | - $updated = $wpdb->update( |
|
| 829 | - $wpdb->actionscheduler_actions, |
|
| 830 | - array( |
|
| 831 | - 'status' => self::STATUS_COMPLETE, |
|
| 832 | - 'last_attempt_gmt' => current_time( 'mysql', true ), |
|
| 833 | - 'last_attempt_local' => current_time( 'mysql' ), |
|
| 834 | - ), |
|
| 835 | - array( 'action_id' => $action_id ), |
|
| 836 | - array( '%s' ), |
|
| 837 | - array( '%d' ) |
|
| 838 | - ); |
|
| 839 | - if ( empty( $updated ) ) { |
|
| 840 | - throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'woocommerce' ), $action_id ) ); //phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment |
|
| 841 | - } |
|
| 842 | - |
|
| 843 | - /** |
|
| 844 | - * Fires after a scheduled action has been completed. |
|
| 845 | - * |
|
| 846 | - * @since 3.4.2 |
|
| 847 | - * |
|
| 848 | - * @param int $action_id Action ID. |
|
| 849 | - */ |
|
| 850 | - do_action( 'action_scheduler_completed_action', $action_id ); |
|
| 851 | - } |
|
| 852 | - |
|
| 853 | - /** |
|
| 854 | - * Get an action's status. |
|
| 855 | - * |
|
| 856 | - * @param int $action_id Action ID. |
|
| 857 | - * |
|
| 858 | - * @return string |
|
| 859 | - * @throws \InvalidArgumentException Throw an exception if not status was found for action_id. |
|
| 860 | - * @throws \RuntimeException Throw an exception if action status could not be retrieved. |
|
| 861 | - */ |
|
| 862 | - public function get_status( $action_id ) { |
|
| 863 | - /** @var \wpdb $wpdb */ |
|
| 864 | - global $wpdb; |
|
| 865 | - $sql = "SELECT status FROM {$wpdb->actionscheduler_actions} WHERE action_id=%d"; |
|
| 866 | - $sql = $wpdb->prepare( $sql, $action_id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
|
| 867 | - $status = $wpdb->get_var( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
|
| 868 | - |
|
| 869 | - if ( null === $status ) { |
|
| 870 | - throw new \InvalidArgumentException( __( 'Invalid action ID. No status found.', 'woocommerce' ) ); |
|
| 871 | - } elseif ( empty( $status ) ) { |
|
| 872 | - throw new \RuntimeException( __( 'Unknown status found for action.', 'woocommerce' ) ); |
|
| 873 | - } else { |
|
| 874 | - return $status; |
|
| 875 | - } |
|
| 876 | - } |
|
| 12 | + /** |
|
| 13 | + * Used to share information about the before_date property of claims internally. |
|
| 14 | + * |
|
| 15 | + * This is used in preference to passing the same information as a method param |
|
| 16 | + * for backwards-compatibility reasons. |
|
| 17 | + * |
|
| 18 | + * @var DateTime|null |
|
| 19 | + */ |
|
| 20 | + private $claim_before_date = null; |
|
| 21 | + |
|
| 22 | + /** @var int */ |
|
| 23 | + protected static $max_args_length = 8000; |
|
| 24 | + |
|
| 25 | + /** @var int */ |
|
| 26 | + protected static $max_index_length = 191; |
|
| 27 | + |
|
| 28 | + /** |
|
| 29 | + * Initialize the data store |
|
| 30 | + * |
|
| 31 | + * @codeCoverageIgnore |
|
| 32 | + */ |
|
| 33 | + public function init() { |
|
| 34 | + $table_maker = new ActionScheduler_StoreSchema(); |
|
| 35 | + $table_maker->init(); |
|
| 36 | + $table_maker->register_tables(); |
|
| 37 | + } |
|
| 38 | + |
|
| 39 | + /** |
|
| 40 | + * Save an action. |
|
| 41 | + * |
|
| 42 | + * @param ActionScheduler_Action $action Action object. |
|
| 43 | + * @param DateTime $date Optional schedule date. Default null. |
|
| 44 | + * |
|
| 45 | + * @return int Action ID. |
|
| 46 | + * @throws RuntimeException Throws exception when saving the action fails. |
|
| 47 | + */ |
|
| 48 | + public function save_action( ActionScheduler_Action $action, \DateTime $date = null ) { |
|
| 49 | + try { |
|
| 50 | + |
|
| 51 | + $this->validate_action( $action ); |
|
| 52 | + |
|
| 53 | + /** @var \wpdb $wpdb */ |
|
| 54 | + global $wpdb; |
|
| 55 | + $data = array( |
|
| 56 | + 'hook' => $action->get_hook(), |
|
| 57 | + 'status' => ( $action->is_finished() ? self::STATUS_COMPLETE : self::STATUS_PENDING ), |
|
| 58 | + 'scheduled_date_gmt' => $this->get_scheduled_date_string( $action, $date ), |
|
| 59 | + 'scheduled_date_local' => $this->get_scheduled_date_string_local( $action, $date ), |
|
| 60 | + 'schedule' => serialize( $action->get_schedule() ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize |
|
| 61 | + 'group_id' => $this->get_group_id( $action->get_group() ), |
|
| 62 | + ); |
|
| 63 | + $args = wp_json_encode( $action->get_args() ); |
|
| 64 | + if ( strlen( $args ) <= static::$max_index_length ) { |
|
| 65 | + $data['args'] = $args; |
|
| 66 | + } else { |
|
| 67 | + $data['args'] = $this->hash_args( $args ); |
|
| 68 | + $data['extended_args'] = $args; |
|
| 69 | + } |
|
| 70 | + |
|
| 71 | + $table_name = ! empty( $wpdb->actionscheduler_actions ) ? $wpdb->actionscheduler_actions : $wpdb->prefix . 'actionscheduler_actions'; |
|
| 72 | + $wpdb->insert( $table_name, $data ); |
|
| 73 | + $action_id = $wpdb->insert_id; |
|
| 74 | + |
|
| 75 | + if ( is_wp_error( $action_id ) ) { |
|
| 76 | + throw new \RuntimeException( $action_id->get_error_message() ); |
|
| 77 | + } elseif ( empty( $action_id ) ) { |
|
| 78 | + throw new \RuntimeException( $wpdb->last_error ? $wpdb->last_error : __( 'Database error.', 'woocommerce' ) ); |
|
| 79 | + } |
|
| 80 | + |
|
| 81 | + do_action( 'action_scheduler_stored_action', $action_id ); |
|
| 82 | + |
|
| 83 | + return $action_id; |
|
| 84 | + } catch ( \Exception $e ) { |
|
| 85 | + /* translators: %s: error message */ |
|
| 86 | + throw new \RuntimeException( sprintf( __( 'Error saving action: %s', 'woocommerce' ), $e->getMessage() ), 0 ); |
|
| 87 | + } |
|
| 88 | + } |
|
| 89 | + |
|
| 90 | + /** |
|
| 91 | + * Generate a hash from json_encoded $args using MD5 as this isn't for security. |
|
| 92 | + * |
|
| 93 | + * @param string $args JSON encoded action args. |
|
| 94 | + * @return string |
|
| 95 | + */ |
|
| 96 | + protected function hash_args( $args ) { |
|
| 97 | + return md5( $args ); |
|
| 98 | + } |
|
| 99 | + |
|
| 100 | + /** |
|
| 101 | + * Get action args query param value from action args. |
|
| 102 | + * |
|
| 103 | + * @param array $args Action args. |
|
| 104 | + * @return string |
|
| 105 | + */ |
|
| 106 | + protected function get_args_for_query( $args ) { |
|
| 107 | + $encoded = wp_json_encode( $args ); |
|
| 108 | + if ( strlen( $encoded ) <= static::$max_index_length ) { |
|
| 109 | + return $encoded; |
|
| 110 | + } |
|
| 111 | + return $this->hash_args( $encoded ); |
|
| 112 | + } |
|
| 113 | + /** |
|
| 114 | + * Get a group's ID based on its name/slug. |
|
| 115 | + * |
|
| 116 | + * @param string $slug The string name of a group. |
|
| 117 | + * @param bool $create_if_not_exists Whether to create the group if it does not already exist. Default, true - create the group. |
|
| 118 | + * |
|
| 119 | + * @return int The group's ID, if it exists or is created, or 0 if it does not exist and is not created. |
|
| 120 | + */ |
|
| 121 | + protected function get_group_id( $slug, $create_if_not_exists = true ) { |
|
| 122 | + if ( empty( $slug ) ) { |
|
| 123 | + return 0; |
|
| 124 | + } |
|
| 125 | + /** @var \wpdb $wpdb */ |
|
| 126 | + global $wpdb; |
|
| 127 | + $group_id = (int) $wpdb->get_var( $wpdb->prepare( "SELECT group_id FROM {$wpdb->actionscheduler_groups} WHERE slug=%s", $slug ) ); |
|
| 128 | + if ( empty( $group_id ) && $create_if_not_exists ) { |
|
| 129 | + $group_id = $this->create_group( $slug ); |
|
| 130 | + } |
|
| 131 | + |
|
| 132 | + return $group_id; |
|
| 133 | + } |
|
| 134 | + |
|
| 135 | + /** |
|
| 136 | + * Create an action group. |
|
| 137 | + * |
|
| 138 | + * @param string $slug Group slug. |
|
| 139 | + * |
|
| 140 | + * @return int Group ID. |
|
| 141 | + */ |
|
| 142 | + protected function create_group( $slug ) { |
|
| 143 | + /** @var \wpdb $wpdb */ |
|
| 144 | + global $wpdb; |
|
| 145 | + $wpdb->insert( $wpdb->actionscheduler_groups, array( 'slug' => $slug ) ); |
|
| 146 | + |
|
| 147 | + return (int) $wpdb->insert_id; |
|
| 148 | + } |
|
| 149 | + |
|
| 150 | + /** |
|
| 151 | + * Retrieve an action. |
|
| 152 | + * |
|
| 153 | + * @param int $action_id Action ID. |
|
| 154 | + * |
|
| 155 | + * @return ActionScheduler_Action |
|
| 156 | + */ |
|
| 157 | + public function fetch_action( $action_id ) { |
|
| 158 | + /** @var \wpdb $wpdb */ |
|
| 159 | + global $wpdb; |
|
| 160 | + $data = $wpdb->get_row( |
|
| 161 | + $wpdb->prepare( |
|
| 162 | + "SELECT a.*, g.slug AS `group` FROM {$wpdb->actionscheduler_actions} a LEFT JOIN {$wpdb->actionscheduler_groups} g ON a.group_id=g.group_id WHERE a.action_id=%d", |
|
| 163 | + $action_id |
|
| 164 | + ) |
|
| 165 | + ); |
|
| 166 | + |
|
| 167 | + if ( empty( $data ) ) { |
|
| 168 | + return $this->get_null_action(); |
|
| 169 | + } |
|
| 170 | + |
|
| 171 | + if ( ! empty( $data->extended_args ) ) { |
|
| 172 | + $data->args = $data->extended_args; |
|
| 173 | + unset( $data->extended_args ); |
|
| 174 | + } |
|
| 175 | + |
|
| 176 | + // Convert NULL dates to zero dates. |
|
| 177 | + $date_fields = array( |
|
| 178 | + 'scheduled_date_gmt', |
|
| 179 | + 'scheduled_date_local', |
|
| 180 | + 'last_attempt_gmt', |
|
| 181 | + 'last_attempt_gmt', |
|
| 182 | + ); |
|
| 183 | + foreach ( $date_fields as $date_field ) { |
|
| 184 | + if ( is_null( $data->$date_field ) ) { |
|
| 185 | + $data->$date_field = ActionScheduler_StoreSchema::DEFAULT_DATE; |
|
| 186 | + } |
|
| 187 | + } |
|
| 188 | + |
|
| 189 | + try { |
|
| 190 | + $action = $this->make_action_from_db_record( $data ); |
|
| 191 | + } catch ( ActionScheduler_InvalidActionException $exception ) { |
|
| 192 | + do_action( 'action_scheduler_failed_fetch_action', $action_id, $exception ); |
|
| 193 | + return $this->get_null_action(); |
|
| 194 | + } |
|
| 195 | + |
|
| 196 | + return $action; |
|
| 197 | + } |
|
| 198 | + |
|
| 199 | + /** |
|
| 200 | + * Create a null action. |
|
| 201 | + * |
|
| 202 | + * @return ActionScheduler_NullAction |
|
| 203 | + */ |
|
| 204 | + protected function get_null_action() { |
|
| 205 | + return new ActionScheduler_NullAction(); |
|
| 206 | + } |
|
| 207 | + |
|
| 208 | + /** |
|
| 209 | + * Create an action from a database record. |
|
| 210 | + * |
|
| 211 | + * @param object $data Action database record. |
|
| 212 | + * |
|
| 213 | + * @return ActionScheduler_Action|ActionScheduler_CanceledAction|ActionScheduler_FinishedAction |
|
| 214 | + */ |
|
| 215 | + protected function make_action_from_db_record( $data ) { |
|
| 216 | + |
|
| 217 | + $hook = $data->hook; |
|
| 218 | + $args = json_decode( $data->args, true ); |
|
| 219 | + $schedule = unserialize( $data->schedule ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize |
|
| 220 | + |
|
| 221 | + $this->validate_args( $args, $data->action_id ); |
|
| 222 | + $this->validate_schedule( $schedule, $data->action_id ); |
|
| 223 | + |
|
| 224 | + if ( empty( $schedule ) ) { |
|
| 225 | + $schedule = new ActionScheduler_NullSchedule(); |
|
| 226 | + } |
|
| 227 | + $group = $data->group ? $data->group : ''; |
|
| 228 | + |
|
| 229 | + return ActionScheduler::factory()->get_stored_action( $data->status, $data->hook, $args, $schedule, $group ); |
|
| 230 | + } |
|
| 231 | + |
|
| 232 | + /** |
|
| 233 | + * Returns the SQL statement to query (or count) actions. |
|
| 234 | + * |
|
| 235 | + * @since x.x.x $query['status'] accepts array of statuses instead of a single status. |
|
| 236 | + * |
|
| 237 | + * @param array $query Filtering options. |
|
| 238 | + * @param string $select_or_count Whether the SQL should select and return the IDs or just the row count. |
|
| 239 | + * |
|
| 240 | + * @return string SQL statement already properly escaped. |
|
| 241 | + * @throws InvalidArgumentException If the query is invalid. |
|
| 242 | + */ |
|
| 243 | + protected function get_query_actions_sql( array $query, $select_or_count = 'select' ) { |
|
| 244 | + |
|
| 245 | + if ( ! in_array( $select_or_count, array( 'select', 'count' ), true ) ) { |
|
| 246 | + throw new InvalidArgumentException( __( 'Invalid value for select or count parameter. Cannot query actions.', 'woocommerce' ) ); |
|
| 247 | + } |
|
| 248 | + |
|
| 249 | + $query = wp_parse_args( |
|
| 250 | + $query, |
|
| 251 | + array( |
|
| 252 | + 'hook' => '', |
|
| 253 | + 'args' => null, |
|
| 254 | + 'date' => null, |
|
| 255 | + 'date_compare' => '<=', |
|
| 256 | + 'modified' => null, |
|
| 257 | + 'modified_compare' => '<=', |
|
| 258 | + 'group' => '', |
|
| 259 | + 'status' => '', |
|
| 260 | + 'claimed' => null, |
|
| 261 | + 'per_page' => 5, |
|
| 262 | + 'offset' => 0, |
|
| 263 | + 'orderby' => 'date', |
|
| 264 | + 'order' => 'ASC', |
|
| 265 | + ) |
|
| 266 | + ); |
|
| 267 | + |
|
| 268 | + /** @var \wpdb $wpdb */ |
|
| 269 | + global $wpdb; |
|
| 270 | + $sql = ( 'count' === $select_or_count ) ? 'SELECT count(a.action_id)' : 'SELECT a.action_id'; |
|
| 271 | + $sql .= " FROM {$wpdb->actionscheduler_actions} a"; |
|
| 272 | + $sql_params = array(); |
|
| 273 | + |
|
| 274 | + if ( ! empty( $query['group'] ) || 'group' === $query['orderby'] ) { |
|
| 275 | + $sql .= " LEFT JOIN {$wpdb->actionscheduler_groups} g ON g.group_id=a.group_id"; |
|
| 276 | + } |
|
| 277 | + |
|
| 278 | + $sql .= ' WHERE 1=1'; |
|
| 279 | + |
|
| 280 | + if ( ! empty( $query['group'] ) ) { |
|
| 281 | + $sql .= ' AND g.slug=%s'; |
|
| 282 | + $sql_params[] = $query['group']; |
|
| 283 | + } |
|
| 284 | + |
|
| 285 | + if ( $query['hook'] ) { |
|
| 286 | + $sql .= ' AND a.hook=%s'; |
|
| 287 | + $sql_params[] = $query['hook']; |
|
| 288 | + } |
|
| 289 | + if ( ! is_null( $query['args'] ) ) { |
|
| 290 | + $sql .= ' AND a.args=%s'; |
|
| 291 | + $sql_params[] = $this->get_args_for_query( $query['args'] ); |
|
| 292 | + } |
|
| 293 | + |
|
| 294 | + if ( $query['status'] ) { |
|
| 295 | + $statuses = (array) $query['status']; |
|
| 296 | + $placeholders = array_fill( 0, count( $statuses ), '%s' ); |
|
| 297 | + $sql .= ' AND a.status IN (' . join( ', ', $placeholders ) . ')'; |
|
| 298 | + $sql_params = array_merge( $sql_params, array_values( $statuses ) ); |
|
| 299 | + } |
|
| 300 | + |
|
| 301 | + if ( $query['date'] instanceof \DateTime ) { |
|
| 302 | + $date = clone $query['date']; |
|
| 303 | + $date->setTimezone( new \DateTimeZone( 'UTC' ) ); |
|
| 304 | + $date_string = $date->format( 'Y-m-d H:i:s' ); |
|
| 305 | + $comparator = $this->validate_sql_comparator( $query['date_compare'] ); |
|
| 306 | + $sql .= " AND a.scheduled_date_gmt $comparator %s"; |
|
| 307 | + $sql_params[] = $date_string; |
|
| 308 | + } |
|
| 309 | + |
|
| 310 | + if ( $query['modified'] instanceof \DateTime ) { |
|
| 311 | + $modified = clone $query['modified']; |
|
| 312 | + $modified->setTimezone( new \DateTimeZone( 'UTC' ) ); |
|
| 313 | + $date_string = $modified->format( 'Y-m-d H:i:s' ); |
|
| 314 | + $comparator = $this->validate_sql_comparator( $query['modified_compare'] ); |
|
| 315 | + $sql .= " AND a.last_attempt_gmt $comparator %s"; |
|
| 316 | + $sql_params[] = $date_string; |
|
| 317 | + } |
|
| 318 | + |
|
| 319 | + if ( true === $query['claimed'] ) { |
|
| 320 | + $sql .= ' AND a.claim_id != 0'; |
|
| 321 | + } elseif ( false === $query['claimed'] ) { |
|
| 322 | + $sql .= ' AND a.claim_id = 0'; |
|
| 323 | + } elseif ( ! is_null( $query['claimed'] ) ) { |
|
| 324 | + $sql .= ' AND a.claim_id = %d'; |
|
| 325 | + $sql_params[] = $query['claimed']; |
|
| 326 | + } |
|
| 327 | + |
|
| 328 | + if ( ! empty( $query['search'] ) ) { |
|
| 329 | + $sql .= ' AND (a.hook LIKE %s OR (a.extended_args IS NULL AND a.args LIKE %s) OR a.extended_args LIKE %s'; |
|
| 330 | + for ( $i = 0; $i < 3; $i++ ) { |
|
| 331 | + $sql_params[] = sprintf( '%%%s%%', $query['search'] ); |
|
| 332 | + } |
|
| 333 | + |
|
| 334 | + $search_claim_id = (int) $query['search']; |
|
| 335 | + if ( $search_claim_id ) { |
|
| 336 | + $sql .= ' OR a.claim_id = %d'; |
|
| 337 | + $sql_params[] = $search_claim_id; |
|
| 338 | + } |
|
| 339 | + |
|
| 340 | + $sql .= ')'; |
|
| 341 | + } |
|
| 342 | + |
|
| 343 | + if ( 'select' === $select_or_count ) { |
|
| 344 | + if ( 'ASC' === strtoupper( $query['order'] ) ) { |
|
| 345 | + $order = 'ASC'; |
|
| 346 | + } else { |
|
| 347 | + $order = 'DESC'; |
|
| 348 | + } |
|
| 349 | + switch ( $query['orderby'] ) { |
|
| 350 | + case 'hook': |
|
| 351 | + $sql .= " ORDER BY a.hook $order"; |
|
| 352 | + break; |
|
| 353 | + case 'group': |
|
| 354 | + $sql .= " ORDER BY g.slug $order"; |
|
| 355 | + break; |
|
| 356 | + case 'modified': |
|
| 357 | + $sql .= " ORDER BY a.last_attempt_gmt $order"; |
|
| 358 | + break; |
|
| 359 | + case 'none': |
|
| 360 | + break; |
|
| 361 | + case 'action_id': |
|
| 362 | + $sql .= " ORDER BY a.action_id $order"; |
|
| 363 | + break; |
|
| 364 | + case 'date': |
|
| 365 | + default: |
|
| 366 | + $sql .= " ORDER BY a.scheduled_date_gmt $order"; |
|
| 367 | + break; |
|
| 368 | + } |
|
| 369 | + |
|
| 370 | + if ( $query['per_page'] > 0 ) { |
|
| 371 | + $sql .= ' LIMIT %d, %d'; |
|
| 372 | + $sql_params[] = $query['offset']; |
|
| 373 | + $sql_params[] = $query['per_page']; |
|
| 374 | + } |
|
| 375 | + } |
|
| 376 | + |
|
| 377 | + if ( ! empty( $sql_params ) ) { |
|
| 378 | + $sql = $wpdb->prepare( $sql, $sql_params ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
|
| 379 | + } |
|
| 380 | + |
|
| 381 | + return $sql; |
|
| 382 | + } |
|
| 383 | + |
|
| 384 | + /** |
|
| 385 | + * Query for action count or list of action IDs. |
|
| 386 | + * |
|
| 387 | + * @since x.x.x $query['status'] accepts array of statuses instead of a single status. |
|
| 388 | + * |
|
| 389 | + * @see ActionScheduler_Store::query_actions for $query arg usage. |
|
| 390 | + * |
|
| 391 | + * @param array $query Query filtering options. |
|
| 392 | + * @param string $query_type Whether to select or count the results. Defaults to select. |
|
| 393 | + * |
|
| 394 | + * @return string|array|null The IDs of actions matching the query. Null on failure. |
|
| 395 | + */ |
|
| 396 | + public function query_actions( $query = array(), $query_type = 'select' ) { |
|
| 397 | + /** @var wpdb $wpdb */ |
|
| 398 | + global $wpdb; |
|
| 399 | + |
|
| 400 | + $sql = $this->get_query_actions_sql( $query, $query_type ); |
|
| 401 | + |
|
| 402 | + return ( 'count' === $query_type ) ? $wpdb->get_var( $sql ) : $wpdb->get_col( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoSql, WordPress.DB.DirectDatabaseQuery.NoCaching |
|
| 403 | + } |
|
| 404 | + |
|
| 405 | + /** |
|
| 406 | + * Get a count of all actions in the store, grouped by status. |
|
| 407 | + * |
|
| 408 | + * @return array Set of 'status' => int $count pairs for statuses with 1 or more actions of that status. |
|
| 409 | + */ |
|
| 410 | + public function action_counts() { |
|
| 411 | + global $wpdb; |
|
| 412 | + |
|
| 413 | + $sql = "SELECT a.status, count(a.status) as 'count'"; |
|
| 414 | + $sql .= " FROM {$wpdb->actionscheduler_actions} a"; |
|
| 415 | + $sql .= ' GROUP BY a.status'; |
|
| 416 | + |
|
| 417 | + $actions_count_by_status = array(); |
|
| 418 | + $action_stati_and_labels = $this->get_status_labels(); |
|
| 419 | + |
|
| 420 | + foreach ( $wpdb->get_results( $sql ) as $action_data ) { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
|
| 421 | + // Ignore any actions with invalid status. |
|
| 422 | + if ( array_key_exists( $action_data->status, $action_stati_and_labels ) ) { |
|
| 423 | + $actions_count_by_status[ $action_data->status ] = $action_data->count; |
|
| 424 | + } |
|
| 425 | + } |
|
| 426 | + |
|
| 427 | + return $actions_count_by_status; |
|
| 428 | + } |
|
| 429 | + |
|
| 430 | + /** |
|
| 431 | + * Cancel an action. |
|
| 432 | + * |
|
| 433 | + * @param int $action_id Action ID. |
|
| 434 | + * |
|
| 435 | + * @return void |
|
| 436 | + * @throws \InvalidArgumentException If the action update failed. |
|
| 437 | + */ |
|
| 438 | + public function cancel_action( $action_id ) { |
|
| 439 | + /** @var \wpdb $wpdb */ |
|
| 440 | + global $wpdb; |
|
| 441 | + |
|
| 442 | + $updated = $wpdb->update( |
|
| 443 | + $wpdb->actionscheduler_actions, |
|
| 444 | + array( 'status' => self::STATUS_CANCELED ), |
|
| 445 | + array( 'action_id' => $action_id ), |
|
| 446 | + array( '%s' ), |
|
| 447 | + array( '%d' ) |
|
| 448 | + ); |
|
| 449 | + if ( false === $updated ) { |
|
| 450 | + /* translators: %s: action ID */ |
|
| 451 | + throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'woocommerce' ), $action_id ) ); |
|
| 452 | + } |
|
| 453 | + do_action( 'action_scheduler_canceled_action', $action_id ); |
|
| 454 | + } |
|
| 455 | + |
|
| 456 | + /** |
|
| 457 | + * Cancel pending actions by hook. |
|
| 458 | + * |
|
| 459 | + * @since 3.0.0 |
|
| 460 | + * |
|
| 461 | + * @param string $hook Hook name. |
|
| 462 | + * |
|
| 463 | + * @return void |
|
| 464 | + */ |
|
| 465 | + public function cancel_actions_by_hook( $hook ) { |
|
| 466 | + $this->bulk_cancel_actions( array( 'hook' => $hook ) ); |
|
| 467 | + } |
|
| 468 | + |
|
| 469 | + /** |
|
| 470 | + * Cancel pending actions by group. |
|
| 471 | + * |
|
| 472 | + * @param string $group Group slug. |
|
| 473 | + * |
|
| 474 | + * @return void |
|
| 475 | + */ |
|
| 476 | + public function cancel_actions_by_group( $group ) { |
|
| 477 | + $this->bulk_cancel_actions( array( 'group' => $group ) ); |
|
| 478 | + } |
|
| 479 | + |
|
| 480 | + /** |
|
| 481 | + * Bulk cancel actions. |
|
| 482 | + * |
|
| 483 | + * @since 3.0.0 |
|
| 484 | + * |
|
| 485 | + * @param array $query_args Query parameters. |
|
| 486 | + */ |
|
| 487 | + protected function bulk_cancel_actions( $query_args ) { |
|
| 488 | + /** @var \wpdb $wpdb */ |
|
| 489 | + global $wpdb; |
|
| 490 | + |
|
| 491 | + if ( ! is_array( $query_args ) ) { |
|
| 492 | + return; |
|
| 493 | + } |
|
| 494 | + |
|
| 495 | + // Don't cancel actions that are already canceled. |
|
| 496 | + if ( isset( $query_args['status'] ) && self::STATUS_CANCELED === $query_args['status'] ) { |
|
| 497 | + return; |
|
| 498 | + } |
|
| 499 | + |
|
| 500 | + $action_ids = true; |
|
| 501 | + $query_args = wp_parse_args( |
|
| 502 | + $query_args, |
|
| 503 | + array( |
|
| 504 | + 'per_page' => 1000, |
|
| 505 | + 'status' => self::STATUS_PENDING, |
|
| 506 | + 'orderby' => 'action_id', |
|
| 507 | + ) |
|
| 508 | + ); |
|
| 509 | + |
|
| 510 | + while ( $action_ids ) { |
|
| 511 | + $action_ids = $this->query_actions( $query_args ); |
|
| 512 | + if ( empty( $action_ids ) ) { |
|
| 513 | + break; |
|
| 514 | + } |
|
| 515 | + |
|
| 516 | + $format = array_fill( 0, count( $action_ids ), '%d' ); |
|
| 517 | + $query_in = '(' . implode( ',', $format ) . ')'; |
|
| 518 | + $parameters = $action_ids; |
|
| 519 | + array_unshift( $parameters, self::STATUS_CANCELED ); |
|
| 520 | + |
|
| 521 | + $wpdb->query( |
|
| 522 | + $wpdb->prepare( |
|
| 523 | + "UPDATE {$wpdb->actionscheduler_actions} SET status = %s WHERE action_id IN {$query_in}", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared |
|
| 524 | + $parameters |
|
| 525 | + ) |
|
| 526 | + ); |
|
| 527 | + |
|
| 528 | + do_action( 'action_scheduler_bulk_cancel_actions', $action_ids ); |
|
| 529 | + } |
|
| 530 | + } |
|
| 531 | + |
|
| 532 | + /** |
|
| 533 | + * Delete an action. |
|
| 534 | + * |
|
| 535 | + * @param int $action_id Action ID. |
|
| 536 | + * @throws \InvalidArgumentException If the action deletion failed. |
|
| 537 | + */ |
|
| 538 | + public function delete_action( $action_id ) { |
|
| 539 | + /** @var \wpdb $wpdb */ |
|
| 540 | + global $wpdb; |
|
| 541 | + $deleted = $wpdb->delete( $wpdb->actionscheduler_actions, array( 'action_id' => $action_id ), array( '%d' ) ); |
|
| 542 | + if ( empty( $deleted ) ) { |
|
| 543 | + throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'woocommerce' ), $action_id ) ); //phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment |
|
| 544 | + } |
|
| 545 | + do_action( 'action_scheduler_deleted_action', $action_id ); |
|
| 546 | + } |
|
| 547 | + |
|
| 548 | + /** |
|
| 549 | + * Get the schedule date for an action. |
|
| 550 | + * |
|
| 551 | + * @param string $action_id Action ID. |
|
| 552 | + * |
|
| 553 | + * @return \DateTime The local date the action is scheduled to run, or the date that it ran. |
|
| 554 | + */ |
|
| 555 | + public function get_date( $action_id ) { |
|
| 556 | + $date = $this->get_date_gmt( $action_id ); |
|
| 557 | + ActionScheduler_TimezoneHelper::set_local_timezone( $date ); |
|
| 558 | + return $date; |
|
| 559 | + } |
|
| 560 | + |
|
| 561 | + /** |
|
| 562 | + * Get the GMT schedule date for an action. |
|
| 563 | + * |
|
| 564 | + * @param int $action_id Action ID. |
|
| 565 | + * |
|
| 566 | + * @throws \InvalidArgumentException If action cannot be identified. |
|
| 567 | + * @return \DateTime The GMT date the action is scheduled to run, or the date that it ran. |
|
| 568 | + */ |
|
| 569 | + protected function get_date_gmt( $action_id ) { |
|
| 570 | + /** @var \wpdb $wpdb */ |
|
| 571 | + global $wpdb; |
|
| 572 | + $record = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->actionscheduler_actions} WHERE action_id=%d", $action_id ) ); |
|
| 573 | + if ( empty( $record ) ) { |
|
| 574 | + throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'woocommerce' ), $action_id ) ); //phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment |
|
| 575 | + } |
|
| 576 | + if ( self::STATUS_PENDING === $record->status ) { |
|
| 577 | + return as_get_datetime_object( $record->scheduled_date_gmt ); |
|
| 578 | + } else { |
|
| 579 | + return as_get_datetime_object( $record->last_attempt_gmt ); |
|
| 580 | + } |
|
| 581 | + } |
|
| 582 | + |
|
| 583 | + /** |
|
| 584 | + * Stake a claim on actions. |
|
| 585 | + * |
|
| 586 | + * @param int $max_actions Maximum number of action to include in claim. |
|
| 587 | + * @param \DateTime $before_date Jobs must be schedule before this date. Defaults to now. |
|
| 588 | + * @param array $hooks Hooks to filter for. |
|
| 589 | + * @param string $group Group to filter for. |
|
| 590 | + * |
|
| 591 | + * @return ActionScheduler_ActionClaim |
|
| 592 | + */ |
|
| 593 | + public function stake_claim( $max_actions = 10, \DateTime $before_date = null, $hooks = array(), $group = '' ) { |
|
| 594 | + $claim_id = $this->generate_claim_id(); |
|
| 595 | + |
|
| 596 | + $this->claim_before_date = $before_date; |
|
| 597 | + $this->claim_actions( $claim_id, $max_actions, $before_date, $hooks, $group ); |
|
| 598 | + $action_ids = $this->find_actions_by_claim_id( $claim_id ); |
|
| 599 | + $this->claim_before_date = null; |
|
| 600 | + |
|
| 601 | + return new ActionScheduler_ActionClaim( $claim_id, $action_ids ); |
|
| 602 | + } |
|
| 603 | + |
|
| 604 | + /** |
|
| 605 | + * Generate a new action claim. |
|
| 606 | + * |
|
| 607 | + * @return int Claim ID. |
|
| 608 | + */ |
|
| 609 | + protected function generate_claim_id() { |
|
| 610 | + /** @var \wpdb $wpdb */ |
|
| 611 | + global $wpdb; |
|
| 612 | + $now = as_get_datetime_object(); |
|
| 613 | + $wpdb->insert( $wpdb->actionscheduler_claims, array( 'date_created_gmt' => $now->format( 'Y-m-d H:i:s' ) ) ); |
|
| 614 | + |
|
| 615 | + return $wpdb->insert_id; |
|
| 616 | + } |
|
| 617 | + |
|
| 618 | + /** |
|
| 619 | + * Mark actions claimed. |
|
| 620 | + * |
|
| 621 | + * @param string $claim_id Claim Id. |
|
| 622 | + * @param int $limit Number of action to include in claim. |
|
| 623 | + * @param \DateTime $before_date Should use UTC timezone. |
|
| 624 | + * @param array $hooks Hooks to filter for. |
|
| 625 | + * @param string $group Group to filter for. |
|
| 626 | + * |
|
| 627 | + * @return int The number of actions that were claimed. |
|
| 628 | + * @throws \InvalidArgumentException Throws InvalidArgumentException if group doesn't exist. |
|
| 629 | + * @throws \RuntimeException Throws RuntimeException if unable to claim action. |
|
| 630 | + */ |
|
| 631 | + protected function claim_actions( $claim_id, $limit, \DateTime $before_date = null, $hooks = array(), $group = '' ) { |
|
| 632 | + /** @var \wpdb $wpdb */ |
|
| 633 | + global $wpdb; |
|
| 634 | + |
|
| 635 | + $now = as_get_datetime_object(); |
|
| 636 | + $date = is_null( $before_date ) ? $now : clone $before_date; |
|
| 637 | + |
|
| 638 | + // can't use $wpdb->update() because of the <= condition. |
|
| 639 | + $update = "UPDATE {$wpdb->actionscheduler_actions} SET claim_id=%d, last_attempt_gmt=%s, last_attempt_local=%s"; |
|
| 640 | + $params = array( |
|
| 641 | + $claim_id, |
|
| 642 | + $now->format( 'Y-m-d H:i:s' ), |
|
| 643 | + current_time( 'mysql' ), |
|
| 644 | + ); |
|
| 645 | + |
|
| 646 | + $where = 'WHERE claim_id = 0 AND scheduled_date_gmt <= %s AND status=%s'; |
|
| 647 | + $params[] = $date->format( 'Y-m-d H:i:s' ); |
|
| 648 | + $params[] = self::STATUS_PENDING; |
|
| 649 | + |
|
| 650 | + if ( ! empty( $hooks ) ) { |
|
| 651 | + $placeholders = array_fill( 0, count( $hooks ), '%s' ); |
|
| 652 | + $where .= ' AND hook IN (' . join( ', ', $placeholders ) . ')'; |
|
| 653 | + $params = array_merge( $params, array_values( $hooks ) ); |
|
| 654 | + } |
|
| 655 | + |
|
| 656 | + if ( ! empty( $group ) ) { |
|
| 657 | + |
|
| 658 | + $group_id = $this->get_group_id( $group, false ); |
|
| 659 | + |
|
| 660 | + // throw exception if no matching group found, this matches ActionScheduler_wpPostStore's behaviour. |
|
| 661 | + if ( empty( $group_id ) ) { |
|
| 662 | + /* translators: %s: group name */ |
|
| 663 | + throw new InvalidArgumentException( sprintf( __( 'The group "%s" does not exist.', 'woocommerce' ), $group ) ); |
|
| 664 | + } |
|
| 665 | + |
|
| 666 | + $where .= ' AND group_id = %d'; |
|
| 667 | + $params[] = $group_id; |
|
| 668 | + } |
|
| 669 | + |
|
| 670 | + /** |
|
| 671 | + * Sets the order-by clause used in the action claim query. |
|
| 672 | + * |
|
| 673 | + * @since x.x.x |
|
| 674 | + * |
|
| 675 | + * @param string $order_by_sql |
|
| 676 | + */ |
|
| 677 | + $order = apply_filters( 'action_scheduler_claim_actions_order_by', 'ORDER BY attempts ASC, scheduled_date_gmt ASC, action_id ASC' ); |
|
| 678 | + $params[] = $limit; |
|
| 679 | + |
|
| 680 | + $sql = $wpdb->prepare( "{$update} {$where} {$order} LIMIT %d", $params ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders |
|
| 681 | + $rows_affected = $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching |
|
| 682 | + if ( false === $rows_affected ) { |
|
| 683 | + throw new \RuntimeException( __( 'Unable to claim actions. Database error.', 'woocommerce' ) ); |
|
| 684 | + } |
|
| 685 | + |
|
| 686 | + return (int) $rows_affected; |
|
| 687 | + } |
|
| 688 | + |
|
| 689 | + /** |
|
| 690 | + * Get the number of active claims. |
|
| 691 | + * |
|
| 692 | + * @return int |
|
| 693 | + */ |
|
| 694 | + public function get_claim_count() { |
|
| 695 | + global $wpdb; |
|
| 696 | + |
|
| 697 | + $sql = "SELECT COUNT(DISTINCT claim_id) FROM {$wpdb->actionscheduler_actions} WHERE claim_id != 0 AND status IN ( %s, %s)"; |
|
| 698 | + $sql = $wpdb->prepare( $sql, array( self::STATUS_PENDING, self::STATUS_RUNNING ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
|
| 699 | + |
|
| 700 | + return (int) $wpdb->get_var( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
|
| 701 | + } |
|
| 702 | + |
|
| 703 | + /** |
|
| 704 | + * Return an action's claim ID, as stored in the claim_id column. |
|
| 705 | + * |
|
| 706 | + * @param string $action_id Action ID. |
|
| 707 | + * @return mixed |
|
| 708 | + */ |
|
| 709 | + public function get_claim_id( $action_id ) { |
|
| 710 | + /** @var \wpdb $wpdb */ |
|
| 711 | + global $wpdb; |
|
| 712 | + |
|
| 713 | + $sql = "SELECT claim_id FROM {$wpdb->actionscheduler_actions} WHERE action_id=%d"; |
|
| 714 | + $sql = $wpdb->prepare( $sql, $action_id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
|
| 715 | + |
|
| 716 | + return (int) $wpdb->get_var( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
|
| 717 | + } |
|
| 718 | + |
|
| 719 | + /** |
|
| 720 | + * Retrieve the action IDs of action in a claim. |
|
| 721 | + * |
|
| 722 | + * @param int $claim_id Claim ID. |
|
| 723 | + * @return int[] |
|
| 724 | + */ |
|
| 725 | + public function find_actions_by_claim_id( $claim_id ) { |
|
| 726 | + /** @var \wpdb $wpdb */ |
|
| 727 | + global $wpdb; |
|
| 728 | + |
|
| 729 | + $action_ids = array(); |
|
| 730 | + $before_date = isset( $this->claim_before_date ) ? $this->claim_before_date : as_get_datetime_object(); |
|
| 731 | + $cut_off = $before_date->format( 'Y-m-d H:i:s' ); |
|
| 732 | + |
|
| 733 | + $sql = $wpdb->prepare( |
|
| 734 | + "SELECT action_id, scheduled_date_gmt FROM {$wpdb->actionscheduler_actions} WHERE claim_id = %d", |
|
| 735 | + $claim_id |
|
| 736 | + ); |
|
| 737 | + |
|
| 738 | + // Verify that the scheduled date for each action is within the expected bounds (in some unusual |
|
| 739 | + // cases, we cannot depend on MySQL to honor all of the WHERE conditions we specify). |
|
| 740 | + foreach ( $wpdb->get_results( $sql ) as $claimed_action ) { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
|
| 741 | + if ( $claimed_action->scheduled_date_gmt <= $cut_off ) { |
|
| 742 | + $action_ids[] = absint( $claimed_action->action_id ); |
|
| 743 | + } |
|
| 744 | + } |
|
| 745 | + |
|
| 746 | + return $action_ids; |
|
| 747 | + } |
|
| 748 | + |
|
| 749 | + /** |
|
| 750 | + * Release actions from a claim and delete the claim. |
|
| 751 | + * |
|
| 752 | + * @param ActionScheduler_ActionClaim $claim Claim object. |
|
| 753 | + */ |
|
| 754 | + public function release_claim( ActionScheduler_ActionClaim $claim ) { |
|
| 755 | + /** @var \wpdb $wpdb */ |
|
| 756 | + global $wpdb; |
|
| 757 | + $wpdb->update( $wpdb->actionscheduler_actions, array( 'claim_id' => 0 ), array( 'claim_id' => $claim->get_id() ), array( '%d' ), array( '%d' ) ); |
|
| 758 | + $wpdb->delete( $wpdb->actionscheduler_claims, array( 'claim_id' => $claim->get_id() ), array( '%d' ) ); |
|
| 759 | + } |
|
| 760 | + |
|
| 761 | + /** |
|
| 762 | + * Remove the claim from an action. |
|
| 763 | + * |
|
| 764 | + * @param int $action_id Action ID. |
|
| 765 | + * |
|
| 766 | + * @return void |
|
| 767 | + */ |
|
| 768 | + public function unclaim_action( $action_id ) { |
|
| 769 | + /** @var \wpdb $wpdb */ |
|
| 770 | + global $wpdb; |
|
| 771 | + $wpdb->update( |
|
| 772 | + $wpdb->actionscheduler_actions, |
|
| 773 | + array( 'claim_id' => 0 ), |
|
| 774 | + array( 'action_id' => $action_id ), |
|
| 775 | + array( '%s' ), |
|
| 776 | + array( '%d' ) |
|
| 777 | + ); |
|
| 778 | + } |
|
| 779 | + |
|
| 780 | + /** |
|
| 781 | + * Mark an action as failed. |
|
| 782 | + * |
|
| 783 | + * @param int $action_id Action ID. |
|
| 784 | + * @throws \InvalidArgumentException Throw an exception if action was not updated. |
|
| 785 | + */ |
|
| 786 | + public function mark_failure( $action_id ) { |
|
| 787 | + /** @var \wpdb $wpdb */ |
|
| 788 | + global $wpdb; |
|
| 789 | + $updated = $wpdb->update( |
|
| 790 | + $wpdb->actionscheduler_actions, |
|
| 791 | + array( 'status' => self::STATUS_FAILED ), |
|
| 792 | + array( 'action_id' => $action_id ), |
|
| 793 | + array( '%s' ), |
|
| 794 | + array( '%d' ) |
|
| 795 | + ); |
|
| 796 | + if ( empty( $updated ) ) { |
|
| 797 | + throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'woocommerce' ), $action_id ) ); //phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment |
|
| 798 | + } |
|
| 799 | + } |
|
| 800 | + |
|
| 801 | + /** |
|
| 802 | + * Add execution message to action log. |
|
| 803 | + * |
|
| 804 | + * @param int $action_id Action ID. |
|
| 805 | + * |
|
| 806 | + * @return void |
|
| 807 | + */ |
|
| 808 | + public function log_execution( $action_id ) { |
|
| 809 | + /** @var \wpdb $wpdb */ |
|
| 810 | + global $wpdb; |
|
| 811 | + |
|
| 812 | + $sql = "UPDATE {$wpdb->actionscheduler_actions} SET attempts = attempts+1, status=%s, last_attempt_gmt = %s, last_attempt_local = %s WHERE action_id = %d"; |
|
| 813 | + $sql = $wpdb->prepare( $sql, self::STATUS_RUNNING, current_time( 'mysql', true ), current_time( 'mysql' ), $action_id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
|
| 814 | + $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
|
| 815 | + } |
|
| 816 | + |
|
| 817 | + /** |
|
| 818 | + * Mark an action as complete. |
|
| 819 | + * |
|
| 820 | + * @param int $action_id Action ID. |
|
| 821 | + * |
|
| 822 | + * @return void |
|
| 823 | + * @throws \InvalidArgumentException Throw an exception if action was not updated. |
|
| 824 | + */ |
|
| 825 | + public function mark_complete( $action_id ) { |
|
| 826 | + /** @var \wpdb $wpdb */ |
|
| 827 | + global $wpdb; |
|
| 828 | + $updated = $wpdb->update( |
|
| 829 | + $wpdb->actionscheduler_actions, |
|
| 830 | + array( |
|
| 831 | + 'status' => self::STATUS_COMPLETE, |
|
| 832 | + 'last_attempt_gmt' => current_time( 'mysql', true ), |
|
| 833 | + 'last_attempt_local' => current_time( 'mysql' ), |
|
| 834 | + ), |
|
| 835 | + array( 'action_id' => $action_id ), |
|
| 836 | + array( '%s' ), |
|
| 837 | + array( '%d' ) |
|
| 838 | + ); |
|
| 839 | + if ( empty( $updated ) ) { |
|
| 840 | + throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'woocommerce' ), $action_id ) ); //phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment |
|
| 841 | + } |
|
| 842 | + |
|
| 843 | + /** |
|
| 844 | + * Fires after a scheduled action has been completed. |
|
| 845 | + * |
|
| 846 | + * @since 3.4.2 |
|
| 847 | + * |
|
| 848 | + * @param int $action_id Action ID. |
|
| 849 | + */ |
|
| 850 | + do_action( 'action_scheduler_completed_action', $action_id ); |
|
| 851 | + } |
|
| 852 | + |
|
| 853 | + /** |
|
| 854 | + * Get an action's status. |
|
| 855 | + * |
|
| 856 | + * @param int $action_id Action ID. |
|
| 857 | + * |
|
| 858 | + * @return string |
|
| 859 | + * @throws \InvalidArgumentException Throw an exception if not status was found for action_id. |
|
| 860 | + * @throws \RuntimeException Throw an exception if action status could not be retrieved. |
|
| 861 | + */ |
|
| 862 | + public function get_status( $action_id ) { |
|
| 863 | + /** @var \wpdb $wpdb */ |
|
| 864 | + global $wpdb; |
|
| 865 | + $sql = "SELECT status FROM {$wpdb->actionscheduler_actions} WHERE action_id=%d"; |
|
| 866 | + $sql = $wpdb->prepare( $sql, $action_id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
|
| 867 | + $status = $wpdb->get_var( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
|
| 868 | + |
|
| 869 | + if ( null === $status ) { |
|
| 870 | + throw new \InvalidArgumentException( __( 'Invalid action ID. No status found.', 'woocommerce' ) ); |
|
| 871 | + } elseif ( empty( $status ) ) { |
|
| 872 | + throw new \RuntimeException( __( 'Unknown status found for action.', 'woocommerce' ) ); |
|
| 873 | + } else { |
|
| 874 | + return $status; |
|
| 875 | + } |
|
| 876 | + } |
|
| 877 | 877 | } |
@@ -9,146 +9,146 @@ |
||
| 9 | 9 | */ |
| 10 | 10 | class ActionScheduler_DBLogger extends ActionScheduler_Logger { |
| 11 | 11 | |
| 12 | - /** |
|
| 13 | - * Add a record to an action log. |
|
| 14 | - * |
|
| 15 | - * @param int $action_id Action ID. |
|
| 16 | - * @param string $message Message to be saved in the log entry. |
|
| 17 | - * @param DateTime $date Timestamp of the log entry. |
|
| 18 | - * |
|
| 19 | - * @return int The log entry ID. |
|
| 20 | - */ |
|
| 21 | - public function log( $action_id, $message, DateTime $date = null ) { |
|
| 22 | - if ( empty( $date ) ) { |
|
| 23 | - $date = as_get_datetime_object(); |
|
| 24 | - } else { |
|
| 25 | - $date = clone $date; |
|
| 26 | - } |
|
| 27 | - |
|
| 28 | - $date_gmt = $date->format( 'Y-m-d H:i:s' ); |
|
| 29 | - ActionScheduler_TimezoneHelper::set_local_timezone( $date ); |
|
| 30 | - $date_local = $date->format( 'Y-m-d H:i:s' ); |
|
| 31 | - |
|
| 32 | - /** @var \wpdb $wpdb */ //phpcs:ignore Generic.Commenting.DocComment.MissingShort |
|
| 33 | - global $wpdb; |
|
| 34 | - $wpdb->insert( |
|
| 35 | - $wpdb->actionscheduler_logs, |
|
| 36 | - array( |
|
| 37 | - 'action_id' => $action_id, |
|
| 38 | - 'message' => $message, |
|
| 39 | - 'log_date_gmt' => $date_gmt, |
|
| 40 | - 'log_date_local' => $date_local, |
|
| 41 | - ), |
|
| 42 | - array( '%d', '%s', '%s', '%s' ) |
|
| 43 | - ); |
|
| 44 | - |
|
| 45 | - return $wpdb->insert_id; |
|
| 46 | - } |
|
| 47 | - |
|
| 48 | - /** |
|
| 49 | - * Retrieve an action log entry. |
|
| 50 | - * |
|
| 51 | - * @param int $entry_id Log entry ID. |
|
| 52 | - * |
|
| 53 | - * @return ActionScheduler_LogEntry |
|
| 54 | - */ |
|
| 55 | - public function get_entry( $entry_id ) { |
|
| 56 | - /** @var \wpdb $wpdb */ //phpcs:ignore Generic.Commenting.DocComment.MissingShort |
|
| 57 | - global $wpdb; |
|
| 58 | - $entry = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->actionscheduler_logs} WHERE log_id=%d", $entry_id ) ); |
|
| 59 | - |
|
| 60 | - return $this->create_entry_from_db_record( $entry ); |
|
| 61 | - } |
|
| 62 | - |
|
| 63 | - /** |
|
| 64 | - * Create an action log entry from a database record. |
|
| 65 | - * |
|
| 66 | - * @param object $record Log entry database record object. |
|
| 67 | - * |
|
| 68 | - * @return ActionScheduler_LogEntry |
|
| 69 | - */ |
|
| 70 | - private function create_entry_from_db_record( $record ) { |
|
| 71 | - if ( empty( $record ) ) { |
|
| 72 | - return new ActionScheduler_NullLogEntry(); |
|
| 73 | - } |
|
| 74 | - |
|
| 75 | - if ( is_null( $record->log_date_gmt ) ) { |
|
| 76 | - $date = as_get_datetime_object( ActionScheduler_StoreSchema::DEFAULT_DATE ); |
|
| 77 | - } else { |
|
| 78 | - $date = as_get_datetime_object( $record->log_date_gmt ); |
|
| 79 | - } |
|
| 80 | - |
|
| 81 | - return new ActionScheduler_LogEntry( $record->action_id, $record->message, $date ); |
|
| 82 | - } |
|
| 83 | - |
|
| 84 | - /** |
|
| 85 | - * Retrieve the an action's log entries from the database. |
|
| 86 | - * |
|
| 87 | - * @param int $action_id Action ID. |
|
| 88 | - * |
|
| 89 | - * @return ActionScheduler_LogEntry[] |
|
| 90 | - */ |
|
| 91 | - public function get_logs( $action_id ) { |
|
| 92 | - /** @var \wpdb $wpdb */ //phpcs:ignore Generic.Commenting.DocComment.MissingShort |
|
| 93 | - global $wpdb; |
|
| 94 | - |
|
| 95 | - $records = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->actionscheduler_logs} WHERE action_id=%d", $action_id ) ); |
|
| 96 | - |
|
| 97 | - return array_map( array( $this, 'create_entry_from_db_record' ), $records ); |
|
| 98 | - } |
|
| 99 | - |
|
| 100 | - /** |
|
| 101 | - * Initialize the data store. |
|
| 102 | - * |
|
| 103 | - * @codeCoverageIgnore |
|
| 104 | - */ |
|
| 105 | - public function init() { |
|
| 106 | - $table_maker = new ActionScheduler_LoggerSchema(); |
|
| 107 | - $table_maker->init(); |
|
| 108 | - $table_maker->register_tables(); |
|
| 109 | - |
|
| 110 | - parent::init(); |
|
| 111 | - |
|
| 112 | - add_action( 'action_scheduler_deleted_action', array( $this, 'clear_deleted_action_logs' ), 10, 1 ); |
|
| 113 | - } |
|
| 114 | - |
|
| 115 | - /** |
|
| 116 | - * Delete the action logs for an action. |
|
| 117 | - * |
|
| 118 | - * @param int $action_id Action ID. |
|
| 119 | - */ |
|
| 120 | - public function clear_deleted_action_logs( $action_id ) { |
|
| 121 | - /** @var \wpdb $wpdb */ //phpcs:ignore Generic.Commenting.DocComment.MissingShort |
|
| 122 | - global $wpdb; |
|
| 123 | - $wpdb->delete( $wpdb->actionscheduler_logs, array( 'action_id' => $action_id ), array( '%d' ) ); |
|
| 124 | - } |
|
| 125 | - |
|
| 126 | - /** |
|
| 127 | - * Bulk add cancel action log entries. |
|
| 128 | - * |
|
| 129 | - * @param array $action_ids List of action ID. |
|
| 130 | - */ |
|
| 131 | - public function bulk_log_cancel_actions( $action_ids ) { |
|
| 132 | - if ( empty( $action_ids ) ) { |
|
| 133 | - return; |
|
| 134 | - } |
|
| 135 | - |
|
| 136 | - /** @var \wpdb $wpdb */ //phpcs:ignore Generic.Commenting.DocComment.MissingShort |
|
| 137 | - global $wpdb; |
|
| 138 | - $date = as_get_datetime_object(); |
|
| 139 | - $date_gmt = $date->format( 'Y-m-d H:i:s' ); |
|
| 140 | - ActionScheduler_TimezoneHelper::set_local_timezone( $date ); |
|
| 141 | - $date_local = $date->format( 'Y-m-d H:i:s' ); |
|
| 142 | - $message = __( 'action canceled', 'woocommerce' ); |
|
| 143 | - $format = '(%d, ' . $wpdb->prepare( '%s, %s, %s', $message, $date_gmt, $date_local ) . ')'; |
|
| 144 | - $sql_query = "INSERT {$wpdb->actionscheduler_logs} (action_id, message, log_date_gmt, log_date_local) VALUES "; |
|
| 145 | - $value_rows = array(); |
|
| 146 | - |
|
| 147 | - foreach ( $action_ids as $action_id ) { |
|
| 148 | - $value_rows[] = $wpdb->prepare( $format, $action_id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
|
| 149 | - } |
|
| 150 | - $sql_query .= implode( ',', $value_rows ); |
|
| 151 | - |
|
| 152 | - $wpdb->query( $sql_query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
|
| 153 | - } |
|
| 12 | + /** |
|
| 13 | + * Add a record to an action log. |
|
| 14 | + * |
|
| 15 | + * @param int $action_id Action ID. |
|
| 16 | + * @param string $message Message to be saved in the log entry. |
|
| 17 | + * @param DateTime $date Timestamp of the log entry. |
|
| 18 | + * |
|
| 19 | + * @return int The log entry ID. |
|
| 20 | + */ |
|
| 21 | + public function log( $action_id, $message, DateTime $date = null ) { |
|
| 22 | + if ( empty( $date ) ) { |
|
| 23 | + $date = as_get_datetime_object(); |
|
| 24 | + } else { |
|
| 25 | + $date = clone $date; |
|
| 26 | + } |
|
| 27 | + |
|
| 28 | + $date_gmt = $date->format( 'Y-m-d H:i:s' ); |
|
| 29 | + ActionScheduler_TimezoneHelper::set_local_timezone( $date ); |
|
| 30 | + $date_local = $date->format( 'Y-m-d H:i:s' ); |
|
| 31 | + |
|
| 32 | + /** @var \wpdb $wpdb */ //phpcs:ignore Generic.Commenting.DocComment.MissingShort |
|
| 33 | + global $wpdb; |
|
| 34 | + $wpdb->insert( |
|
| 35 | + $wpdb->actionscheduler_logs, |
|
| 36 | + array( |
|
| 37 | + 'action_id' => $action_id, |
|
| 38 | + 'message' => $message, |
|
| 39 | + 'log_date_gmt' => $date_gmt, |
|
| 40 | + 'log_date_local' => $date_local, |
|
| 41 | + ), |
|
| 42 | + array( '%d', '%s', '%s', '%s' ) |
|
| 43 | + ); |
|
| 44 | + |
|
| 45 | + return $wpdb->insert_id; |
|
| 46 | + } |
|
| 47 | + |
|
| 48 | + /** |
|
| 49 | + * Retrieve an action log entry. |
|
| 50 | + * |
|
| 51 | + * @param int $entry_id Log entry ID. |
|
| 52 | + * |
|
| 53 | + * @return ActionScheduler_LogEntry |
|
| 54 | + */ |
|
| 55 | + public function get_entry( $entry_id ) { |
|
| 56 | + /** @var \wpdb $wpdb */ //phpcs:ignore Generic.Commenting.DocComment.MissingShort |
|
| 57 | + global $wpdb; |
|
| 58 | + $entry = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->actionscheduler_logs} WHERE log_id=%d", $entry_id ) ); |
|
| 59 | + |
|
| 60 | + return $this->create_entry_from_db_record( $entry ); |
|
| 61 | + } |
|
| 62 | + |
|
| 63 | + /** |
|
| 64 | + * Create an action log entry from a database record. |
|
| 65 | + * |
|
| 66 | + * @param object $record Log entry database record object. |
|
| 67 | + * |
|
| 68 | + * @return ActionScheduler_LogEntry |
|
| 69 | + */ |
|
| 70 | + private function create_entry_from_db_record( $record ) { |
|
| 71 | + if ( empty( $record ) ) { |
|
| 72 | + return new ActionScheduler_NullLogEntry(); |
|
| 73 | + } |
|
| 74 | + |
|
| 75 | + if ( is_null( $record->log_date_gmt ) ) { |
|
| 76 | + $date = as_get_datetime_object( ActionScheduler_StoreSchema::DEFAULT_DATE ); |
|
| 77 | + } else { |
|
| 78 | + $date = as_get_datetime_object( $record->log_date_gmt ); |
|
| 79 | + } |
|
| 80 | + |
|
| 81 | + return new ActionScheduler_LogEntry( $record->action_id, $record->message, $date ); |
|
| 82 | + } |
|
| 83 | + |
|
| 84 | + /** |
|
| 85 | + * Retrieve the an action's log entries from the database. |
|
| 86 | + * |
|
| 87 | + * @param int $action_id Action ID. |
|
| 88 | + * |
|
| 89 | + * @return ActionScheduler_LogEntry[] |
|
| 90 | + */ |
|
| 91 | + public function get_logs( $action_id ) { |
|
| 92 | + /** @var \wpdb $wpdb */ //phpcs:ignore Generic.Commenting.DocComment.MissingShort |
|
| 93 | + global $wpdb; |
|
| 94 | + |
|
| 95 | + $records = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->actionscheduler_logs} WHERE action_id=%d", $action_id ) ); |
|
| 96 | + |
|
| 97 | + return array_map( array( $this, 'create_entry_from_db_record' ), $records ); |
|
| 98 | + } |
|
| 99 | + |
|
| 100 | + /** |
|
| 101 | + * Initialize the data store. |
|
| 102 | + * |
|
| 103 | + * @codeCoverageIgnore |
|
| 104 | + */ |
|
| 105 | + public function init() { |
|
| 106 | + $table_maker = new ActionScheduler_LoggerSchema(); |
|
| 107 | + $table_maker->init(); |
|
| 108 | + $table_maker->register_tables(); |
|
| 109 | + |
|
| 110 | + parent::init(); |
|
| 111 | + |
|
| 112 | + add_action( 'action_scheduler_deleted_action', array( $this, 'clear_deleted_action_logs' ), 10, 1 ); |
|
| 113 | + } |
|
| 114 | + |
|
| 115 | + /** |
|
| 116 | + * Delete the action logs for an action. |
|
| 117 | + * |
|
| 118 | + * @param int $action_id Action ID. |
|
| 119 | + */ |
|
| 120 | + public function clear_deleted_action_logs( $action_id ) { |
|
| 121 | + /** @var \wpdb $wpdb */ //phpcs:ignore Generic.Commenting.DocComment.MissingShort |
|
| 122 | + global $wpdb; |
|
| 123 | + $wpdb->delete( $wpdb->actionscheduler_logs, array( 'action_id' => $action_id ), array( '%d' ) ); |
|
| 124 | + } |
|
| 125 | + |
|
| 126 | + /** |
|
| 127 | + * Bulk add cancel action log entries. |
|
| 128 | + * |
|
| 129 | + * @param array $action_ids List of action ID. |
|
| 130 | + */ |
|
| 131 | + public function bulk_log_cancel_actions( $action_ids ) { |
|
| 132 | + if ( empty( $action_ids ) ) { |
|
| 133 | + return; |
|
| 134 | + } |
|
| 135 | + |
|
| 136 | + /** @var \wpdb $wpdb */ //phpcs:ignore Generic.Commenting.DocComment.MissingShort |
|
| 137 | + global $wpdb; |
|
| 138 | + $date = as_get_datetime_object(); |
|
| 139 | + $date_gmt = $date->format( 'Y-m-d H:i:s' ); |
|
| 140 | + ActionScheduler_TimezoneHelper::set_local_timezone( $date ); |
|
| 141 | + $date_local = $date->format( 'Y-m-d H:i:s' ); |
|
| 142 | + $message = __( 'action canceled', 'woocommerce' ); |
|
| 143 | + $format = '(%d, ' . $wpdb->prepare( '%s, %s, %s', $message, $date_gmt, $date_local ) . ')'; |
|
| 144 | + $sql_query = "INSERT {$wpdb->actionscheduler_logs} (action_id, message, log_date_gmt, log_date_local) VALUES "; |
|
| 145 | + $value_rows = array(); |
|
| 146 | + |
|
| 147 | + foreach ( $action_ids as $action_id ) { |
|
| 148 | + $value_rows[] = $wpdb->prepare( $format, $action_id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
|
| 149 | + } |
|
| 150 | + $sql_query .= implode( ',', $value_rows ); |
|
| 151 | + |
|
| 152 | + $wpdb->query( $sql_query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
|
| 153 | + } |
|
| 154 | 154 | } |
@@ -5,22 +5,22 @@ |
||
| 5 | 5 | * @codeCoverageIgnore |
| 6 | 6 | */ |
| 7 | 7 | class ActionScheduler_wpPostStore_TaxonomyRegistrar { |
| 8 | - public function register() { |
|
| 9 | - register_taxonomy( ActionScheduler_wpPostStore::GROUP_TAXONOMY, ActionScheduler_wpPostStore::POST_TYPE, $this->taxonomy_args() ); |
|
| 10 | - } |
|
| 8 | + public function register() { |
|
| 9 | + register_taxonomy( ActionScheduler_wpPostStore::GROUP_TAXONOMY, ActionScheduler_wpPostStore::POST_TYPE, $this->taxonomy_args() ); |
|
| 10 | + } |
|
| 11 | 11 | |
| 12 | - protected function taxonomy_args() { |
|
| 13 | - $args = array( |
|
| 14 | - 'label' => __( 'Action Group', 'woocommerce' ), |
|
| 15 | - 'public' => false, |
|
| 16 | - 'hierarchical' => false, |
|
| 17 | - 'show_admin_column' => true, |
|
| 18 | - 'query_var' => false, |
|
| 19 | - 'rewrite' => false, |
|
| 20 | - ); |
|
| 12 | + protected function taxonomy_args() { |
|
| 13 | + $args = array( |
|
| 14 | + 'label' => __( 'Action Group', 'woocommerce' ), |
|
| 15 | + 'public' => false, |
|
| 16 | + 'hierarchical' => false, |
|
| 17 | + 'show_admin_column' => true, |
|
| 18 | + 'query_var' => false, |
|
| 19 | + 'rewrite' => false, |
|
| 20 | + ); |
|
| 21 | 21 | |
| 22 | - $args = apply_filters( 'action_scheduler_taxonomy_args', $args ); |
|
| 23 | - return $args; |
|
| 24 | - } |
|
| 22 | + $args = apply_filters( 'action_scheduler_taxonomy_args', $args ); |
|
| 23 | + return $args; |
|
| 24 | + } |
|
| 25 | 25 | } |
| 26 | - |
|
| 27 | 26 | \ No newline at end of file |
| 27 | + |
|
| 28 | 28 | \ No newline at end of file |