; } if ( $record->status == self::STATUS_PENDING ) { return as_get_datetime_object( $record->scheduled_date_gmt ); } else { return as_get_datetime_object( $record->last_attempt_gmt ); } } /** * Stake a claim on actions. * * @param int $max_actions Maximum number of action to include in claim. * @param \DateTime $before_date Jobs must be schedule before this date. Defaults to now. * * @return ActionScheduler_ActionClaim */ public function stake_claim( $max_actions = 10, \DateTime $before_date = null, $hooks = array(), $group = '' ) { $claim_id = $this->generate_claim_id(); $this->claim_actions( $claim_id, $max_actions, $before_date, $hooks, $group ); $action_ids = $this->find_actions_by_claim_id( $claim_id ); return new ActionScheduler_ActionClaim( $claim_id, $action_ids ); } /** * Generate a new action claim. * * @return int Claim ID. */ protected function generate_claim_id() { /** @var \wpdb $wpdb */ global $wpdb; $now = as_get_datetime_object(); $wpdb->insert( $wpdb->actionscheduler_claims, [ 'date_created_gmt' => $now->format( 'Y-m-d H:i:s' ) ] ); return $wpdb->insert_id; } /** * Mark actions claimed. * * @param string $claim_id Claim Id. * @param int $limit Number of action to include in claim. * @param \DateTime $before_date Should use UTC timezone. * * @return int The number of actions that were claimed. * @throws \RuntimeException */ protected function claim_actions( $claim_id, $limit, \DateTime $before_date = null, $hooks = array(), $group = '' ) { /** @var \wpdb $wpdb */ global $wpdb; $now = as_get_datetime_object(); $date = is_null( $before_date ) ? $now : clone $before_date; // can't use $wpdb->update() because of the <= condition $update = "UPDATE {$wpdb->actionscheduler_actions} SET claim_id=%d, last_attempt_gmt=%s, last_attempt_local=%s"; $params = array( $claim_id, $now->format( 'Y-m-d H:i:s' ), current_time( 'mysql' ), ); $where = "WHERE claim_id = 0 AND scheduled_date_gmt <= %s AND status=%s"; $params[] = $date->format( 'Y-m-d H:i:s' ); $params[] = self::STATUS_PENDING; if ( ! empty( $hooks ) ) { $placeholders = array_fill( 0, count( $hooks ), '%s' ); $where .= ' AND hook IN (' . join( ', ', $placeholders ) . ')'; $params = array_merge( $params, array_values( $hooks ) ); } if ( ! empty( $group ) ) { $group_id = $this->get_group_id( $group, false ); // throw exception if no matching group found, this matches ActionScheduler_wpPostStore's behaviour if ( empty( $group_id ) ) { /* translators: %s: group name */ throw new InvalidArgumentException( sprintf( __( 'The group "%s" does not exist.', 'woocommerce' ), $group ) ); } $where .= ' AND group_id = %d'; $params[] = $group_id; } $order = "ORDER BY attempts ASC, scheduled_date_gmt ASC, action_id ASC LIMIT %d"; $params[] = $limit; $sql = $wpdb->prepare( "{$update} {$where} {$order}", $params ); $rows_affected = $wpdb->query( $sql ); if ( $rows_affected === false ) { throw new \RuntimeException( __( 'Unable to claim actions. Database error.', 'woocommerce' ) ); } return (int) $rows_affected; } /** * Get the number of active claims. * * @return int */ public function get_claim_count() { global $wpdb; $sql = "SELECT COUNT(DISTINCT claim_id) FROM {$wpdb->actionscheduler_actions} WHERE claim_id != 0 AND status IN ( %s, %s)"; $sql = $wpdb->prepare( $sql, [ self::STATUS_PENDING, self::STATUS_RUNNING ] ); return (int) $wpdb->get_var( $sql ); } /** * Return an action's claim ID, as stored in the claim_id column. * * @param string $action_id Action ID. * @return mixed */ public function get_claim_id( $action_id ) { /** @var \wpdb $wpdb */ global $wpdb; $sql = "SELECT claim_id FROM {$wpdb->actionscheduler_actions} WHERE action_id=%d"; $sql = $wpdb->prepare( $sql, $action_id ); return (int) $wpdb->get_var( $sql ); } /** * Retrieve the action IDs of action in a claim. * * @param string $claim_id Claim ID. * * @return int[] */ public function find_actions_by_claim_id( $claim_id ) { /** @var \wpdb $wpdb */ global $wpdb; $sql = "SELECT action_id FROM {$wpdb->actionscheduler_actions} WHERE claim_id=%d"; $sql = $wpdb->prepare( $sql, $claim_id ); $action_ids = $wpdb->get_col( $sql ); return array_map( 'intval', $action_ids ); } /** * Release actions from a claim and delete the claim. * * @param ActionScheduler_ActionClaim $claim Claim object. */ public function release_claim( ActionScheduler_ActionClaim $claim ) { /** @var \wpdb $wpdb */ global $wpdb; $wpdb->update( $wpdb->actionscheduler_actions, [ 'claim_id' => 0 ], [ 'claim_id' => $claim->get_id() ], [ '%d' ], [ '%d' ] ); $wpdb->delete( $wpdb->actionscheduler_claims, [ 'claim_id' => $claim->get_id() ], [ '%d' ] ); } /** * Remove the claim from an action. * * @param int $action_id Action ID. * * @return void */ public function unclaim_action( $action_id ) { /** @var \wpdb $wpdb */ global $wpdb; $wpdb->update( $wpdb->actionscheduler_actions, [ 'claim_id' => 0 ], [ 'action_id' => $action_id ], [ '%s' ], [ '%d' ] ); } /** * Mark an action as failed. * * @param int $action_id Action ID. */ public function mark_failure( $action_id ) { /** @var \wpdb $wpdb */ global $wpdb; $updated = $wpdb->update( $wpdb->actionscheduler_actions, [ 'status' => self::STATUS_FAILED ], [ 'action_id' => $action_id ], [ '%s' ], [ '%d' ] ); if ( empty( $updated ) ) { throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'woocommerce' ), $action_id ) ); } } /** * Add execution message to action log. * * @param int $action_id Action ID. * * @return void */ public function log_execution( $action_id ) { /** @var \wpdb $wpdb */ global $wpdb; $sql = "UPDATE {$wpdb->actionscheduler_actions} SET attempts = attempts+1, status=%s, last_attempt_gmt = %s, last_attempt_local = %s WHERE action_id = %d"; $sql = $wpdb->prepare( $sql, self::STATUS_RUNNING, current_time( 'mysql', true ), current_time( 'mysql' ), $action_id ); $wpdb->query( $sql ); } /** * Mark an action as complete. * * @param int $action_id Action ID. * * @return void */ public function mark_complete( $action_id ) { /** @var \wpdb $wpdb */ global $wpdb; $updated = $wpdb->update( $wpdb->actionscheduler_actions, [ 'status' => self::STATUS_COMPLETE, 'last_attempt_gmt' => current_time( 'mysql', true ), 'last_attempt_local' => current_time( 'mysql' ), ], [ 'action_id' => $action_id ], [ '%s' ], [ '%d' ] ); if ( empty( $updated ) ) { throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'woocommerce' ), $action_id ) ); } } /** * Get an action's status. * * @param int $action_id Action ID. * * @return string */ public function get_status( $action_id ) { /** @var \wpdb $wpdb */ global $wpdb; $sql = "SELECT status FROM {$wpdb->actionscheduler_actions} WHERE action_id=%d"; $sql = $wpdb->prepare( $sql, $action_id ); $status = $wpdb->get_var( $sql ); if ( $status === null ) { throw new \InvalidArgumentException( __( 'Invalid action ID. No status found.', 'woocommerce' ) ); } elseif ( empty( $status ) ) { throw new \RuntimeException( __( 'Unknown status found for action.', 'woocommerce' ) ); } else { return $status; } } }