PluginProbe
NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar / 3.3.1
NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar v3.3.1
3.3.1 3.3.0 3.2.14 3.2.13 3.2.12 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 trunk 0.2.5.5 0.2.5.6 0.2.5.7 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.2.0 1.2.1 All 156 releases
notificationx / includes / Abilities / Manage / UpdateNotification.php

UpdateNotification.php in NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar 3.3.1, at includes/Abilities/Manage/UpdateNotification.php

237 lines 10.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Ability: update fields of an existing notification.
4 *
5 * @package NotificationX\Abilities\Manage
6 */
7
8 namespace NotificationX\Abilities\Manage;
9
10 use NotificationX\Abilities\AbilityBase;
11 use NotificationX\Abilities\BuilderInfo;
12 use NotificationX\Core\PostType;
13
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit;
16 }
17
18 /**
19 * Updates the title, theme, data source and/or configuration fields of a
20 * notification.
21 *
22 * The notification is loaded, the requested changes are merged over its stored
23 * data, and it is saved back through the normal PostType::save_post() pipeline
24 * so all NotificationX filters/hooks still run. The data source *can* be changed
25 * (as in the admin); when it is, the notification type is realigned to the new
26 * source automatically and the theme is validated/defaulted for that source.
27 */
28 class UpdateNotification extends AbilityBase {
29
30 protected $id = 'notificationx/update-notification';
31 protected $label = 'Update a notification';
32 protected $description = 'Update an existing notification: change its title, theme, data source, or other configuration fields. Provide nx_id plus what to change. Changing "source" realigns the type automatically and, if you do not also pass a valid "themes", resets the theme to that source\'s default — call describe-type for the new source to get valid theme ids and content fields. Invalid theme/source values are rejected (not silently ignored).';
33 protected $is_write = true;
34 protected $is_idempotent = true;
35
36 /**
37 * Fields that must never be overwritten via the free-form `fields` map.
38 * (source/type are handled explicitly as first-class inputs.)
39 *
40 * @var string[]
41 */
42 protected $protected_keys = array( 'nx_id', 'type', 'source', 'created_at', 'update_status' );
43
44 public function input_schema() {
45 return array(
46 'type' => 'object',
47 'required' => array( 'nx_id' ),
48 'properties' => array(
49 'nx_id' => array(
50 'type' => 'integer',
51 'description' => 'The notification id to update.',
52 ),
53 'title' => array(
54 'type' => 'string',
55 'description' => 'New title for the notification.',
56 ),
57 'source' => array(
58 'type' => 'string',
59 'description' => 'New data source id (from list-sources / describe-type). Changing it realigns the type and, unless a valid "themes" is also given, resets the theme to the new source default.',
60 ),
61 'themes' => array(
62 'type' => 'string',
63 'description' => 'New theme id (must be valid for the notification\'s source; see describe-type). Rejected if invalid.',
64 ),
65 'fields' => array(
66 'type' => 'object',
67 'description' => 'Advanced: a map of additional configuration fields to merge (e.g. content/design settings). Use get-notification / describe-type first to see the available fields.',
68 ),
69 ),
70 );
71 }
72
73 public function output_schema() {
74 return array(
75 'type' => 'object',
76 'properties' => array(
77 'nx_id' => array( 'type' => 'integer' ),
78 'applied' => array( 'type' => 'array' ),
79 'ignored' => array( 'type' => 'array' ),
80 'warnings' => array( 'type' => 'array' ),
81 'notification' => array( 'type' => 'object' ),
82 ),
83 );
84 }
85
86 public function execute( $input ) {
87 $nx_id = (int) $input['nx_id'];
88 $post_type = PostType::get_instance();
89 $existing = $post_type->get_post( $nx_id );
90
91 if ( empty( $existing ) ) {
92 return new \WP_Error(
93 'nx_mcp_not_found',
94 /* translators: %d: notification id. */
95 sprintf( __( 'No notification found with id %d.', 'notificationx' ), $nx_id ),
96 array( 'status' => 404 )
97 );
98 }
99
100 $applied = array();
101 $ignored = array();
102 $warnings = array();
103
104 // Start from the stored config so save_post() has everything it needs.
105 $data = $existing;
106 $old_source = isset( $existing['source'] ) ? $existing['source'] : '';
107 $source = $old_source;
108 $source_changed = false;
109
110 // --- Data source change (validated) ---------------------------------
111 if ( ! empty( $input['source'] ) && (string) $input['source'] !== $old_source ) {
112 $new_source = sanitize_text_field( $input['source'] );
113 if ( ! BuilderInfo::source_exists( $new_source ) ) {
114 return new \WP_Error(
115 'nx_mcp_invalid_source',
116 /* translators: %s: source id. */
117 sprintf( __( 'Unknown data source "%s". Call list-sources for valid ids.', 'notificationx' ), $new_source ),
118 array( 'status' => 400 )
119 );
120 }
121 if ( ! BuilderInfo::source_enabled( $new_source ) ) {
122 return new \WP_Error(
123 'nx_mcp_source_disabled',
124 /* translators: %s: source id. */
125 sprintf( __( 'The module for data source "%s" is disabled; enable it before using it.', 'notificationx' ), $new_source ),
126 array( 'status' => 409 )
127 );
128 }
129 $source = $new_source;
130 $source_changed = true;
131 $applied[] = 'source';
132 }
133
134 // Type always follows the source (authoritative).
135 $type = BuilderInfo::type_for_source( $source );
136 if ( '' === $type ) {
137 $type = isset( $existing['type'] ) ? $existing['type'] : $type;
138 }
139 if ( $type !== ( isset( $existing['type'] ) ? $existing['type'] : '' ) ) {
140 $applied[] = 'type';
141 }
142
143 // --- Theme (validated against the effective source) -----------------
144 // Only reject when the source's themes can actually be enumerated; a
145 // source whose themes cannot be introspected accepts the given theme.
146 $valid_themes = BuilderInfo::theme_ids_for_source( $source );
147 $theme_changed = false;
148 $old_theme = isset( $existing['themes'] ) ? $existing['themes'] : '';
149 if ( ! empty( $input['themes'] ) ) {
150 $theme = sanitize_text_field( $input['themes'] );
151 if ( ! empty( $valid_themes ) && ! in_array( $theme, $valid_themes, true ) ) {
152 return new \WP_Error(
153 'nx_mcp_invalid_theme',
154 sprintf(
155 /* translators: 1: theme id, 2: source id, 3: comma-separated valid ids. */
156 __( 'Theme "%1$s" is not valid for source "%2$s". Valid themes: %3$s', 'notificationx' ),
157 $theme,
158 $source,
159 implode( ', ', $valid_themes )
160 ),
161 array( 'status' => 400 )
162 );
163 }
164 $data['themes'] = $theme;
165 $applied[] = 'themes';
166 $theme_changed = ( $theme !== $old_theme );
167 } elseif ( $source_changed && ! empty( $valid_themes ) && ! in_array( isset( $data['themes'] ) ? $data['themes'] : '', $valid_themes, true ) ) {
168 // Old theme belongs to the old source; reset to the new source default.
169 $data['themes'] = BuilderInfo::effective_default_theme( $source );
170 $applied[] = 'themes';
171 $theme_changed = true;
172 $warnings[] = sprintf(
173 /* translators: 1: theme id, 2: source id. */
174 __( 'Theme reset to "%1$s" (the default for the new source "%2$s"). Pass a valid "themes" to choose another; see describe-type.', 'notificationx' ),
175 $data['themes'],
176 $source
177 );
178 }
179
180 // When the theme changed and the caller didn't explicitly set a content
181 // template, re-apply the new theme's default notification-template — same
182 // as selecting a theme in the admin builder — so data-driven types don't
183 // render blank after a theme/source switch. Restricted to the cases that
184 // actually break (no existing template, or a source change that
185 // invalidates the old tags) so a same-type theme swap never clobbers a
186 // user's customised content. Static-content themes have no template here.
187 $template_supplied = ! empty( $input['fields']['notification-template'] );
188 $existing_template = isset( $existing['notification-template'] ) ? $existing['notification-template'] : array();
189 if ( $theme_changed && ! $template_supplied && ( empty( $existing_template ) || $source_changed ) ) {
190 $default_template = BuilderInfo::default_template_for_theme( $data['themes'] );
191 if ( ! empty( $default_template ) ) {
192 $data['notification-template'] = $default_template;
193 $applied[] = 'notification-template';
194 }
195 }
196
197 if ( $source_changed ) {
198 $warnings[] = __( 'Data source changed: content fields from the previous source were kept. Set the new source\'s content via "fields" (see describe-type) so the notification renders as intended.', 'notificationx' );
199 }
200
201 // --- Title ----------------------------------------------------------
202 if ( isset( $input['title'] ) ) {
203 $data['title'] = sanitize_text_field( $input['title'] );
204 $applied[] = 'title';
205 }
206
207 // --- Free-form fields (protected keys reported, not silently dropped) -
208 if ( ! empty( $input['fields'] ) && is_array( $input['fields'] ) ) {
209 foreach ( $input['fields'] as $key => $value ) {
210 if ( in_array( $key, $this->protected_keys, true ) ) {
211 $ignored[] = $key;
212 continue;
213 }
214 $data[ $key ] = $value;
215 $applied[] = $key;
216 }
217 }
218
219 // Apply the resolved identity + source/type and mark updated.
220 $data['nx_id'] = $nx_id;
221 $data['type'] = $type;
222 $data['source'] = $source;
223 $data['updated_at'] = current_time( 'mysql' );
224 unset( $data['update_status'] );
225
226 $post_type->save_post( $data );
227
228 return array(
229 'nx_id' => $nx_id,
230 'applied' => array_values( array_unique( $applied ) ),
231 'ignored' => array_values( array_unique( $ignored ) ),
232 'warnings' => $warnings,
233 'notification' => $post_type->get_post( $nx_id ),
234 );
235 }
236 }
237