$subject_types Subjects. * @param bool $is_send Send action. * @param bool $is_destructive Destructive. * @param bool $requires_fresh Force fresh context. * @param AutomationNotificationSender|null $sender Notification sender. */ public function __construct( private readonly string $identifier, private readonly string $label, private readonly array $subject_types, private readonly bool $is_send = false, private readonly bool $is_destructive = false, private readonly bool $requires_fresh = false, private readonly ?AutomationNotificationSender $sender = null, ) {} /** * {@inheritDoc} */ public function identifier(): string { return $this->identifier; } /** * {@inheritDoc} */ public function label(): string { return $this->label; } /** * {@inheritDoc} * * @return list */ public function subject_types(): array { return $this->subject_types; } /** * {@inheritDoc} */ public function is_send(): bool { return $this->is_send; } /** * {@inheritDoc} */ public function is_destructive(): bool { return $this->is_destructive; } /** * {@inheritDoc} */ public function requires_fresh_context(): bool { return $this->requires_fresh; } /** * {@inheritDoc} * * @param AutomationContext $ctx Context. * @param ActionStep $step Step. */ public function execute( AutomationContext $ctx, ActionStep $step ): ActionResult { if ( $this->is_send ) { return $this->execute_send( $ctx, $step ); } if ( 'wait' === $this->identifier ) { return ActionResult::completed( 'wait' ); } $encoded = wp_json_encode( $step->config() ); $rendered = $this->identifier . ':' . ( is_string( $encoded ) ? $encoded : '' ); return ActionResult::completed( $rendered ); } /** * {@inheritDoc} * * @return array */ public function schema(): array { $fields = []; if ( $this->is_send ) { $fields[] = [ 'name' => 'template_key', 'type' => 'text', 'label' => 'Template key', 'required' => true, ]; } if ( 'add_internal_note' === $this->identifier ) { $fields[] = [ 'name' => 'note', 'type' => 'textarea', 'label' => 'Note', ]; } if ( 'add_customer_tag' === $this->identifier ) { $fields[] = [ 'name' => 'tag', 'type' => 'text', 'label' => 'Tag', ]; } if ( 'change_booking_status' === $this->identifier ) { $fields[] = [ 'name' => 'status', 'type' => 'select', 'label' => 'New status', 'options' => [ [ 'value' => 'confirmed', 'label' => 'Confirmed', ], [ 'value' => 'pending', 'label' => 'Pending', ], [ 'value' => 'cancelled', 'label' => 'Cancelled', ], [ 'value' => 'completed', 'label' => 'Completed', ], [ 'value' => 'no-show', 'label' => 'No-show', ], ], ]; } $category = 'data'; if ( $this->is_send ) { $category = 'communication'; } elseif ( 'change_booking_status' === $this->identifier || 'add_internal_note' === $this->identifier ) { $category = 'booking'; } return [ 'send' => $this->is_send, 'destructive' => $this->is_destructive, 'category' => $category, 'fields' => $fields, ]; } /** * Send-path guardrails (docs/20 §5.2–5.3). * * @param AutomationContext $ctx Context. * @param ActionStep $step Step. */ private function execute_send( AutomationContext $ctx, ActionStep $step ): ActionResult { if ( 'send_notification_template' === $this->identifier && null !== $this->sender ) { return $this->sender->send_template( $ctx, $step ); } $intent = $step->intent() ?? SendIntent::Transactional; if ( SendIntent::Marketing === $intent && ! $ctx->field_bool( 'marketing_consent' ) ) { return ActionResult::skipped( SkipReason::NoMarketingConsent ); } $recipient = $step->recipient() ?? AutomationRecipient::Customer; if ( AutomationRecipient::Agent === $recipient && $ctx->field_int( 'agent_id' ) <= 0 ) { return ActionResult::skipped( SkipReason::NoRecipient ); } $template = isset( $step->config()['template_key'] ) ? (string) $step->config()['template_key'] : $this->identifier; $rendered = sprintf( '%s → %s (%s)', $template, $recipient->value, $intent->value ); if ( ! OutboundGuard::allows_outbound() ) { return ActionResult::drafted( $rendered ); } return ActionResult::completed( $rendered ); } }