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 / CreateNotification.php

CreateNotification.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/CreateNotification.php

209 lines 9.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Ability: create a notification from a configuration object.
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 * Creates a new notification from a config object. The config is the same shape
20 * that get-notification returns (and that export-notification produces in Pro),
21 * so this is both a "create from scratch" tool and the second half of a
22 * cross-site copy (export on the origin, create on the destination).
23 *
24 * The notification is created disabled by default so it never surprises the
25 * site the moment it lands. Passing enabled:true activates it immediately; on
26 * the free plan the single-active limit still applies, so the stored record may
27 * come back disabled if another notification is already active — the returned
28 * "enabled" reflects the real state after save.
29 */
30 class CreateNotification extends AbilityBase {
31
32 protected $id = 'notificationx/create-notification';
33 protected $label = 'Create a notification';
34 protected $description = 'Create a new notification from a configuration object (the shape returned by get-notification). Use it to build a notification or to recreate an exported one on another site. Requires config.type, config.source and config.themes. Created disabled unless enabled:true (the free-plan single-active limit still applies).';
35 protected $is_write = true;
36 protected $is_idempotent = false;
37
38 /**
39 * Keys stripped from an incoming config so the save always inserts a new
40 * record rather than colliding with identity from another site.
41 *
42 * @var string[]
43 */
44 protected $strip_keys = array( 'nx_id', 'created_at', 'updated_at', 'update_status', '_nx_export' );
45
46 public function input_schema() {
47 return array(
48 'type' => 'object',
49 'required' => array( 'config' ),
50 'properties' => array(
51 'config' => array(
52 'type' => 'object',
53 'description' => 'The notification configuration. Must include at least "type", "source" and "themes". Get a valid shape from get-notification.',
54 ),
55 'title' => array(
56 'type' => 'string',
57 'description' => 'Optional title override for the new notification.',
58 ),
59 'enabled' => array(
60 'type' => 'boolean',
61 'description' => 'Activate the notification immediately. Defaults to false (created disabled).',
62 ),
63 ),
64 );
65 }
66
67 public function output_schema() {
68 return array(
69 'type' => 'object',
70 'properties' => array(
71 'nx_id' => array( 'type' => 'integer' ),
72 'enabled' => array( 'type' => 'boolean' ),
73 'notification' => array( 'type' => 'object' ),
74 ),
75 );
76 }
77
78 public function execute( $input ) {
79 $config = isset( $input['config'] ) && is_array( $input['config'] ) ? $input['config'] : array();
80
81 foreach ( $this->strip_keys as $key ) {
82 unset( $config[ $key ] );
83 }
84
85 // save_post() reads type/source/themes directly; without them it would
86 // emit notices and store an unusable record. Fail loudly instead.
87 foreach ( array( 'type', 'source', 'themes' ) as $required ) {
88 if ( empty( $config[ $required ] ) || ! is_string( $config[ $required ] ) ) {
89 return new \WP_Error(
90 'nx_mcp_invalid_config',
91 /* translators: %s: config field name. */
92 sprintf( __( 'The config is missing a valid "%s". A notification needs at least type, source and themes.', 'notificationx' ), $required ),
93 array( 'status' => 400 )
94 );
95 }
96 }
97
98 // The data source must be real and its module enabled.
99 if ( ! BuilderInfo::source_exists( $config['source'] ) ) {
100 return new \WP_Error(
101 'nx_mcp_invalid_source',
102 /* translators: %s: source id. */
103 sprintf( __( 'Unknown data source "%s". Call list-sources for valid ids.', 'notificationx' ), $config['source'] ),
104 array( 'status' => 400 )
105 );
106 }
107 if ( ! BuilderInfo::source_enabled( $config['source'] ) ) {
108 return new \WP_Error(
109 'nx_mcp_source_disabled',
110 /* translators: %s: source id. */
111 sprintf( __( 'The module for data source "%s" is disabled; enable it before creating this notification.', 'notificationx' ), $config['source'] ),
112 array( 'status' => 409 )
113 );
114 }
115
116 // The type is defined by the source — realign it so a mismatched/guessed
117 // type can never produce a broken record.
118 $resolved_type = BuilderInfo::type_for_source( $config['source'] );
119 if ( $resolved_type ) {
120 $config['type'] = $resolved_type;
121 }
122
123 // The theme must be valid for this source, else it saves but renders
124 // nothing. Only reject when we can actually enumerate the source's
125 // themes — if the list is empty (a source whose themes cannot be
126 // introspected) accept the theme rather than block creation.
127 $valid_themes = BuilderInfo::theme_ids_for_source( $config['source'] );
128 if ( ! empty( $valid_themes ) && ! in_array( $config['themes'], $valid_themes, true ) ) {
129 return new \WP_Error(
130 'nx_mcp_invalid_theme',
131 sprintf(
132 /* translators: 1: theme id, 2: source id, 3: comma-separated valid ids. */
133 __( 'Theme "%1$s" is not valid for source "%2$s". Call describe-type for this type; valid themes: %3$s', 'notificationx' ),
134 $config['themes'],
135 $config['source'],
136 implode( ', ', $valid_themes )
137 ),
138 array( 'status' => 400 )
139 );
140 }
141
142 // Data-driven types (comments, download stats, reviews, sales, forms)
143 // render their text through a notification-template that maps content
144 // slots to data tags. The admin builder writes that template from the
145 // selected theme's defaults via the nx_themes_trigger system; a headless
146 // create (MCP/REST/CLI) never fires those UI triggers, so without this
147 // the record saves and lists fine but renders blank. Backfill the theme's
148 // default template when the caller didn't supply one — reconstructed from
149 // the exact same trigger data the wizard uses, so it can never diverge.
150 // Static-content types (bar, cookie notice, announcement, exit intent)
151 // carry no template here and are unaffected.
152 if ( empty( $config['notification-template'] ) ) {
153 $default_template = BuilderInfo::default_template_for_theme( $config['themes'] );
154 if ( ! empty( $default_template ) ) {
155 $config['notification-template'] = $default_template;
156 }
157 }
158
159 if ( ! empty( $input['title'] ) ) {
160 $config['title'] = sanitize_text_field( $input['title'] );
161 } elseif ( ! isset( $config['title'] ) ) {
162 $config['title'] = __( 'Untitled notification', 'notificationx' );
163 }
164
165 $want_enabled = ! empty( $input['enabled'] );
166
167 // Always insert disabled, so the new record can never bypass the
168 // free-plan single-active cap on the way in. Activation (if asked for)
169 // then goes through the same gated path ToggleNotification uses.
170 $config['enabled'] = false;
171
172 $post_type = PostType::get_instance();
173 $saved = $post_type->save_post( $config );
174 $new_id = isset( $saved['nx_id'] ) ? (int) $saved['nx_id'] : 0;
175
176 if ( empty( $new_id ) ) {
177 return new \WP_Error(
178 'nx_mcp_create_failed',
179 __( 'Could not create the notification from the provided config.', 'notificationx' ),
180 array( 'status' => 500 )
181 );
182 }
183
184 // Activate only if requested AND allowed: can_enable() honours the free
185 // single-active limit (Pro lifts it via the nx_can_enable filter), and
186 // the update_status path keeps the enabled-source bookkeeping correct.
187 if ( $want_enabled && $post_type->can_enable( $config['source'] ) ) {
188 $post_type->save_post(
189 array(
190 'update_status' => true,
191 'nx_id' => $new_id,
192 'enabled' => true,
193 'source' => $config['source'],
194 )
195 );
196 }
197
198 // Re-read so the caller sees the stored, normalised record and the real
199 // enabled state (may be false if the single-active cap blocked it).
200 $fresh = $post_type->get_post( $new_id );
201
202 return array(
203 'nx_id' => $new_id,
204 'enabled' => ! empty( $fresh['enabled'] ),
205 'notification' => $fresh,
206 );
207 }
208 }
209