PluginProbe
WANotifier for Forms and Actions / 3.1.1
WANotifier for Forms and Actions v3.1.1
3.1.1 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 All 67 releases
← All changes | includes/classes/class-notifier-notification-triggers.php +229 -448 2.7.93.1.1 View file →
@@ -1,5 +1,9 @@
1 1 <?php
2 +if ( ! defined( 'ABSPATH' ) ) {
3 + exit;
4 +}
5 +
2 6 /**
3 7 * Notification Triggers class
4 8 *
5 9 * @package Wa_Notifier
@@ -12,358 +16,74 @@
12 16 public static function init() {
13 17 add_filter( 'init', array( __CLASS__ , 'register_cpt') );
14 18 add_action( 'admin_menu', array( __CLASS__ , 'setup_admin_page') );
15 19 add_action( 'init', array( __CLASS__ , 'setup_triggers_action_hooks' ));
16 - add_action( 'add_meta_boxes', array( __CLASS__, 'create_meta_box' ) );
17 - add_action( 'post_submitbox_minor_actions', array( __CLASS__ , 'add_submitbox_meta' ) );
18 - add_action( 'save_post_wa_notifier_trigger', array(__CLASS__, 'save_meta'), 10, 2 );
19 - add_filter( 'bulk_actions-wa_notifier_trigger', array( __CLASS__, 'remove_bulk_actions' ) );
20 - add_filter( 'post_row_actions', array(__CLASS__, 'remove_quick_edit') , 10, 2);
21 - add_filter( 'gettext', array(__CLASS__, 'change_texts') , 10, 2 );
22 - add_filter( 'post_updated_messages', array(__CLASS__, 'update_save_messages') );
23 - add_filter( 'enter_title_here', array(__CLASS__, 'change_title_text') );
24 20 add_action( 'wp_ajax_notifier_change_trigger_status', array(__CLASS__, 'notifier_change_trigger_status'));
25 21 add_action( 'wp_ajax_notifier_fetch_trigger_fields', array(__CLASS__, 'notifier_fetch_trigger_fields'));
26 - add_filter( 'manage_wa_notifier_trigger_posts_columns', array( __CLASS__ , 'add_columns' ) );
27 - add_action( 'manage_wa_notifier_trigger_posts_custom_column', array( __CLASS__ , 'add_column_content' ) , 10, 2 );
28 - add_action( 'notifier_send_trigger_request', array( __CLASS__, 'send_scheduled_trigger_request' ), 10, 2 );
29 22 }
30 23
31 24 /**
32 - * Register custom post type
25 + * Register custom post type.
26 + *
27 + * Always registered so existing v2 trigger posts remain accessible for
28 + * migration queries and backward-compat firing. The admin UI is hidden.
33 29 */
34 30 public static function register_cpt () {
35 - if (!Notifier::is_api_active()){
36 - return;
37 - }
38 -
39 31 notifier_register_post_type('wa_notifier_trigger', 'Trigger', 'Triggers');
40 32 }
41 33
42 34 /**
43 - * Add page to admin menu
35 + * Add page to admin menu.
36 + *
37 + * Hidden after migration is complete — users no longer need the CPT UI.
44 38 */
45 39 public static function setup_admin_page () {
46 - if (!Notifier::is_api_active()){
47 - return;
48 - }
49 -
50 - add_submenu_page( NOTIFIER_NAME, 'Trigger', 'Triggers', 'manage_options', 'edit.php?post_type=wa_notifier_trigger' );
51 - add_submenu_page( NOTIFIER_NAME, 'Add trigger', 'Add trigger', 'manage_options', 'post-new.php?post_type=wa_notifier_trigger' );
40 + // v3: Trigger CPT menu items are always hidden.
41 + // The CPT is still registered (register_cpt) so posts exist and fire,
42 + // but users manage everything through the React Integrations UI.
43 + return;
52 44 }
53 45
54 46 /**
55 - * Create meta boxes
47 + * Setup triggers action hooks
56 48 */
57 - public static function create_meta_box () {
58 - add_meta_box(
59 - NOTIFIER_NAME . '-trigger-data',
60 - 'Trigger Settings',
61 - 'Notifier_Notification_Triggers::output',
62 - 'wa_notifier_trigger'
63 - );
49 + public static function setup_triggers_action_hooks() {
50 + // Legacy path: register hooks for CPT-enabled triggers.
51 + $triggers_to_register = self::get_enabled_notification_triggers();
64 52
65 - remove_meta_box( 'submitdiv', 'wa_notifier_trigger', 'side' );
66 - add_meta_box( 'submitdiv', 'Save Trigger', 'post_submit_meta_box', 'wa_notifier_trigger', 'side', 'high' );
67 - }
68 -
69 - /**
70 - * Add message template meta data in submit box
71 - */
72 - public static function add_submitbox_meta () {
73 - if ( 'wa_notifier_trigger' != get_post_type() ) {
74 - return;
75 - }
76 - global $post_id;
77 -
78 - if(!isset($post_id)){
79 - return;
80 - }
81 -
82 - $enabled = get_post_meta( $post_id, NOTIFIER_PREFIX . 'trigger_enabled' , true);
83 - $checked = '';
84 - if('yes' == $enabled) {
85 - $checked = 'checked="checked"';
86 - }
87 - echo '<div class="notifier-trigger-status d-flex justify-content-between align-items-center">';
88 - echo '<b>Enable:</b>';
89 - echo '<div class="notifier-toggle-switch"><input class="notifier-enable-trigger" type="checkbox" '.$checked.' data-post-id="'.$post_id.'"></div>';
90 - echo '</div>';
91 - }
92 -
93 - /**
94 - * Output meta box
95 - */
96 - public static function output () {
97 - include_once NOTIFIER_PATH . 'views/admin-triggers.php';
98 - }
99 -
100 - /**
101 - * Save meta
102 - */
103 - public static function save_meta( $post_id, $post ) {
104 - if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
105 - return;
106 - }
107 -
108 - if ( ! current_user_can( 'edit_post', $post_id ) ) {
109 - return;
110 - }
111 -
112 - if ( 'trash' == get_post_status( $post_id ) || 'auto-draft' == get_post_status( $post_id ) ){
113 - return;
114 - }
115 -
116 - $trigger = isset($_POST[NOTIFIER_PREFIX . 'trigger']) ? sanitize_text_field( wp_unslash( $_POST[NOTIFIER_PREFIX . 'trigger'] ) ) : '';
117 - $in_use_post_id = self::is_trigger_in_use($trigger, array($post_id));
118 - if($in_use_post_id){
119 - $notices[] = array(
120 - 'message' => '<b>ERROR: Trigger not Saved.</b> The selected trigger is already in use with another trigger: <a href="'. admin_url( 'post.php?post='.$in_use_post_id.'&action=edit' ) .'">' . get_the_title( $in_use_post_id ) . '</a>. Please select a different trigger or delete that trigger to save this one.',
121 - 'type' => 'error'
122 - );
123 - new Notifier_Admin_Notices($notices, true);
124 - return;
125 - }
126 -
127 - $trigger_data = array();
128 -
129 - foreach ($_POST as $key => $data) {
130 - if (strpos($key, NOTIFIER_PREFIX) !== false) {
131 - if(is_array($data)) {
132 - $trigger_data[$key] = notifier_sanitize_array($data);
133 - }
134 - else{
135 - $trigger_data[$key] = sanitize_text_field( wp_unslash ($data) );
136 - }
137 - update_post_meta( $post_id, $key, $trigger_data[$key]);
138 - }
139 - }
140 -
141 - // Delete data fields meta if not added in current request
142 - if(!isset($trigger_data[NOTIFIER_PREFIX . 'data_fields'])){
143 - delete_post_meta( $post_id, NOTIFIER_PREFIX . 'data_fields');
144 - }
145 -
146 - // Delete recipient fields meta if not added in current request
147 - if(!isset($trigger_data[NOTIFIER_PREFIX . 'recipient_fields'])){
148 - delete_post_meta( $post_id, NOTIFIER_PREFIX . 'recipient_fields');
149 - }
150 -
151 - $triggers_array = self::build_final_triggers_array();
152 -
153 - $params = array(
154 - 'site_url' => site_url(),
155 - 'source' => 'wp',
156 - 'triggers' => $triggers_array
157 - );
158 -
159 - $response = Notifier::send_api_request( 'update_triggers', $params, 'POST' );
160 -
161 - if($response->error){
162 - $notices[] = array(
163 - 'message' => $response->message,
164 - 'type' => 'error'
165 - );
166 - new Notifier_Admin_Notices($notices, true);
167 - }
168 - else{
169 - $notices[] = array(
170 - 'message' => 'Trigger details saved and synced with your WANotifier.com account successfully!',
171 - 'type' => 'success'
172 - );
173 - new Notifier_Admin_Notices($notices, true);
174 - }
175 - }
176 -
177 - /**
178 - * Build final triggers array
179 - */
180 - public static function build_final_triggers_array(){
181 - $final_triggers = array();
182 - $trigger_post_ids = get_posts(array (
183 - 'post_type' => 'wa_notifier_trigger',
184 - 'post_status' => 'publish',
185 - 'numberposts' => -1,
186 - 'fields' => 'ids'
187 - ));
188 -
189 - if(! empty($trigger_post_ids)){
190 - foreach($trigger_post_ids as $trigger_post_id){
191 - $trigger_name = get_post_meta($trigger_post_id, NOTIFIER_PREFIX . 'trigger', true);
192 - $data_fields = get_post_meta($trigger_post_id, NOTIFIER_PREFIX . 'data_fields', true);
193 - $recipient_fields = get_post_meta($trigger_post_id, NOTIFIER_PREFIX . 'recipient_fields', true);
194 - $trigger_name = self::get_trigger_id_with_site_key($trigger_name);
195 - $final_triggers[] = array(
196 - 'id' => $trigger_name,
197 - 'data_fields' => isset($data_fields) ? $data_fields : array(),
198 - 'recipient_fields' => isset($recipient_fields) ? $recipient_fields : array()
199 - );
200 - }
201 - }
202 -
203 - $triggers_array = array();
204 -
205 - $all_triggers = self::get_notification_triggers();
206 - foreach ($all_triggers as $key => $triggers) {
207 - foreach ($triggers as $trigger){
208 - foreach ($final_triggers as $final_trigger){
209 - if($trigger['id'] != $final_trigger['id']){
210 - continue;
53 + // v3 path: also register hooks for any trigger whose integration is connected,
54 + // regardless of whether a legacy CPT trigger exists.
55 + if ( class_exists( 'Notifier_Connection' ) ) {
56 + $all_triggers = self::get_notification_triggers();
57 + foreach ( $all_triggers as $group => $group_triggers ) {
58 + foreach ( $group_triggers as $trigger ) {
59 + $trigger_id = isset( $trigger['id'] ) ? $trigger['id'] : '';
60 + $base = self::get_trigger_id_without_site_key( $trigger_id );
61 + $slug = self::get_integration_slug_for_trigger( $base );
62 + if ( $slug && Notifier_Connection::is_connected( $slug ) ) {
63 + $triggers_to_register[] = $trigger;
211 64 }
212 -
213 - unset($trigger['action']);
214 -
215 - $final_trigger_merge_tags = array();
216 - if(!empty($trigger['merge_tags'])){
217 - foreach($trigger['merge_tags'] as $merge_tags_group => $merge_tags){
218 - foreach($merge_tags as $merge_tag){
219 - if( !empty($final_trigger['data_fields']) && in_array($merge_tag['id'], $final_trigger['data_fields']) ){
220 - $final_trigger_merge_tags[$merge_tags_group][] = array(
221 - 'id' => $merge_tag['id'],
222 - 'label' => $merge_tag['label'],
223 - 'preview_value' => isset($merge_tag['preview_value']) ? $merge_tag['preview_value'] : '',
224 - 'return_type' => isset($merge_tag['return_type']) ? $merge_tag['return_type'] : ''
225 - );
226 - }
227 - }
228 - }
229 - }
230 - $trigger['merge_tags'] = $final_trigger_merge_tags;
231 -
232 - $final_trigger_recipient_fields = array();
233 - if(!empty($trigger['recipient_fields'])){
234 - foreach($trigger['recipient_fields'] as $recipient_tags_group => $recipient_tags){
235 - foreach($recipient_tags as $recipient_tag){
236 - if( ! empty($final_trigger['recipient_fields']) && in_array($recipient_tag['id'], $final_trigger['recipient_fields']) ){
237 - $final_trigger_recipient_fields[$recipient_tags_group][] = array(
238 - 'id' => $recipient_tag['id'],
239 - 'label' => $recipient_tag['label']
240 - );
241 - }
242 - }
243 - }
244 - }
245 - $trigger['recipient_fields'] = $final_trigger_recipient_fields;
246 -
247 - $triggers_array[$key][] = $trigger;
248 65 }
249 66 }
250 67 }
251 68
252 - return $triggers_array;
253 - }
254 -
255 - /**
256 - * Update save action messages
257 - */
258 - public static function update_save_messages ($messages) {
259 - $messages['wa_notifier_trigger'][1] = '';
260 - $messages['wa_notifier_trigger'][6] = '';
261 - return $messages;
262 - }
263 -
264 - /**
265 - * Remove inline edit from Bulk Edit
266 - */
267 - public static function remove_bulk_actions( $actions ){
268 - unset( $actions['inline'] );
269 - return $actions;
270 - }
271 -
272 - /**
273 - * Remove inline Quick Edit
274 - */
275 - public static function remove_quick_edit( $actions, $post ) {
276 - if ('wa_notifier_trigger' == $post->post_type){
277 - unset($actions['inline hide-if-no-js']);
278 - }
279 - return $actions;
280 - }
281 -
282 - /**
283 - * Change text of buttons and links
284 - */
285 - public static function change_texts( $translation, $text ) {
286 - if ( 'wa_notifier_trigger' == get_post_type() ) {
287 - if ( $text == 'Update' ) {
288 - return 'Update';
69 + // De-duplicate by trigger ID to avoid registering the same hook twice.
70 + $registered = array();
71 + foreach ( $triggers_to_register as $trigger ) {
72 + $trigger_id = isset( $trigger['id'] ) ? $trigger['id'] : '';
73 + $hook = isset( $trigger['action']['hook'] ) ? $trigger['action']['hook'] : '';
74 + $callback = isset( $trigger['action']['callback'] ) ? $trigger['action']['callback'] : '';
75 + $priority = isset( $trigger['action']['priority'] ) ? $trigger['action']['priority'] : 10;
76 + $args_num = isset( $trigger['action']['args_num'] ) ? $trigger['action']['args_num'] : 1;
77 + if ( '' === $hook || '' === $callback ) {
78 + continue;
289 79 }
290 - elseif ($text == 'Publish') {
291 - return 'Save';
292 - }
293 - }
294 - return $translation;
295 - }
296 -
297 - /**
298 - * Change add title here text
299 - */
300 - public static function change_title_text( $title ){
301 - $screen = get_current_screen();
302 - if ( 'wa_notifier_trigger' == $screen->post_type ) {
303 - $title = 'Enter a trigger name';
304 - }
305 - return $title;
306 - }
307 -
308 - /**
309 - * Add columns to list page
310 - */
311 - public static function add_columns ($columns) {
312 - $date_col = $columns['date'];
313 - unset($columns['date']);
314 - $columns['trigger'] = 'Trigger';
315 - $columns['trigger_enabled'] = 'Status';
316 - $columns['date'] = $date_col;
317 - return $columns;
318 - }
319 -
320 - /**
321 - * Add column content
322 - */
323 - public static function add_column_content ( $column, $post_id ) {
324 - $group_name = '';
325 - $name = '';
326 - if ( 'trigger' === $column ) {
327 - $trigger = self::get_post_trigger_id( $post_id );
328 - $main_triggers = self::get_notification_triggers();
329 - foreach ($main_triggers as $key => $triggers) {
330 - foreach($triggers as $the_trigger){
331 - if($the_trigger['id'] == $trigger){
332 - $name = $the_trigger['label'];
333 - $group_name = $key;
334 - break 2;
335 - }
336 - }
337 - }
338 - echo '<small class="text-muted">' . $group_name . ' »</small><br>' . $name;
339 - }
340 - if ( 'trigger_enabled' === $column ) {
341 - $enabled = get_post_meta( $post_id, NOTIFIER_PREFIX . 'trigger_enabled' , true);
342 - $checked = '';
343 - if('yes' == $enabled) {
344 - $checked = 'checked="checked"';
345 - }
346 - echo '<div class="notifier-toggle-switch"><input class="notifier-enable-trigger" type="checkbox" '.$checked.' data-post-id="'.$post_id.'"></div>';
347 - }
348 - }
349 -
350 - /**
351 - * Setup triggers action hooks
352 - */
353 - public static function setup_triggers_action_hooks() {
354 - $enabled_triggers = self::get_enabled_notification_triggers();
355 - foreach ($enabled_triggers as $trigger) {
356 - $hook = isset($trigger['action']['hook']) ? $trigger['action']['hook'] : '';
357 - $callback = isset($trigger['action']['callback']) ? $trigger['action']['callback'] : '';
358 - $priority = isset($trigger['action']['priority']) ? $trigger['action']['priority'] : 10;
359 - $args_num = isset($trigger['action']['args_num']) ? $trigger['action']['args_num'] : 1;
360 - if ('' == $hook || '' == $callback) {
80 + $key = $trigger_id ?: ( $hook . '|' . ( is_string( $callback ) ? $callback : '' ) );
81 + if ( isset( $registered[ $key ] ) ) {
361 82 continue;
362 83 }
363 - else{
364 - add_action($hook, $callback, $priority, $args_num);
365 - }
84 + $registered[ $key ] = true;
85 + add_action( $hook, $callback, $priority, $args_num );
366 86 }
367 87 }
368 88
369 89 /**
@@ -385,10 +105,10 @@
385 105 $triggers['WordPress'][] = array(
386 106 'id' => 'new_'.$post->name,
387 107 'label' => 'New '.$post->labels->singular_name.' is published',
388 108 'description' => 'Trigger notification when a new '.$post->name.' is published.',
389 - 'merge_tags' => Notifier_Notification_Merge_Tags::get_merge_tags( array($post->labels->singular_name, $post->labels->singular_name . ' Custom Meta') ),
390 - 'recipient_fields' => Notifier_Notification_Merge_Tags::get_recipient_fields( array($post->labels->singular_name . ' Custom Meta') ),
109 + 'merge_tags' => Notifier_Notification_Merge_Tags::get_merge_tags( Notifier_Notification_Merge_Tags::get_post_merge_tag_types( $post->labels->singular_name ) ),
110 + 'recipient_fields' => Notifier_Notification_Merge_Tags::get_recipient_fields( Notifier_Notification_Merge_Tags::get_post_recipient_types( $post->labels->singular_name ) ),
391 111 'action' => array (
392 112 'hook' => 'publish_'.$post->name,
393 113 'args_num' => 3,
394 114 'callback' => function ( $post_id, $post, $old_status ) use ($post_slug) {
@@ -407,9 +127,9 @@
407 127 $triggers['WordPress'][] = array(
408 128 'id' => 'new_comment',
409 129 'label' => 'New Comment is added',
410 130 'description' => 'Trigger notification when a new comment is added.',
411 - 'merge_tags' => Notifier_Notification_Merge_Tags::get_merge_tags( array('Comment') ),
131 + 'merge_tags' => Notifier_Notification_Merge_Tags::get_merge_tags( Notifier_Notification_Merge_Tags::get_comment_merge_tag_types() ),
412 132 'recipient_fields' => array(),
413 133 'action' => array (
414 134 'hook' => 'comment_post',
415 135 'args_num' => 3,
@@ -428,10 +148,10 @@
428 148 $triggers['WordPress'][] = array(
429 149 'id' => 'new_user',
430 150 'label' => 'New User is registered',
431 151 'description' => 'Trigger notification when a new user is created.',
432 - 'merge_tags' => Notifier_Notification_Merge_Tags::get_merge_tags( array('User', 'User Custom Meta') ),
433 - 'recipient_fields' => Notifier_Notification_Merge_Tags::get_recipient_fields( array('User Custom Meta') ),
152 + 'merge_tags' => Notifier_Notification_Merge_Tags::get_merge_tags( Notifier_Notification_Merge_Tags::get_user_merge_tag_types() ),
153 + 'recipient_fields' => Notifier_Notification_Merge_Tags::get_recipient_fields( Notifier_Notification_Merge_Tags::get_user_recipient_types() ),
434 154 'action' => array (
435 155 'hook' => 'user_register',
436 156 'callback' => function ( $user_id ) {
437 157 $args = array (
@@ -447,10 +167,10 @@
447 167 $triggers['WordPress'][] = array(
448 168 'id' => 'new_attachment',
449 169 'label' => 'New Media is uploded',
450 170 'description' => 'Trigger notification when a new attachement is uploded.',
451 - 'merge_tags' => Notifier_Notification_Merge_Tags::get_merge_tags( array('Attachment', 'Attachment Custom Meta') ),
452 - 'recipient_fields' => Notifier_Notification_Merge_Tags::get_recipient_fields( array('Attachment Custom Meta') ),
171 + 'merge_tags' => Notifier_Notification_Merge_Tags::get_merge_tags( Notifier_Notification_Merge_Tags::get_attachment_merge_tag_types() ),
172 + 'recipient_fields' => Notifier_Notification_Merge_Tags::get_recipient_fields( Notifier_Notification_Merge_Tags::get_attachment_recipient_types() ),
453 173 'action' => array (
454 174 'hook' => 'add_attachment',
455 175 'callback' => function ( $attachement_id ) {
456 176 $args = array (
@@ -489,20 +209,8 @@
489 209 return $site_key;
490 210 }
491 211
492 212 /**
493 - * Get notification triggers for dropdown
494 - */
495 - public static function get_notification_triggers_dropdown() {
496 - $main_triggers = self::get_notification_triggers();
497 - $dropdown_triggers = array('' => 'Select trigger');
498 - foreach ($main_triggers as $key => $triggers) {
499 - $dropdown_triggers[$key] = wp_list_pluck($triggers, 'label', 'id');
500 - }
501 - return $dropdown_triggers;
502 - }
503 -
504 - /**
505 213 * Get notification trigger
506 214 */
507 215 public static function get_notification_trigger($trigger) {
508 216 $trigger = self::get_trigger_id_with_site_key($trigger);
@@ -519,24 +227,8 @@
519 227 return $found_trigger;
520 228 }
521 229
522 230 /**
523 - * Get notification trigger display name
524 - */
525 - public static function get_notification_trigger_display_name($trigger){
526 - $main_triggers = self::get_notification_triggers();
527 - foreach ($main_triggers as $key => $triggers) {
528 - foreach($triggers as $the_trigger){
529 - if($the_trigger['id'] == $trigger){
530 - $name = $the_trigger['label'];
531 - break 2;
532 - }
533 - }
534 - }
535 - return $name;
536 - }
537 -
538 - /**
539 231 * Get enabled notification triggers
540 232 */
541 233 public static function get_enabled_notification_triggers() {
542 234 $enabled_triggers = array();
@@ -591,119 +283,178 @@
591 283 return $in_use;
592 284 }
593 285
594 286 /**
595 - * Get trigger post meta
287 + * Send triggered notifications.
288 + *
289 + * Runs both paths simultaneously during migration:
290 + * - v3 path: dispatches via Action Scheduler when the integration is connected.
291 + * - Legacy path: fires via old API key until migration is complete.
292 + *
293 + * @param string $trigger Trigger ID (with site key prefix).
294 + * @param array $context_args Context arguments passed to payload builders.
596 295 */
597 - public static function get_trigger_post_meta($trigger, $meta_key) {
598 - $meta_value = '';
599 - $trigger_ids = get_posts( array (
600 - 'post_type' => 'wa_notifier_trigger',
601 - 'post_status' => 'publish',
602 - 'numberposts' => -1,
603 - 'fields' => 'ids',
604 - 'meta_query' => array(
605 - 'relation' => 'OR',
606 - array(
607 - 'key' => NOTIFIER_PREFIX . 'trigger',
608 - 'value' => $trigger,
609 - 'compare' => '='
610 - ),
611 - array( // Backward compatibility
612 - 'key' => NOTIFIER_PREFIX . 'trigger',
613 - 'value' => self::get_trigger_id_without_site_key($trigger),
614 - 'compare' => '='
615 - )
616 - ),
617 - ) );
296 + public static function send_trigger_request( $trigger, $context_args ) {
297 + if ( empty( $context_args ) ) {
298 + return false;
299 + }
618 300
619 - if(!empty($trigger_ids)){
620 - $meta_value = get_post_meta($trigger_ids[0], $meta_key, true);
621 - }
301 + $slug = self::get_integration_slug_for_trigger( $trigger );
622 302
623 - return $meta_value;
624 - }
303 + error_log( sprintf( 'Notifier [send_trigger_request] trigger=%s slug=%s', $trigger, $slug ) );
625 304
626 - /**
627 - * Send triggered notifications
628 - */
629 - public static function send_trigger_request($trigger, $context_args) {
630 - if(empty($context_args)){
305 + // If the Woo plugin is active it owns all WooCommerce triggers — skip them here
306 + // so they are not double-fired. The Woo plugin registers its own action hooks.
307 + if ( 'woocommerce' === $slug && class_exists( 'WANOWC_Notification_Triggers' ) ) {
308 + error_log( 'Notifier [send_trigger_request] skipped — handed off to Woo plugin' );
631 309 return false;
632 310 }
633 311
634 - if('yes' === get_option('notifier_enable_async_triggers')){
635 - $option_name = 'notifier_'.notifier_generate_random_key(10);
636 - update_option( $option_name, $context_args );
312 + // v3 path: dispatch via Action Scheduler to SaaS integration endpoint.
313 + // Payload is built inside the async job to keep the main request fast.
314 + $is_connected = $slug && class_exists( 'Notifier_Connection' ) && Notifier_Connection::is_connected( $slug );
315 + error_log( sprintf( 'Notifier [send_trigger_request] is_connected=%s', $is_connected ? 'yes' : 'no' ) );
316 + if ( $is_connected ) {
317 + Notifier_Connection::dispatch_trigger( $slug, self::get_trigger_id_without_site_key( $trigger ), $context_args );
318 + }
637 319
638 - as_enqueue_async_action(
639 - 'notifier_send_trigger_request',
640 - array('trigger' => $trigger, 'option_name' => $option_name ),
641 - 'notifier'
642 - );
643 - }else {
644 - self::notifier_send_trigger_request($trigger, $context_args);
320 + // Legacy path: fire via old API key until migration is complete.
321 + $migration_complete = class_exists( 'Notifier_Migration' ) && Notifier_Migration::is_migration_complete();
322 + error_log( sprintf( 'Notifier [send_trigger_request] migration_complete=%s', $migration_complete ? 'yes' : 'no' ) );
323 + if ( ! $migration_complete ) {
324 + self::send_legacy_trigger_request( $trigger, $context_args );
645 325 }
646 326 }
647 327
648 328 /**
649 - * Send scheduled trigger request
650 -
329 + * Fire a trigger via the legacy /api/v1/fire_notification endpoint.
330 + * Used during the transition period before migration is complete.
331 + *
332 + * @param string $trigger Trigger ID (with site key prefix).
333 + * @param array $context_args Context arguments.
651 334 */
652 - public static function send_scheduled_trigger_request($trigger, $option_name) {
653 - if (is_array($option_name)){ // For backward compatibilty before 2.4.0
654 - $context_args = $option_name;
335 + private static function send_legacy_trigger_request( $trigger, $context_args ) {
336 + $api_key = trim( get_option( 'notifier_api_key', '' ) );
337 + if ( empty( $api_key ) ) {
338 + error_log( 'Notifier [send_legacy_trigger_request] skipped — no API key' );
339 + return;
655 340 }
656 - else {
657 - $context_args = get_option($option_name);
658 - }
659 341
660 - self::notifier_send_trigger_request($trigger, $context_args);
661 - }
342 + // Send trigger in v2 format (wp_{key}_{slug}) so Phase 1 SaaS can rewrite and match notifications.
343 + $trigger = self::get_trigger_id_with_site_key( $trigger );
662 344
663 - /**
664 - * Send async trigger request
665 - */
666 - public static function notifier_send_trigger_request($trigger, $context_args){
667 - $trigger_old = $trigger;
668 - $trigger = self::get_trigger_id_with_site_key($trigger);
345 + error_log( sprintf( 'Notifier [send_legacy_trigger_request] trigger=%s', $trigger ) );
669 346
670 - $merge_tags = self::get_trigger_post_meta($trigger, NOTIFIER_PREFIX . 'data_fields');
671 - $recipient_fields = self::get_trigger_post_meta($trigger, NOTIFIER_PREFIX . 'recipient_fields');
347 + // Look up the v2 CPT post for this trigger to get the user-selected fields.
348 + // This mirrors exactly what v2 sent — only the fields the user configured.
349 + $cpt_post = get_posts( array(
350 + 'post_type' => 'wa_notifier_trigger',
351 + 'post_status' => 'publish',
352 + 'numberposts' => 1,
353 + 'fields' => 'ids',
354 + 'meta_query' => array(
355 + array(
356 + 'key' => NOTIFIER_PREFIX . 'trigger',
357 + 'value' => $trigger,
358 + ),
359 + ),
360 + ) );
672 361
673 - $data = array();
674 - $recipient_data = array();
675 - Notifier_Tools::insert_activity_log('debug','Triggering '. $trigger.' with arguments: '.json_encode($context_args));
362 + // If no CPT post exists for this trigger, the user never configured it in v2 — don't send.
363 + if ( empty( $cpt_post ) ) {
364 + error_log( sprintf( 'Notifier [send_legacy_trigger_request] skipped — no CPT post for trigger=%s', $trigger ) );
365 + return;
366 + }
676 367
677 - if(!empty($merge_tags)){
678 - foreach($merge_tags as $tag){
679 - $data[$tag] = Notifier_Notification_Merge_Tags::get_trigger_merge_tag_value($tag, $context_args);
680 - }
368 + $post_id = $cpt_post[0];
369 + $enabled_merge_tags = get_post_meta( $post_id, NOTIFIER_PREFIX . 'data_fields', true );
370 + $enabled_recipient_fields = get_post_meta( $post_id, NOTIFIER_PREFIX . 'recipient_fields', true );
371 + $enabled_merge_tags = is_array( $enabled_merge_tags ) ? $enabled_merge_tags : array();
372 + $enabled_recipient_fields = is_array( $enabled_recipient_fields ) ? $enabled_recipient_fields : array();
373 +
374 + // Resolve only the selected field values.
375 + $merge_tags_data = array();
376 + $recipient_fields = array();
377 + foreach ( $enabled_merge_tags as $tag_id ) {
378 + $merge_tags_data[ $tag_id ] = Notifier_Notification_Merge_Tags::get_trigger_merge_tag_value( $tag_id, $context_args );
681 379 }
380 + foreach ( $enabled_recipient_fields as $field_id ) {
381 + $recipient_fields[ $field_id ] = Notifier_Notification_Merge_Tags::get_trigger_merge_tag_value( $field_id, $context_args );
382 + }
682 383
683 - if(!empty($recipient_fields)){
684 - foreach($recipient_fields as $field){
685 - $recipient_data[$field] = Notifier_Notification_Merge_Tags::get_trigger_recipient_field_value($field, $context_args);
686 - }
384 + error_log( sprintf( 'Notifier [send_legacy_trigger_request] recipient_fields before=%s', wp_json_encode( $recipient_fields ) ) );
385 + foreach ( $recipient_fields as $key => $value ) {
386 + $recipient_fields[ $key ] = notifier_maybe_add_default_country_code( notifier_sanitize_phone_number( (string) $value ) );
687 387 }
388 + error_log( sprintf( 'Notifier [send_legacy_trigger_request] recipient_fields after=%s', wp_json_encode( $recipient_fields ) ) );
688 389
689 - $params = array(
690 - 'site_url' => site_url(),
691 - 'source' => 'wp',
692 - 'trigger' => $trigger,
693 - 'trigger_old' => $trigger_old,
694 - 'merge_tags_data' => $data,
695 - 'recipient_fields' => $recipient_data
696 - );
390 + $body = wp_json_encode( array(
391 + 'source' => 'wp',
392 + 'trigger' => $trigger,
393 + 'merge_tags_data' => $merge_tags_data,
394 + 'recipient_fields' => $recipient_fields,
395 + 'site_url' => site_url(),
396 + ) );
697 397
698 - Notifier_Tools::insert_activity_log('debug','Sending API request for ' . $trigger . '. Request params: '.json_encode($params));
699 - $response = Notifier::send_api_request( 'fire_notification', $params, 'POST' );
700 - Notifier_Tools::insert_activity_log('debug','API response for '. $trigger . ': ' . $response->data);
398 + error_log( 'Notifier: ' . $body );
701 399
702 - if($response->error){
703 - error_log($response->message);
704 - Notifier_Tools::insert_activity_log('debug', 'API response for '. $trigger . ': ' . $response->message);
400 + $url = untrailingslashit( NOTIFIER_APP_BASE_URL ) . '/' . NOTIFIER_APP_API_PATH . '/fire_notification?key=' . rawurlencode( $api_key );
401 +
402 + wp_remote_post( $url, array(
403 + 'headers' => array( 'Content-Type' => 'application/json; charset=utf-8' ),
404 + 'body' => $body,
405 + 'timeout' => 30,
406 + 'sslverify' => true,
407 + ) );
408 + }
409 +
410 + /**
411 + * Determine the integration slug for a given trigger ID.
412 + *
413 + * @param string $trigger Trigger ID (with or without site key prefix).
414 + * @return string|null Integration slug or null if not determinable.
415 + */
416 + public static function get_integration_slug_for_trigger( $trigger ) {
417 + $base = self::get_trigger_id_without_site_key( $trigger );
418 +
419 + // woocommerce_cart_abandonment_recovery falls through to the woocommerce_ prefix check below.
420 + if ( strpos( $base, 'woo_' ) === 0 || strpos( $base, 'woocommerce_' ) === 0 ) {
421 + return 'woocommerce';
705 422 }
423 + if ( strpos( $base, 'gravityforms_' ) === 0 ) {
424 + return 'gravityforms';
425 + }
426 + if ( strpos( $base, 'cf7_' ) === 0 ) {
427 + return 'cf7';
428 + }
429 + if ( strpos( $base, 'wpf_' ) === 0 || strpos( $base, 'wpforms_' ) === 0 ) {
430 + return 'wpforms';
431 + }
432 + if ( strpos( $base, 'ninjaforms_' ) === 0 ) {
433 + return 'ninjaforms';
434 + }
435 + if ( strpos( $base, 'formidableforms_' ) === 0 || strpos( $base, 'formidable_' ) === 0 ) {
436 + return 'formidable';
437 + }
438 + if ( strpos( $base, 'fluentforms_' ) === 0 ) {
439 + return 'fluentforms';
440 + }
441 + if ( strpos( $base, 'forminator_' ) === 0 ) {
442 + return 'forminator';
443 + }
444 + if ( strpos( $base, 'sureforms_' ) === 0 ) {
445 + return 'sureforms';
446 + }
447 + if ( strpos( $base, 'wsform_' ) === 0 ) {
448 + return 'wsform';
449 + }
450 + // WordPress core triggers (new_post, new_user, new_comment, new_attachment, etc.)
451 + if ( in_array( $base, array( 'new_comment', 'new_user', 'new_attachment' ), true )
452 + || strpos( $base, 'new_' ) === 0 ) {
453 + return 'wordpress';
454 + }
455 +
456 + return null;
706 457 }
707 458
708 459 /**
709 460 * Enable / disable trigger
@@ -708,10 +459,25 @@
708 459 /**
709 460 * Enable / disable trigger
710 461 */
711 462 public static function notifier_change_trigger_status(){
712 - $post_id = isset($_POST['post_id']) ? $_POST['post_id'] : 0;
713 - $enabled = isset($_POST['enabled']) ? $_POST['enabled'] : 'no';
463 + // Security check: Verify user has manage_options capability
464 + if ( ! current_user_can( 'manage_options' ) ) {
465 + wp_send_json_error( array(
466 + 'message' => 'You do not have permission to perform this action.'
467 + ) );
468 + }
469 +
470 + // Verify nonce for additional security
471 + if ( ! wp_verify_nonce( $_POST['_wpnonce'] ?? '', 'notifier_change_trigger_status' ) ) {
472 + wp_send_json_error( array(
473 + 'message' => 'Security check failed.'
474 + ) );
475 + }
476 +
477 + $post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0;
478 + $enabled = isset($_POST['enabled']) ? sanitize_text_field($_POST['enabled']) : 'no';
479 +
714 480 if('wa_notifier_trigger' != get_post_type($post_id)){
715 481 wp_send_json( array(
716 482 'error' => true,
717 483 'message' => 'Invalid post type.'
@@ -716,8 +482,9 @@
716 482 'error' => true,
717 483 'message' => 'Invalid post type.'
718 484 ) );
719 485 }
486 +
720 487 update_post_meta( $post_id, NOTIFIER_PREFIX . 'trigger_enabled' , $enabled);
721 488 wp_send_json( array(
722 489 'error' => false,
723 490 'message' => 'Updated'
@@ -727,10 +494,24 @@
727 494 /**
728 495 * Fetch trigger fields for a speicific trigger
729 496 */
730 497 public static function notifier_fetch_trigger_fields(){
731 - $post_id = isset($_POST['post_id']) ? $_POST['post_id'] : 0;
732 - $trigger = isset($_POST['trigger']) ? $_POST['trigger'] : '';
498 + // Security check: Verify user has manage_options capability
499 + if ( ! current_user_can( 'manage_options' ) ) {
500 + wp_send_json_error( array(
501 + 'message' => 'You do not have permission to perform this action.'
502 + ) );
503 + }
504 +
505 + // Verify nonce for additional security
506 + if ( ! wp_verify_nonce( $_POST['_wpnonce'] ?? '', 'notifier_fetch_trigger_fields' ) ) {
507 + wp_send_json_error( array(
508 + 'message' => 'Security check failed.'
509 + ) );
510 + }
511 +
512 + $post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0;
513 + $trigger = isset($_POST['trigger']) ? sanitize_text_field($_POST['trigger']) : '';
733 514
734 515 $data_fields = get_post_meta( $post_id, NOTIFIER_PREFIX . 'data_fields', true);
735 516 $recipient_fields = get_post_meta( $post_id, NOTIFIER_PREFIX . 'recipient_fields', true);
736 517