PluginProbe
NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar / trunk
NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar vtrunk
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 / Read / DescribeType.php

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

270 lines 11.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Ability: describe a notification type — its sources, valid themes and content
4 * template — so a client can build/edit a notification with correct values
5 * instead of guessing theme ids and field keys.
6 *
7 * @package NotificationX\Abilities\Read
8 */
9
10 namespace NotificationX\Abilities\Read;
11
12 use NotificationX\Abilities\AbilityBase;
13 use NotificationX\Abilities\BuilderInfo;
14 use NotificationX\Types\TypeFactory;
15
16 if ( ! defined( 'ABSPATH' ) ) {
17 exit;
18 }
19
20 /**
21 * Returns the builder-accurate blueprint for a notification type: which data
22 * sources power it, and for each source the valid theme ids (with labels,
23 * preview images and the default), responsive themes, and the content-template
24 * placeholders. This is the source of truth the admin wizard uses, so nothing
25 * here is guessed.
26 *
27 * Intended flow: call describe-type first, pick a source + a theme id from the
28 * returned list, then call create-notification / update-notification with those
29 * exact values.
30 */
31 class DescribeType extends AbilityBase {
32
33 protected $id = 'notificationx/describe-type';
34 protected $label = 'Describe a notification type';
35 protected $description = 'Get everything needed to build or edit a notification of a given type: its data sources, and for each source the valid theme ids (with labels, preview images, pro-flag and the default), responsive theme ids, and the content-template placeholders. Always call this before create-notification / update-notification so the theme and source are valid instead of guessed. Omit "type" for a summary of every type; pass "type" (and optionally "source") for full detail.';
36 protected $is_write = false;
37
38 public function input_schema() {
39 return array(
40 'type' => 'object',
41 'properties' => array(
42 'type' => array(
43 'type' => 'string',
44 'description' => 'A notification type id (from list-types), e.g. "notification_bar", "offer_announcement", "exit_intent". Omit to get a summary of all types.',
45 ),
46 'source' => array(
47 'type' => 'string',
48 'description' => 'Optional data source id (from list-sources) to scope the themes/content to a single source. Defaults to the type\'s default source.',
49 ),
50 ),
51 );
52 }
53
54 public function output_schema() {
55 return array(
56 'type' => 'object',
57 'properties' => array(
58 'types' => array( 'type' => 'array' ),
59 'type' => array( 'type' => 'object' ),
60 ),
61 );
62 }
63
64 public function execute( $input ) {
65 BuilderInfo::boot();
66
67 $type_id = isset( $input['type'] ) ? (string) $input['type'] : '';
68 if ( '' === $type_id ) {
69 return array( 'types' => $this->summarize_all() );
70 }
71
72 $type = $this->find_type( $type_id );
73 if ( ! $type ) {
74 return new \WP_Error(
75 'nx_mcp_unknown_type',
76 /* translators: %s: type id. */
77 sprintf( __( 'Unknown notification type: %s. Call list-types (or describe-type with no argument) for valid ids.', 'notificationx' ), $type_id ),
78 array( 'status' => 404 )
79 );
80 }
81 $type_id = isset( $type->id ) ? $type->id : $type_id;
82
83 $sources = $this->sources_for_type( $type_id );
84 $source_ids = wp_list_pluck( $sources, 'id' );
85
86 // Which source to detail: requested (if valid), else the default, else first.
87 $requested = isset( $input['source'] ) ? (string) $input['source'] : '';
88 $source_id = '';
89 if ( $requested && in_array( $requested, $source_ids, true ) ) {
90 $source_id = $requested;
91 } elseif ( ! empty( $type->default_source ) && in_array( $type->default_source, $source_ids, true ) ) {
92 $source_id = $type->default_source;
93 } elseif ( ! empty( $source_ids ) ) {
94 $source_id = $source_ids[0];
95 }
96
97 $detail = array(
98 'id' => $type_id,
99 'title' => isset( $type->dashboard_title ) && $type->dashboard_title ? $type->dashboard_title : ( isset( $type->title ) ? $type->title : '' ),
100 'is_pro' => ! empty( $type->is_pro ),
101 'default_source' => isset( $type->default_source ) ? $type->default_source : '',
102 'default_theme' => isset( $type->default_theme ) ? $type->default_theme : '',
103 'link_type' => isset( $type->link_type ) ? $type->link_type : 'none',
104 'sources' => $sources,
105 );
106
107 if ( $source_id ) {
108 $themes = $this->rich_themes( BuilderInfo::theme_ids_for_source( $source_id ), BuilderInfo::all_themes() );
109 $effective = BuilderInfo::effective_default_theme( $source_id );
110
111 $detail['detailed_source'] = $source_id;
112 $detail['themes'] = $this->mark_default( $themes, $effective );
113 $detail['responsive_themes'] = $this->rich_themes( BuilderInfo::res_theme_ids_for_source( $source_id ), BuilderInfo::all_res_themes() );
114 $detail['content_template'] = $this->content_template_for_source( $source_id );
115 $detail['effective_default_theme'] = $effective;
116
117 // Flag a declared default that does not actually exist for this source
118 // (some types ship a stale default, e.g. "announcements_theme-one").
119 $declared = $detail['default_theme'];
120 if ( $declared && $declared !== $effective && ! in_array( $declared, wp_list_pluck( $themes, 'id' ), true ) ) {
121 $detail['default_theme_invalid'] = $declared;
122 }
123
124 $detail['required_to_create'] = array(
125 'type' => $type_id,
126 'source' => $source_id,
127 'themes' => $effective,
128 );
129 }
130
131 return array( 'type' => $detail );
132 }
133
134 /**
135 * Find a type by array key or by its ->id.
136 *
137 * @param string $type_id Type id.
138 * @return object|null
139 */
140 protected function find_type( $type_id ) {
141 $types = TypeFactory::get_instance()->get_all();
142 if ( isset( $types[ $type_id ] ) ) {
143 return $types[ $type_id ];
144 }
145 foreach ( (array) $types as $t ) {
146 if ( is_object( $t ) && isset( $t->id ) && $t->id === $type_id ) {
147 return $t;
148 }
149 }
150 return null;
151 }
152
153 /**
154 * Summary line for every registered type.
155 *
156 * @return array
157 */
158 protected function summarize_all() {
159 $out = array();
160 $types = TypeFactory::get_instance()->get_all();
161 if ( ! is_array( $types ) ) {
162 return $out;
163 }
164 foreach ( $types as $key => $type ) {
165 if ( ! is_object( $type ) ) {
166 continue;
167 }
168 $tid = isset( $type->id ) ? $type->id : (string) $key;
169 $out[] = array(
170 'id' => $tid,
171 'title' => isset( $type->dashboard_title ) && $type->dashboard_title ? $type->dashboard_title : ( isset( $type->title ) ? $type->title : '' ),
172 'is_pro' => ! empty( $type->is_pro ),
173 'default_source' => isset( $type->default_source ) ? $type->default_source : '',
174 'sources' => wp_list_pluck( $this->sources_for_type( $tid ), 'id' ),
175 );
176 }
177 return $out;
178 }
179
180 /**
181 * All data sources (extensions) that belong to a given type.
182 *
183 * @param string $type_id Type id.
184 * @return array[]
185 */
186 protected function sources_for_type( $type_id ) {
187 $result = array();
188 foreach ( BuilderInfo::sources() as $key => $ext ) {
189 if ( ! is_object( $ext ) || ( isset( $ext->types ) ? $ext->types : '' ) !== $type_id ) {
190 continue;
191 }
192 $sid = isset( $ext->id ) ? $ext->id : (string) $key;
193 $result[] = array(
194 'id' => $sid,
195 'title' => isset( $ext->title ) ? $ext->title : '',
196 'is_pro' => ! empty( $ext->is_pro ),
197 'module' => isset( $ext->module ) ? $ext->module : '',
198 'module_enabled' => BuilderInfo::source_enabled( $sid ),
199 'default_theme' => BuilderInfo::effective_default_theme( $sid ),
200 );
201 }
202 return $result;
203 }
204
205 /**
206 * Build rich theme objects from a list of ids + the raw theme map.
207 *
208 * @param string[] $ids Valid theme ids for the source.
209 * @param array $raw Raw nx_themes / nx_res_themes map.
210 * @return array[]
211 */
212 protected function rich_themes( $ids, $raw ) {
213 // Index raw entries by their value for lookup.
214 $by_value = array();
215 foreach ( $raw as $key => $entry ) {
216 $value = is_array( $entry ) && isset( $entry['value'] ) ? (string) $entry['value'] : (string) $key;
217 $by_value[ $value ] = $entry;
218 }
219 $out = array();
220 foreach ( $ids as $id ) {
221 $entry = isset( $by_value[ $id ] ) ? $by_value[ $id ] : array();
222 $out[] = array(
223 'id' => $id,
224 'label' => is_array( $entry ) && isset( $entry['label'] ) ? $entry['label'] : $id,
225 'preview_url' => is_array( $entry ) && isset( $entry['icon'] ) ? $entry['icon'] : '',
226 'is_pro' => is_array( $entry ) ? ! empty( $entry['is_pro'] ) : false,
227 'is_default' => false,
228 );
229 }
230 return $out;
231 }
232
233 /**
234 * Mark the effective-default theme in a rich theme list.
235 *
236 * @param array[] $themes Rich theme list.
237 * @param string $effective Effective default id.
238 * @return array[]
239 */
240 protected function mark_default( $themes, $effective ) {
241 foreach ( $themes as &$t ) {
242 $t['is_default'] = ( $t['id'] === $effective );
243 }
244 unset( $t );
245 return $themes;
246 }
247
248 /**
249 * The content-template placeholders for a source, best-effort (the builder
250 * filter is meant to be seeded by the metabox assembler and can throw when
251 * called bare, so a failure must never crash discovery).
252 *
253 * @param string $source_id Source id.
254 * @return array
255 */
256 protected function content_template_for_source( $source_id ) {
257 $out = array();
258 try {
259 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- consuming a core NotificationX (nx_) hook.
260 $templates = apply_filters( 'nx_notification_template', array() );
261 if ( is_array( $templates ) && isset( $templates[ $source_id ] ) ) {
262 $out['template'] = $templates[ $source_id ];
263 }
264 } catch ( \Throwable $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch -- intentional: degrade gracefully.
265 unset( $e );
266 }
267 return $out;
268 }
269 }
270