PluginProbe
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More / 2.3.1
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More v2.3.1
2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.2 2.2.1 2.2.0 2.1.2 2.1.1 trunk 0.0.1 0.0.2 0.0.3 0.0.4 0.0.5 0.0.6 0.0.7 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 All 66 releases
better-payment / includes / AI / Services / CampaignEditor.php

CampaignEditor.php in Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More 2.3.1, at includes/AI/Services/CampaignEditor.php

88 lines 3.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Better_Payment\Lite\AI\Services;
4
5 if ( ! defined( 'ABSPATH' ) ) {
6 exit;
7 }
8
9 /**
10 * Conversational editing of an existing campaign.
11 *
12 * Thin wrapper over {@see AIService} in 'edit' mode. Kept as its own service so
13 * block-level and bulk-edit features can extend edit behaviour without touching
14 * the generic orchestrator.
15 *
16 * Note: unlike generation, this path deliberately does NOT run
17 * {@see CampaignGenerator::fill_default_photos()}. The campaign being edited
18 * already holds the user's real, uploaded images, and a `set_layout` that echoes
19 * them back would have every one of them overwritten with stock artwork. A
20 * hallucinated `src` on an `insert_block` therefore still reaches the canvas
21 * here — a narrower guard (fill only photos with no attachment id) is the fix,
22 * not this one.
23 */
24 class CampaignEditor {
25
26 /**
27 * @param string $message
28 * @param array $context [ 'layout' => [...], 'meta' => [...] ]
29 * @param array $history
30 * @param string $target_element_id Optional. When the turn came from the
31 * builder's per-element AI buttons, the id of
32 * the selected widget.
33 * @return array|\WP_Error { assistant_message, operations, usage }
34 */
35 public static function edit( string $message, array $context = [], array $history = [], string $target_element_id = '' ) {
36 $target = self::resolve_target( $context, $target_element_id );
37
38 return AIService::run( 'edit', $message, $context, $history, [], $target );
39 }
40
41 /**
42 * Look the target element up in the trusted context layout.
43 *
44 * The client sends only an **id**. Type and current settings are read back out
45 * of the layout the request already carries, so the widget the prompt describes
46 * is by construction the widget that is on the page — a client that sent its
47 * own `type` could otherwise have the model briefed on a Donors Wall while the
48 * id pointed at a headline, and the resulting `update_block` would write
49 * donors-wall keys onto it.
50 *
51 * An id that matches nothing returns `[]`, which degrades to a normal unscoped
52 * edit rather than a scope that matches no element and drops every operation.
53 *
54 * @param array $context
55 * @param string $element_id
56 * @return array{id?: string, type?: string, settings?: array}
57 */
58 private static function resolve_target( array $context, string $element_id ): array {
59 $element_id = trim( $element_id );
60 if ( '' === $element_id ) {
61 return [];
62 }
63
64 $layout = isset( $context['layout'] ) && is_array( $context['layout'] ) ? $context['layout'] : [];
65
66 foreach ( (array) ( $layout['columns'] ?? [] ) as $column ) {
67 if ( ! is_array( $column ) ) {
68 continue;
69 }
70 foreach ( (array) ( $column['elements'] ?? [] ) as $element ) {
71 if ( ! is_array( $element ) ) {
72 continue;
73 }
74 if ( (string) ( $element['id'] ?? '' ) !== $element_id ) {
75 continue;
76 }
77 return [
78 'id' => $element_id,
79 'type' => (string) ( $element['type'] ?? '' ),
80 'settings' => isset( $element['settings'] ) && is_array( $element['settings'] ) ? $element['settings'] : [],
81 ];
82 }
83 }
84
85 return [];
86 }
87 }
88