PluginProbe
WANotifier for Forms and Actions / 3.0.2
WANotifier for Forms and Actions v3.0.2
3.1.0 3.0.4 2.7.10 2.7.11 2.7.12 2.7.13 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 3.0.0 3.0.1 3.0.2 3.0.3 trunk 0.1.0 0.1.1 1.0.0 1.0.1 1.0.2 1.0.3 All 66 releases
notifier / includes / classes / class-notifier-backend.php

class-notifier-backend.php in WANotifier for Forms and Actions 3.0.2, at includes/classes/class-notifier-backend.php

838 lines 34.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) {
3 exit;
4 }
5
6 /**
7 * Backend class for WANotifier plugin v3
8 *
9 * Consolidates admin menu registration, settings management, REST API endpoints,
10 * and the React shell view. Replaces the former Dashboard, Settings, Tools, and
11 * WooCommerce Configure classes.
12 *
13 * @package Notifier
14 */
15 class Notifier_Backend {
16
17 // ========================================
18 // 1. INITIALIZATION
19 // ========================================
20
21 public static function init() {
22 add_action( 'admin_menu', array( __CLASS__, 'register_menus' ) );
23 add_action( 'admin_menu', array( __CLASS__, 'manage_menu_visibility' ), 999 );
24 add_action( 'admin_init', array( __CLASS__, 'handle_disconnect_action' ) );
25 add_action( 'rest_api_init', array( __CLASS__, 'register_rest_routes' ) );
26 add_filter( 'plugin_action_links_notifier/notifier.php', array( __CLASS__, 'add_plugin_action_links' ) );
27 }
28
29 public static function add_plugin_action_links( $links ) {
30 $settings_link = '<a href="' . esc_url( admin_url( 'admin.php?page=notifier' ) ) . '">' . __( 'Settings', 'notifier' ) . '</a>';
31 array_unshift( $links, $settings_link );
32 return $links;
33 }
34
35 // ========================================
36 // 2. MENU REGISTRATION
37 // ========================================
38
39 public static function register_menus() {
40 add_menu_page(
41 'WANotifier',
42 'WANotifier',
43 'manage_options',
44 NOTIFIER_NAME,
45 array( __CLASS__, 'render_react_app' ),
46 'data:image/svg+xml;base64,' . base64_encode( file_get_contents( NOTIFIER_PATH . 'assets/images/menu-icon.svg' ) ),
47 '51'
48 );
49
50 add_submenu_page(
51 NOTIFIER_NAME,
52 __( 'Integrations', 'notifier' ),
53 __( 'Integrations', 'notifier' ),
54 'manage_options',
55 NOTIFIER_NAME,
56 array( __CLASS__, 'render_react_app' )
57 );
58
59 add_submenu_page(
60 NOTIFIER_NAME,
61 __( 'WhatsApp Chat Button', 'notifier' ),
62 __( 'Chat Button', 'notifier' ),
63 'manage_options',
64 'notifier-chat-button',
65 array( __CLASS__, 'render_react_app' )
66 );
67
68 add_submenu_page(
69 NOTIFIER_NAME,
70 __( 'Settings', 'notifier' ),
71 __( 'Settings', 'notifier' ),
72 'manage_options',
73 'notifier-settings',
74 array( __CLASS__, 'render_react_app' )
75 );
76
77 add_submenu_page(
78 NOTIFIER_NAME,
79 __( 'Logs', 'notifier' ),
80 __( 'Logs', 'notifier' ),
81 'manage_options',
82 'notifier-logs',
83 array( __CLASS__, 'render_react_app' )
84 );
85
86 add_submenu_page(
87 NOTIFIER_NAME,
88 __( 'Migrate to v3', 'notifier' ),
89 __( 'Migrate to v3', 'notifier' ),
90 'manage_options',
91 'notifier-migration',
92 array( __CLASS__, 'render_react_app' )
93 );
94 }
95
96 /**
97 * Manage submenu visibility based on application state.
98 */
99 public static function manage_menu_visibility() {
100 global $submenu;
101
102 if ( empty( $submenu[ NOTIFIER_NAME ] ) ) {
103 return;
104 }
105
106 $migration_complete = Notifier_Migration::is_migration_complete();
107 $has_legacy = Notifier_Migration::has_legacy_triggers();
108
109 if ( ! $migration_complete && $has_legacy ) {
110 // Migration pending: show only "Migrate to v3".
111 foreach ( $submenu[ NOTIFIER_NAME ] as $key => $item ) {
112 if ( isset( $item[2] ) && 'notifier-migration' !== $item[2] ) {
113 unset( $submenu[ NOTIFIER_NAME ][ $key ] );
114 }
115 }
116 } else {
117 // Migration complete or no legacy triggers: hide migration page.
118 foreach ( $submenu[ NOTIFIER_NAME ] as $key => $item ) {
119 if ( isset( $item[2] ) && 'notifier-migration' === $item[2] ) {
120 unset( $submenu[ NOTIFIER_NAME ][ $key ] );
121 }
122 }
123 }
124
125 // Always hide legacy trigger CPT menu items.
126 foreach ( $submenu[ NOTIFIER_NAME ] as $key => $item ) {
127 if ( isset( $item[2] ) && (
128 'edit.php?post_type=wa_notifier_trigger' === $item[2] ||
129 'post-new.php?post_type=wa_notifier_trigger' === $item[2]
130 ) ) {
131 unset( $submenu[ NOTIFIER_NAME ][ $key ] );
132 }
133 }
134 }
135
136 // ========================================
137 // 3. REACT SHELL VIEW
138 // ========================================
139
140 public static function render_react_app() {
141 include_once NOTIFIER_PATH . 'views/admin.php';
142 }
143
144 // ========================================
145 // 4. DISCONNECT HANDLER
146 // ========================================
147
148 public static function handle_disconnect_action() {
149 if ( ! isset( $_GET['notifier_disconnect'] ) || ! isset( $_GET['notifier_slug'] ) ) {
150 return;
151 }
152
153 if ( empty( $_GET['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'notifier_disconnect_' . sanitize_key( $_GET['notifier_slug'] ) ) ) {
154 return;
155 }
156
157 if ( ! current_user_can( 'manage_options' ) ) {
158 return;
159 }
160
161 $slug = sanitize_key( $_GET['notifier_slug'] );
162 Notifier_Connection::disconnect( $slug );
163
164 wp_safe_redirect( admin_url( 'admin.php?page=notifier&disconnected=' . $slug ) );
165 exit;
166 }
167
168 // ========================================
169 // 5. REST API ROUTES
170 // ========================================
171
172 public static function register_rest_routes() {
173 $namespace = 'notifier/v1';
174
175 register_rest_route( $namespace, '/admin/integrations', array(
176 'methods' => 'GET',
177 'callback' => array( __CLASS__, 'rest_get_integrations' ),
178 'permission_callback' => array( __CLASS__, 'rest_permission_check' ),
179 ) );
180
181 register_rest_route( $namespace, '/admin/settings', array(
182 array(
183 'methods' => 'GET',
184 'callback' => array( __CLASS__, 'rest_get_settings' ),
185 'permission_callback' => array( __CLASS__, 'rest_permission_check' ),
186 ),
187 array(
188 'methods' => 'POST',
189 'callback' => array( __CLASS__, 'rest_save_settings' ),
190 'permission_callback' => array( __CLASS__, 'rest_permission_check' ),
191 ),
192 ) );
193
194 register_rest_route( $namespace, '/admin/sync-triggers', array(
195 'methods' => 'POST',
196 'callback' => array( __CLASS__, 'rest_sync_triggers' ),
197 'permission_callback' => array( __CLASS__, 'rest_permission_check' ),
198 ) );
199
200 register_rest_route( $namespace, '/admin/preview-btn-style', array(
201 'methods' => 'POST',
202 'callback' => array( __CLASS__, 'rest_preview_btn_style' ),
203 'permission_callback' => array( __CLASS__, 'rest_permission_check' ),
204 ) );
205
206 register_rest_route( $namespace, '/admin/logs/dates', array(
207 'methods' => 'GET',
208 'callback' => array( __CLASS__, 'rest_get_log_dates' ),
209 'permission_callback' => array( __CLASS__, 'rest_permission_check' ),
210 ) );
211
212 register_rest_route( $namespace, '/admin/logs', array(
213 array(
214 'methods' => 'GET',
215 'callback' => array( __CLASS__, 'rest_get_logs' ),
216 'permission_callback' => array( __CLASS__, 'rest_permission_check' ),
217 ),
218 array(
219 'methods' => 'DELETE',
220 'callback' => array( __CLASS__, 'rest_delete_logs' ),
221 'permission_callback' => array( __CLASS__, 'rest_permission_check' ),
222 ),
223 ) );
224
225 register_rest_route( $namespace, '/admin/migration-status', array(
226 'methods' => 'GET',
227 'callback' => array( __CLASS__, 'rest_get_migration_status' ),
228 'permission_callback' => array( __CLASS__, 'rest_permission_check' ),
229 ) );
230
231 register_rest_route( $namespace, '/admin/finalize-migration', array(
232 'methods' => 'POST',
233 'callback' => array( __CLASS__, 'rest_finalize_migration' ),
234 'permission_callback' => array( __CLASS__, 'rest_permission_check' ),
235 ) );
236
237 register_rest_route( $namespace, '/admin/disconnect', array(
238 'methods' => 'POST',
239 'callback' => array( __CLASS__, 'rest_disconnect' ),
240 'permission_callback' => array( __CLASS__, 'rest_permission_check' ),
241 ) );
242
243 register_rest_route( $namespace, '/admin/wp-available-meta-fields', array(
244 'methods' => 'GET',
245 'callback' => array( __CLASS__, 'rest_get_wp_available_meta_fields' ),
246 'permission_callback' => array( __CLASS__, 'rest_permission_check' ),
247 ) );
248 register_rest_route( $namespace, '/admin/sync-meta-fields', array(
249 'methods' => 'POST',
250 'callback' => array( __CLASS__, 'rest_sync_meta_fields' ),
251 'permission_callback' => array( __CLASS__, 'rest_permission_check' ),
252 ) );
253 }
254
255 public static function rest_permission_check() {
256 return current_user_can( 'manage_options' );
257 }
258
259 // ========================================
260 // 6. REST HANDLERS — INTEGRATIONS
261 // ========================================
262
263 public static function rest_get_integrations() {
264 $integrations = self::get_integrations();
265 $result = array();
266
267 foreach ( $integrations as $slug => $integration ) {
268 $no_connect = ! empty( $integration['no_connect'] );
269 $is_connected = ! $no_connect && Notifier_Connection::is_connected( $slug );
270 $connect_url = ! $no_connect ? Notifier_Connection::get_connect_url( $slug ) : '';
271
272 $disconnect_url = wp_nonce_url(
273 admin_url( 'admin.php?page=notifier&notifier_disconnect=1&notifier_slug=' . $slug ),
274 'notifier_disconnect_' . $slug
275 );
276
277 $conn = $is_connected ? Notifier_Connection::get_connection_info( $slug ) : array();
278 $connected_at = ! empty( $conn['connected_at'] ) ? $conn['connected_at'] : '';
279
280 $result[ $slug ] = array_merge( $integration, array(
281 'slug' => $slug,
282 'is_connected' => $is_connected,
283 'connect_url' => $connect_url,
284 'disconnect_url' => $disconnect_url,
285 'connected_at' => $connected_at,
286 ) );
287 }
288
289 $result['_meta'] = array(
290 'has_woo_legacy_triggers' => ! Notifier_Migration::is_migration_complete() && in_array( Notifier_Migration::get_trigger_scenario(), array( 'woo_only', 'mixed' ), true ),
291 'is_woo_active' => class_exists( 'WooCommerce' ),
292 'is_wanowc_active' => Notifier::is_woo_handled_by_standalone_plugin(),
293 );
294
295 return new WP_REST_Response( $result, 200 );
296 }
297
298 // ========================================
299 // 7. REST HANDLERS — SETTINGS
300 // ========================================
301
302 public static function rest_get_settings() {
303 return new WP_REST_Response( self::get_all_settings(), 200 );
304 }
305
306 public static function rest_save_settings( WP_REST_Request $request ) {
307 $data = $request->get_json_params();
308 $errors = array();
309 $allowed = self::get_settings_field_definitions();
310
311 foreach ( $data as $key => $value ) {
312 if ( ! isset( $allowed[ $key ] ) ) {
313 continue;
314 }
315
316 $def = $allowed[ $key ];
317 $option_name = NOTIFIER_PREFIX . $key;
318
319 switch ( $def['type'] ) {
320 case 'checkbox':
321 $value = ( 'yes' === $value || '1' === $value || true === $value ) ? 'yes' : 'no';
322 break;
323 case 'number':
324 $value = absint( $value );
325 if ( isset( $def['min'] ) ) {
326 $value = max( $def['min'], $value );
327 }
328 break;
329 case 'select':
330 if ( ! empty( $def['options'] ) && ! array_key_exists( $value, $def['options'] ) ) {
331 $value = $def['default'] ?? '';
332 }
333 break;
334 case 'textarea':
335 $value = wp_kses_post( trim( $value ) );
336 break;
337 case 'array':
338 $value = is_array( $value ) ? array_map( 'sanitize_text_field', $value ) : array();
339 break;
340 default:
341 $value = sanitize_text_field( $value );
342 break;
343 }
344
345 if ( 'ctc_whatsapp_number' === $key && ! empty( $value ) && function_exists( 'notifier_validate_phone_number' ) && ! notifier_validate_phone_number( $value ) ) {
346 $errors[] = __( 'Please enter a valid WhatsApp phone number with country code.', 'notifier' );
347 continue;
348 }
349
350 update_option( $option_name, $value, 'yes' );
351 }
352
353 if ( ! empty( $errors ) ) {
354 return new WP_REST_Response( array(
355 'success' => false,
356 'errors' => $errors,
357 ), 200 );
358 }
359
360 return new WP_REST_Response( array(
361 'success' => true,
362 'message' => __( 'Settings saved.', 'notifier' ),
363 'settings' => self::get_all_settings(),
364 ), 200 );
365 }
366
367 // ========================================
368 // 8. REST HANDLERS — SYNC TRIGGERS
369 // ========================================
370
371 public static function rest_sync_triggers( WP_REST_Request $request ) {
372 $slug = sanitize_key( $request->get_param( 'slug' ) );
373
374 if ( empty( $slug ) || ! Notifier_Connection::is_connected( $slug ) ) {
375 return new WP_REST_Response( array(
376 'success' => false,
377 'message' => __( 'Integration not connected.', 'notifier' ),
378 ), 400 );
379 }
380
381 $trigger_schema = Notifier_Connection::build_trigger_schema( $slug );
382 $result = Notifier_Connection::send_saas_request( $slug, 'sync', array( 'triggers' => $trigger_schema ) );
383
384 return new WP_REST_Response( array(
385 'success' => ( false !== $result ),
386 'message' => ( false !== $result ) ? __( 'Triggers synced successfully.', 'notifier' ) : __( 'Failed to sync with SaaS.', 'notifier' ),
387 ), 200 );
388 }
389
390 public static function rest_disconnect( WP_REST_Request $request ) {
391 $data = $request->get_json_params();
392 $slug = isset( $data['slug'] ) ? sanitize_key( $data['slug'] ) : '';
393
394 if ( empty( $slug ) ) {
395 return new WP_REST_Response( array( 'success' => false, 'message' => __( 'Invalid slug.', 'notifier' ) ), 400 );
396 }
397
398 Notifier_Connection::disconnect( $slug );
399
400 return new WP_REST_Response( array( 'success' => true, 'message' => __( 'Disconnected successfully.', 'notifier' ) ), 200 );
401 }
402
403 // ========================================
404 // 9. REST HANDLERS — CHAT BUTTON PREVIEW
405 // ========================================
406
407 public static function rest_preview_btn_style( WP_REST_Request $request ) {
408 $btn_style = sanitize_text_field( $request->get_param( 'btn_style' ) );
409 $valid_styles = array_keys( self::get_button_styles() );
410
411 if ( ! in_array( $btn_style, $valid_styles, true ) || 'default' === $btn_style ) {
412 return new WP_REST_Response( array( 'preview' => '' ), 200 );
413 }
414
415 $template = NOTIFIER_PATH . 'templates/buttons/' . $btn_style . '.php';
416 if ( ! file_exists( $template ) ) {
417 return new WP_REST_Response( array( 'preview' => '' ), 200 );
418 }
419
420 ob_start();
421 include $template;
422 $html = ob_get_clean();
423
424 return new WP_REST_Response( array( 'preview' => $html ), 200 );
425 }
426
427 // ========================================
428 // 10. REST HANDLERS — LOGS
429 // ========================================
430
431 public static function rest_get_log_dates() {
432 global $wpdb;
433 $table_name = $wpdb->prefix . NOTIFIER_ACTIVITY_TABLE_NAME;
434
435 $dates = $wpdb->get_col( "SELECT DISTINCT DATE(timestamp) as log_date FROM `$table_name` ORDER BY log_date DESC" );
436
437 $formatted = array();
438 foreach ( $dates as $date ) {
439 $formatted[] = function_exists( 'notifier_convert_date_from_utc' )
440 ? notifier_convert_date_from_utc( $date, 'Y-m-d' )
441 : $date;
442 }
443
444 return new WP_REST_Response( $formatted, 200 );
445 }
446
447 public static function rest_get_logs( WP_REST_Request $request ) {
448 $date = sanitize_text_field( $request->get_param( 'date' ) );
449
450 if ( empty( $date ) ) {
451 return new WP_REST_Response( array(), 200 );
452 }
453
454 global $wpdb;
455 $table_name = $wpdb->prefix . NOTIFIER_ACTIVITY_TABLE_NAME;
456
457 $logs = $wpdb->get_results( $wpdb->prepare(
458 "SELECT log_id, timestamp, message, type FROM `$table_name` WHERE DATE(timestamp) = %s ORDER BY timestamp DESC",
459 $date
460 ) );
461
462 return new WP_REST_Response( $logs ?: array(), 200 );
463 }
464
465 public static function rest_delete_logs() {
466 global $wpdb;
467 $table_name = $wpdb->prefix . NOTIFIER_ACTIVITY_TABLE_NAME;
468
469 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Custom table; $table_name from trusted constant and $wpdb->prefix only.
470 $wpdb->query( "TRUNCATE TABLE `{$table_name}`" );
471
472 return new WP_REST_Response( array(
473 'success' => true,
474 'message' => __( 'All logs have been deleted.', 'notifier' ),
475 ), 200 );
476 }
477
478 // ========================================
479 // 11. REST HANDLERS — MIGRATION
480 // ========================================
481
482 public static function rest_get_migration_status() {
483 $is_complete = Notifier_Migration::is_migration_complete();
484 $has_legacy = Notifier_Migration::has_legacy_triggers();
485 $slugs = Notifier_Migration::get_non_woo_slugs_with_legacy_triggers();
486 $all_connected = Notifier_Migration::all_non_woo_slugs_connected();
487 $migrate_url = Notifier_Migration::get_migrate_connect_url();
488 $trigger_scenario = Notifier_Migration::get_trigger_scenario();
489
490 $trigger_posts = array();
491 if ( $has_legacy ) {
492 $integrations = self::get_integrations();
493
494 $posts = get_posts( array(
495 'post_type' => 'wa_notifier_trigger',
496 'post_status' => 'publish',
497 'numberposts' => -1,
498 ) );
499
500 $all_triggers = Notifier_Notification_Triggers::get_notification_triggers();
501 $trigger_label_map = array();
502 foreach ( $all_triggers as $group => $triggers ) {
503 foreach ( $triggers as $t ) {
504 $base = Notifier_Notification_Triggers::get_trigger_id_without_site_key( $t['id'] );
505 $trigger_label_map[ $base ] = $t['label'];
506 }
507 }
508
509 foreach ( $posts as $post ) {
510 $trigger_id = get_post_meta( $post->ID, NOTIFIER_PREFIX . 'trigger', true );
511 $base_trigger = Notifier_Notification_Triggers::get_trigger_id_without_site_key( $trigger_id );
512 $slug = Notifier_Notification_Triggers::get_integration_slug_for_trigger( $base_trigger );
513 $is_woo = in_array( $slug, Notifier_Migration::WOO_SLUGS, true );
514 $connected = $slug ? Notifier_Connection::is_connected( $slug ) : false;
515 $label = isset( $integrations[ $slug ]['label'] ) ? $integrations[ $slug ]['label'] : ucfirst( $slug ?: 'Unknown' );
516 $trigger_label = isset( $trigger_label_map[ $base_trigger ] ) ? $trigger_label_map[ $base_trigger ] : $base_trigger;
517
518 $trigger_posts[] = array(
519 'id' => $post->ID,
520 'title' => $trigger_label,
521 'slug' => $slug ?: 'unknown',
522 'label' => $label,
523 'is_connected' => $connected,
524 'is_woo' => $is_woo,
525 );
526 }
527 }
528
529 return new WP_REST_Response( array(
530 'is_complete' => $is_complete,
531 'has_legacy' => $has_legacy,
532 'slugs' => $slugs,
533 'all_connected' => $all_connected,
534 'migrate_url' => $migrate_url,
535 'trigger_posts' => $trigger_posts,
536 'trigger_scenario' => $trigger_scenario,
537 'integrations_url' => admin_url( 'admin.php?page=notifier' ),
538 ), 200 );
539 }
540
541 public static function rest_finalize_migration() {
542 if ( ! Notifier_Migration::all_non_woo_slugs_connected() ) {
543 return new WP_REST_Response( array(
544 'success' => false,
545 'message' => __( 'Not all integrations are connected yet.', 'notifier' ),
546 ), 400 );
547 }
548
549 $api_key = trim( get_option( 'notifier_api_key', '' ) );
550 if ( $api_key ) {
551 Notifier_Migration::disconnect_legacy_on_saas( $api_key );
552 }
553
554 Notifier_Migration::delete_legacy_trigger_posts();
555
556 update_option( Notifier_Migration::STATUS_OPTION, 'complete' );
557
558 return new WP_REST_Response( array(
559 'success' => true,
560 'message' => __( 'Migration complete!', 'notifier' ),
561 'redirect' => admin_url( 'admin.php?page=notifier' ),
562 ), 200 );
563 }
564
565
566 // ========================================
567 // 12b. REST HANDLER — WORDPRESS META FIELDS
568 // ========================================
569
570 public static function rest_get_wp_available_meta_fields( $request ) {
571 $show_hidden = rest_sanitize_boolean( $request->get_param( 'show_hidden' ) );
572 $groups = array();
573
574 // Single "Post Meta" group: all custom meta keys from all public post types (option stores all; filter by show_hidden).
575 $combined = Notifier_Notification_Merge_Tags::get_custom_meta_keys( null );
576 if ( ! empty( $combined ) ) {
577 $post_meta = array();
578 foreach ( $combined as $item ) {
579 if ( ! $show_hidden && isset( $item['key'] ) && substr( (string) $item['key'], 0, 1 ) === '_' ) {
580 continue;
581 }
582 $post_meta[] = array(
583 'id' => $item['post_type'] . '_meta_' . $item['key'],
584 'label' => $item['key'],
585 'return_type' => 'all',
586 );
587 }
588 if ( ! empty( $post_meta ) ) {
589 $groups['Post Meta'] = $post_meta;
590 }
591 }
592
593 // User meta group (option stores all; filter by show_hidden).
594 $user_keys = Notifier_Notification_Merge_Tags::get_user_meta_keys();
595 if ( ! empty( $user_keys ) && is_array( $user_keys ) ) {
596 $user_meta = array();
597 foreach ( $user_keys as $key ) {
598 if ( ! $show_hidden && substr( (string) $key, 0, 1 ) === '_' ) {
599 continue;
600 }
601 $user_meta[] = array(
602 'id' => 'user_meta_' . $key,
603 'label' => $key,
604 'return_type' => 'all',
605 );
606 }
607 if ( ! empty( $user_meta ) ) {
608 $groups['User Meta'] = $user_meta;
609 }
610 }
611
612 // Hook-added fields: exclude groups that are builtin (from merge tags / trigger definitions).
613 $hook_fields = self::get_hook_added_merge_tag_fields();
614 foreach ( $hook_fields as $field ) {
615 $group = isset( $field['group'] ) ? $field['group'] : 'Custom Fields';
616 if ( ! isset( $groups[ $group ] ) ) {
617 $groups[ $group ] = array();
618 }
619 $groups[ $group ][] = array(
620 'id' => $field['id'],
621 'label' => isset( $field['label'] ) ? $field['label'] : $field['id'],
622 'return_type' => isset( $field['return_type'] ) ? $field['return_type'] : 'text',
623 );
624 }
625
626 return new WP_REST_Response( array( 'groups' => $groups ), 200 );
627 }
628
629 /**
630 * POST /admin/sync-meta-fields — Sync all post and user meta keys from DB into options (normal + hidden).
631 * Call this to refresh the meta field list when new meta is added on the site.
632 */
633 public static function rest_sync_meta_fields() {
634 delete_option( 'notifier_custom_meta_keys' );
635 delete_option( 'notifier_user_meta_keys' );
636 Notifier_Notification_Merge_Tags::get_custom_meta_keys( null );
637 Notifier_Notification_Merge_Tags::get_user_meta_keys();
638 return new WP_REST_Response( array(
639 'success' => true,
640 'message' => __( 'Meta fields synced. The list now includes all current post and user meta keys.', 'notifier' ),
641 ), 200 );
642 }
643
644 /**
645 * Return merge tag fields added via the notifier_notification_merge_tags filter
646 * that are not part of the built-in groups.
647 *
648 * @return array[] Array of { group, id, label } objects.
649 */
650 private static function get_hook_added_merge_tag_fields() {
651 // Builtin groups from merge tags / trigger definitions so we exclude them from the Data Fields dropdown.
652 $all_tags = apply_filters( 'notifier_notification_merge_tags', array() );
653 $builtin_groups = array_merge( array( 'WordPress' ), array_keys( $all_tags ) );
654 $fields = array();
655
656 foreach ( $all_tags as $group => $tags ) {
657 if ( in_array( $group, $builtin_groups, true ) ) {
658 continue;
659 }
660 foreach ( (array) $tags as $tag ) {
661 if ( empty( $tag['id'] ) ) {
662 continue;
663 }
664 $fields[] = array(
665 'group' => $group,
666 'id' => $tag['id'],
667 'label' => isset( $tag['label'] ) ? $tag['label'] : $tag['id'],
668 );
669 }
670 }
671
672 return $fields;
673 }
674
675 // ========================================
676 // 13. INTEGRATION DEFINITIONS
677 // ========================================
678
679 public static function get_integrations() {
680 $img = NOTIFIER_URL . 'assets/images/integrations/';
681 $woo_handled = Notifier::is_woo_handled_by_standalone_plugin();
682
683 return array(
684 'woocommerce' => array(
685 'label' => 'WooCommerce',
686 'description' => __( 'Order notifications and customer sync', 'notifier' ),
687 'icon' => $img . 'woocommerce-logo.png',
688 'active' => class_exists( 'WooCommerce' ),
689 'has_config' => $woo_handled ? false : true,
690 'config_component' => $woo_handled ? '' : 'WooCommerceConfig',
691 'handled_by_standalone' => $woo_handled,
692 ),
693 'wcar' => array(
694 'label' => 'Cart Abandonment Recovery for WooCommerce',
695 'description' => __( 'Send notifications when a cart is abandoned', 'notifier' ),
696 'icon' => $img . 'wcar-logo.svg',
697 'active' => class_exists( 'CARTFLOWS_CA_Loader' ) && defined( 'CARTFLOWS_CA_VER' ) && version_compare( CARTFLOWS_CA_VER, '1.2.25', '>=' ),
698 'has_config' => false,
699 'handled_by_standalone' => $woo_handled,
700 ),
701 'wordpress' => array(
702 'label' => 'WordPress',
703 'description' => __( 'Posts, users, comments and more', 'notifier' ),
704 'icon' => $img . 'wordpress-logo.png',
705 'active' => true,
706 'has_config' => true,
707 'config_component' => 'WordPressConfig',
708 ),
709 'gravityforms' => array(
710 'label' => 'Gravity Forms',
711 'description' => __( 'Form submission notifications', 'notifier' ),
712 'icon' => $img . 'gravity-forms-logo.png',
713 'active' => class_exists( 'GFCommon' ),
714 'has_config' => false,
715 ),
716 'cf7' => array(
717 'label' => 'Contact Form 7',
718 'description' => __( 'Form submission notifications', 'notifier' ),
719 'icon' => $img . 'contact-form-7-logo.png',
720 'active' => class_exists( 'WPCF7' ),
721 'has_config' => false,
722 ),
723 'wpforms' => array(
724 'label' => 'WPForms',
725 'description' => __( 'Form submission notifications', 'notifier' ),
726 'icon' => $img . 'wpforms-logo.png',
727 'active' => class_exists( 'WPForms' ),
728 'has_config' => false,
729 ),
730 'ninjaforms' => array(
731 'label' => 'Ninja Forms',
732 'description' => __( 'Form submission notifications', 'notifier' ),
733 'icon' => $img . 'nf-logo.png',
734 'active' => class_exists( 'Ninja_Forms' ),
735 'has_config' => false,
736 ),
737 'formidable' => array(
738 'label' => 'Formidable Forms',
739 'description' => __( 'Form submission notifications', 'notifier' ),
740 'icon' => $img . 'formidable-logo.png',
741 'active' => class_exists( 'FrmForm' ),
742 'has_config' => false,
743 ),
744 'fluentforms' => array(
745 'label' => 'Fluent Forms',
746 'description' => __( 'Form submission notifications', 'notifier' ),
747 'icon' => $img . 'fluent-forms-logo.webp',
748 'active' => function_exists( 'fluentFormApi' ),
749 'has_config' => false,
750 ),
751 'forminator' => array(
752 'label' => 'Forminator Forms',
753 'description' => __( 'Form submission notifications', 'notifier' ),
754 'icon' => $img . 'forminator-form-logo.svg',
755 'active' => class_exists( 'Forminator' ),
756 'has_config' => false,
757 ),
758 'sureforms' => array(
759 'label' => 'SureForms',
760 'description' => __( 'Form submission notifications', 'notifier' ),
761 'icon' => $img . 'sureform-logo.svg',
762 'active' => class_exists( 'SRFM\Plugin_Loader' ),
763 'has_config' => false,
764 ),
765 'wsform' => array(
766 'label' => 'WS Forms',
767 'description' => __( 'Form submission notifications', 'notifier' ),
768 'icon' => $img . 'ws-form-logo.svg',
769 'active' => class_exists( 'WS_Form' ),
770 'has_config' => false,
771 ),
772 );
773 }
774
775 // ========================================
776 // 14. SETTINGS DATA
777 // ========================================
778
779 public static function get_all_settings() {
780 return array(
781 'default_country_code' => get_option( 'notifier_default_country_code', '' ),
782 'ctc_enable' => get_option( 'notifier_ctc_enable', 'no' ),
783 'ctc_whatsapp_number' => get_option( 'notifier_ctc_whatsapp_number', '' ),
784 'ctc_greeting_text' => get_option( 'notifier_ctc_greeting_text', '' ),
785 'ctc_button_style' => get_option( 'notifier_ctc_button_style', 'default' ),
786 'ctc_custom_button_image_url' => get_option( 'notifier_ctc_custom_button_image_url', '' ),
787 'enable_activity_log' => get_option( 'notifier_enable_activity_log', 'no' ),
788 'wp_enabled_merge_tags' => get_option( 'notifier_wp_enabled_merge_tags', array() ),
789 'wp_enabled_recipient_fields' => get_option( 'notifier_wp_enabled_recipient_fields', array() ),
790 'button_styles' => self::get_button_styles(),
791 );
792 }
793
794 private static function get_settings_field_definitions() {
795 return array(
796 'default_country_code' => array( 'type' => 'text' ),
797 'ctc_enable' => array( 'type' => 'checkbox' ),
798 'ctc_whatsapp_number' => array( 'type' => 'text' ),
799 'ctc_greeting_text' => array( 'type' => 'text' ),
800 'ctc_button_style' => array( 'type' => 'select', 'options' => self::get_button_styles(), 'default' => 'default' ),
801 'ctc_custom_button_image_url' => array( 'type' => 'text' ),
802 'enable_activity_log' => array( 'type' => 'checkbox' ),
803 'wp_enabled_merge_tags' => array( 'type' => 'array' ),
804 'wp_enabled_recipient_fields' => array( 'type' => 'array' ),
805 );
806 }
807
808 public static function get_button_styles() {
809 return array(
810 'default' => 'Select button style',
811 'btn-style-1' => 'Style 1',
812 'btn-style-2' => 'Style 2',
813 'btn-style-3' => 'Style 3',
814 'btn-style-4' => 'Style 4',
815 'btn-custom-image' => 'Add your own image',
816 );
817 }
818
819 // ========================================
820 // 15. ACTIVITY LOG
821 // ========================================
822
823 public static function insert_activity_log( $type = 'debug', $message = '' ) {
824 if ( 'yes' !== get_option( 'notifier_enable_activity_log' ) ) {
825 return;
826 }
827
828 global $wpdb;
829 $table_name = $wpdb->prefix . NOTIFIER_ACTIVITY_TABLE_NAME;
830
831 $wpdb->insert( $table_name, array(
832 'timestamp' => current_time( 'mysql', true ),
833 'message' => $message,
834 'type' => $type,
835 ), array( '%s', '%s', '%s' ) );
836 }
837 }
838