db->prepare( 'SELECT * FROM %i WHERE `id` = %d LIMIT 1', $this->table( 'automations' ), $id ); $row = $this->db->get_row( $sql, ARRAY_A ); return is_array( $row ) ? $this->hydrate( $row ) : null; } /** * {@inheritDoc} * * @param Uuid $uuid UUID. */ public function find_by_uuid( Uuid $uuid ): ?Automation { $sql = $this->db->prepare( 'SELECT * FROM %i WHERE `uuid` = %s LIMIT 1', $this->table( 'automations' ), $uuid->to_string() ); $row = $this->db->get_row( $sql, ARRAY_A ); return is_array( $row ) ? $this->hydrate( $row ) : null; } /** * {@inheritDoc} * * @param string $recipe_key Recipe key. */ public function find_by_recipe_key( string $recipe_key ): ?Automation { $sql = $this->db->prepare( 'SELECT * FROM %i WHERE `recipe_key` = %s LIMIT 1', $this->table( 'automations' ), $recipe_key ); $row = $this->db->get_row( $sql, ARRAY_A ); return is_array( $row ) ? $this->hydrate( $row ) : null; } /** * {@inheritDoc} * * @param string $event Event. * * @return list */ public function find_enabled_by_event( string $event ): array { $sql = $this->db->prepare( 'SELECT * FROM %i WHERE `is_enabled` = %d', $this->table( 'automations' ), 1 ); $rows = $this->db->get_results( $sql, ARRAY_A ); $out = []; if ( ! is_array( $rows ) ) { return $out; } foreach ( $rows as $row ) { if ( ! is_array( $row ) ) { continue; } $automation = $this->hydrate( $row ); if ( $automation->trigger()->event() === $event ) { $out[] = $automation; } } return $out; } /** * {@inheritDoc} * * @return list */ public function all(): array { $sql = $this->db->prepare( 'SELECT * FROM %i ORDER BY `id` ASC', $this->table( 'automations' ) ); $rows = $this->db->get_results( $sql, ARRAY_A ); $out = []; if ( ! is_array( $rows ) ) { return $out; } foreach ( $rows as $row ) { if ( is_array( $row ) ) { $out[] = $this->hydrate( $row ); } } return $out; } /** * {@inheritDoc} * * @param Automation $automation Entity. * * @throws DomainException When the insert does not yield a positive id. * * @since 0.1.0 */ public function save( Automation $automation ): void { $data = [ 'uuid' => $automation->uuid()->to_string(), 'name' => $automation->name(), 'description' => $automation->description(), 'recipe_key' => $automation->recipe_key(), 'trigger_def' => $this->encode_json( $automation->trigger()->to_array() ), 'conditions' => $this->encode_json( $this->conditions_to_array( $automation->conditions() ) ), 'actions' => $this->encode_json( $this->actions_to_array( $automation->actions() ) ), 'cancel_if' => null === $automation->cancel_if() ? null : $this->encode_json( $automation->cancel_if()->to_array() ), 'pause_period_hours' => $automation->pause_period_hours(), 'is_enabled' => $automation->is_enabled() ? 1 : 0, 'run_count' => $automation->run_count(), 'last_run_at' => null === $automation->last_run_at() ? null : $this->format_datetime( $automation->last_run_at() ), 'created_at' => $this->format_datetime( $automation->created_at() ), 'updated_at' => $this->format_datetime( $automation->updated_at() ), ]; if ( null === $automation->id() ) { $inserted = $this->db->insert( $this->table( 'automations' ), $data ); if ( false === $inserted ) { throw new DomainException( 'automation.insert_failed', 'Could not save the automation. Deactivate and reactivate Bookline so the database tables update, then try again.' ); } $new_id = $this->last_insert_id(); if ( $new_id < 1 ) { throw new DomainException( 'automation.insert_failed', 'Could not save the automation. Deactivate and reactivate Bookline so the database tables update, then try again.' ); } $automation->assign_id( $new_id ); return; } $this->db->update( $this->table( 'automations' ), $data, [ 'id' => $automation->id() ] ); } /** * {@inheritDoc} * * @param Automation $automation Entity. */ public function delete( Automation $automation ): void { $id = $automation->id(); if ( null === $id ) { return; } $sql = $this->db->prepare( 'DELETE FROM %i WHERE `id` = %d', $this->table( 'automations' ), $id ); $this->query_prepared( $sql ); } /** * Hydrate a row. * * @param array $row Row. */ private function hydrate( array $row ): Automation { $trigger_raw = $this->decode_json_array( $row['trigger_def'] ?? null ); $conditions_raw = $this->decode_json_list( $row['conditions'] ?? null ); $actions_raw = $this->decode_json_list( $row['actions'] ?? null ); $cancel_raw = $this->decode_json_array( $row['cancel_if'] ?? null ); $conditions = []; foreach ( $conditions_raw as $item ) { $conditions[] = ConditionDefinition::from_array( $item ); } $actions = []; foreach ( $actions_raw as $item ) { $actions[] = ActionStep::from_array( $item ); } $pause = null; if ( isset( $row['pause_period_hours'] ) && '' !== (string) $row['pause_period_hours'] && null !== $row['pause_period_hours'] ) { $pause = (int) $row['pause_period_hours']; } $is_enabled = false; if ( isset( $row['is_enabled'] ) && 1 === (int) $row['is_enabled'] ) { $is_enabled = true; } return new Automation( (int) $row['id'], new Uuid( (string) $row['uuid'] ), (string) $row['name'], isset( $row['description'] ) && null !== $row['description'] ? (string) $row['description'] : null, isset( $row['recipe_key'] ) && null !== $row['recipe_key'] && '' !== (string) $row['recipe_key'] ? (string) $row['recipe_key'] : null, TriggerDefinition::from_array( $trigger_raw ), $conditions, $actions, [] === $cancel_raw ? null : CancelIfDefinition::from_array( $cancel_raw ), $pause, $is_enabled, (int) ( $row['run_count'] ?? 0 ), isset( $row['last_run_at'] ) && is_string( $row['last_run_at'] ) && '' !== $row['last_run_at'] ? $this->parse_datetime( $row['last_run_at'] ) : null, $this->parse_datetime( (string) $row['created_at'] ), $this->parse_datetime( (string) $row['updated_at'] ) ); } /** * Serialise conditions for JSON storage. * * @param list $conditions Conditions. * @return list> */ private function conditions_to_array( array $conditions ): array { $out = []; foreach ( $conditions as $condition ) { $out[] = $condition->to_array(); } return $out; } /** * Serialise actions for JSON storage. * * @param list $actions Actions. * @return list> */ private function actions_to_array( array $actions ): array { $out = []; foreach ( $actions as $action ) { $out[] = $action->to_array(); } return $out; } /** * Encode JSON. * * @param array|list $data Data. */ private function encode_json( array $data ): string { $json = wp_json_encode( $data ); return is_string( $json ) ? $json : '{}'; } /** * Decode an object JSON map. * * @param mixed $json JSON string or already-decoded map. * @return array */ private function decode_json_array( mixed $json ): array { if ( is_array( $json ) ) { return $json; } if ( ! is_string( $json ) || '' === $json ) { return []; } $decoded = $this->decode_meta( $json ); return null === $decoded ? [] : $decoded; } /** * Decode a JSON list of maps. * * @param mixed $json JSON string or already-decoded list. * @return list> */ private function decode_json_list( mixed $json ): array { if ( is_array( $json ) ) { $decoded = $json; } elseif ( is_string( $json ) && '' !== $json ) { $decoded = json_decode( $json, true ); if ( ! is_array( $decoded ) ) { return []; } } else { return []; } $out = []; foreach ( $decoded as $item ) { if ( is_array( $item ) ) { $out[] = $item; } } return $out; } }