*/ public $before_state = array(); /** * State after apply. * * @var array */ public $after_state = array(); /** * Predicted field changes. * * @var array> */ public $diff = array(); /** * Warnings from the operation. * * @var array */ public $warnings = array(); /** * Error details when stale, failed or invalid. * * @var array|null */ public $error = null; /** * Apply attempts. * * @var int */ public $attempts = 0; /** * Token of the worker that claimed the row. * * @var string|null */ public $worker_token = null; /** * Claim time. * * @var string|null */ public $claimed_at = null; /** * Apply time. * * @var string|null */ public $applied_at = null; /** * Undo run that reverted this item. * * @var int|null */ public $reverted_by_run_id = null; /** * Builds from a database row. * * @param array|object $row Row. * @return RunItem */ public static function from_row( $row ) { $row = (array) $row; $item = new self(); foreach ( array( 'id', 'run_id', 'subscription_id', 'position', 'attempts' ) as $int ) { $item->$int = isset( $row[ $int ] ) ? (int) $row[ $int ] : 0; } $item->idem_key = isset( $row['idem_key'] ) ? (string) $row['idem_key'] : ''; $item->status = isset( $row['status'] ) ? (string) $row['status'] : ItemStatus::PENDING; $item->before_hash = isset( $row['before_hash'] ) ? $row['before_hash'] : null; $item->after_hash = isset( $row['after_hash'] ) ? $row['after_hash'] : null; $item->before_state = Json::decode( isset( $row['before_state'] ) ? $row['before_state'] : null, array() ); $item->after_state = Json::decode( isset( $row['after_state'] ) ? $row['after_state'] : null, array() ); $item->diff = Json::decode( isset( $row['diff'] ) ? $row['diff'] : null, array() ); $item->warnings = Json::decode( isset( $row['warnings'] ) ? $row['warnings'] : null, array() ); $item->error = Json::decode( isset( $row['error'] ) ? $row['error'] : null, null ); $item->worker_token = isset( $row['worker_token'] ) ? $row['worker_token'] : null; $item->claimed_at = isset( $row['claimed_at'] ) ? $row['claimed_at'] : null; $item->applied_at = isset( $row['applied_at'] ) ? $row['applied_at'] : null; $item->reverted_by_run_id = isset( $row['reverted_by_run_id'] ) ? (int) $row['reverted_by_run_id'] : null; return $item; } /** * REST form. * * @return array */ public function to_array() { return array( 'id' => $this->id, 'run_id' => $this->run_id, 'subscription_id' => $this->subscription_id, 'position' => $this->position, 'status' => $this->status, 'changed_fields' => array_values( wp_list_pluck( $this->diff, 'field' ) ), 'diff' => $this->diff_for_display(), 'before' => $this->before_state, 'after' => $this->after_state, 'warnings' => $this->warnings, 'error' => $this->error, 'attempts' => $this->attempts, 'applied_at' => $this->applied_at, 'reverted_by_run_id' => $this->reverted_by_run_id, ); } /** * The change list for the UI, with current labels. After apply, each written field shows the value * WooCommerce saved, not the value predicted at preview: tax is recalculated and * rounded per line on save, so the two can differ by a cent. * * @return array> */ public function diff_for_display() { $saved = null !== $this->applied_at && ! empty( $this->after_state ); $fields = wp_list_pluck( $this->diff, 'field' ); $diff = array(); foreach ( $this->diff as $change ) { $field = isset( $change['field'] ) ? (string) $change['field'] : ''; if ( '' === $field ) { continue; } // The gateway row already shows the name; the title row would repeat it. if ( Fields::PAYMENT_METHOD_TITLE === $field && in_array( Fields::PAYMENT_METHOD, $fields, true ) ) { continue; } // Labels follow the current wording, not the wording stored when the batch was made. $change['label'] = Fields::label( $field ); if ( $saved && array_key_exists( $field, $this->after_state ) ) { $change['after'] = $this->after_state[ $field ]; } if ( Fields::PAYMENT_METHOD === $field ) { $change['before_text'] = Gateways::name( (string) $change['before'], $this->state_value( $this->before_state, Fields::PAYMENT_METHOD_TITLE ) ); $change['after_text'] = Gateways::name( (string) $change['after'], $this->state_value( $this->after_state, Fields::PAYMENT_METHOD_TITLE ) ); } $parsed = Fields::parse( $field ); if ( $parsed && 'item' === $parsed['kind'] && in_array( $parsed['prop'], array( 'product_id', 'variation_id' ), true ) ) { foreach ( array( 'before', 'after' ) as $side ) { $id = isset( $change[ $side ] ) ? (int) $change[ $side ] : 0; if ( $id <= 0 ) { // No variation: the app shows "Not set". $change[ $side ] = null; continue; } $change[ $side . '_text' ] = self::product_name( $id ); } } $diff[] = $change; } return $diff; } /** * The name of a product or variation, or "#id" when the store no longer has it. * * @param int $id Product or variation ID. * @return string */ private static function product_name( $id ) { $product = function_exists( 'wc_get_product' ) ? wc_get_product( $id ) : null; return $product ? wp_strip_all_tags( $product->get_name() ) : '#' . $id; } /** * A value from a recorded state, or an empty string. * * @param array $state State. * @param string $field Field key. * @return string */ private function state_value( array $state, $field ) { return isset( $state[ $field ] ) && is_scalar( $state[ $field ] ) ? (string) $state[ $field ] : ''; } }