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.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 1.2.2 All 155 releases
notificationx / includes / Core / Targeting.php

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

222 lines 8.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Targeting
4 *
5 * Country- and user-role-based targeting for ALL notification types.
6 *
7 * Historically these two fields lived inside the PressBar extension and were
8 * enforced inside the NotificationBar type, so targeting only worked for the
9 * Notification Bar and only while the bar module was enabled. This class
10 * decouples the feature from the bar: it registers the fields on the shared
11 * `nx_customize_fields` schema (so every type exposes them) and enforces them
12 * on the shared `nx_show_on_exclude` filter (applied for every notification in
13 * FrontEnd::get_notifications_ids()), regardless of which modules are active.
14 *
15 * @package NotificationX\Core
16 */
17
18 namespace NotificationX\Core;
19
20 use NotificationX\Admin\InfoTooltipManager;
21 use NotificationX\Extensions\GlobalFields;
22 use NotificationX\GetInstance;
23
24 /**
25 * @method static Targeting get_instance($args = null)
26 */
27 class Targeting {
28 /**
29 * Instance of Targeting
30 *
31 * @var Targeting
32 */
33 use GetInstance;
34
35 /**
36 * Initially Invoked when initialized.
37 */
38 public function __construct() {
39 add_filter('nx_customize_fields', [$this, 'add_targeting_fields'], 20);
40 add_filter('nx_show_on_exclude', [$this, 'show_on_exclude'], 10, 2);
41 }
42
43 /**
44 * Get all WordPress roles dynamically.
45 *
46 * @return array
47 */
48 public function get_roles() {
49 return wp_roles()->role_names;
50 }
51
52 /**
53 * Inject the "Targeting" section (country + user role) into the shared
54 * field schema so it appears for every notification type.
55 *
56 * @param array $fields Field schema.
57 * @return array
58 */
59 public function add_targeting_fields($fields) {
60 // Safety: if some extension already registered the section, don't duplicate.
61 if (isset($fields['targeting'])) {
62 return $fields;
63 }
64
65 $wp_roles = $this->get_roles();
66
67 // Show the Targeting section for every notification type. The two
68 // "special" sources (GDPR + Exit Intent) are excluded, mirroring the
69 // core `appearance` section rule — a section without any rule is not
70 // rendered by the QuickBuilder form engine.
71 $targeting_rules = Rules::logicalRule([
72 Rules::is('source', 'gdpr_notification', true),
73 Rules::is('source', 'exit_intent_custom', true),
74 ], 'and');
75
76 $fields['targeting'] = [
77 'label' => __('Targeting', 'notificationx'),
78 'name' => 'targeting',
79 'type' => 'section',
80 'id' => 'targeting',
81 'classes' => 'nx-targeting',
82 'priority' => 100,
83 'info' => InfoTooltipManager::get_instance()->render('targeting'),
84 'rules' => $targeting_rules,
85 'fields' => [],
86 ];
87
88 // Country Targeting
89 $fields['targeting']['fields']['country_targeting'] = [
90 'label' => __('Country Targeting', 'notificationx'),
91 'name' => 'country_targeting',
92 'type' => 'better-select',
93 'priority' => 10,
94 'is_pro' => true,
95 'multiple' => true,
96 'values' => ['label' => "All Country", 'value' => 'all'],
97 'option' => GlobalFields::get_instance()->normalize_fields(Helper::nx_get_all_country()),
98 'ajax' => [
99 'api' => "/notificationx/v1/get-data",
100 'data' => [
101 'type' => "@type",
102 'source' => "@source",
103 'field' => "country_targeting",
104 ],
105 ],
106 'info' => InfoTooltipManager::get_instance()->render('targeting'),
107 ];
108
109 // User Role Targeting. Includes a "guest" option so logged-out visitors
110 // can be TARGETED (not just excluded) — the enforcement in
111 // show_on_exclude() already honors 'guest', it was just never selectable.
112 $wp_roles_with_default = array_merge(
113 [
114 'all_users' => __('Show for All Users', 'notificationx'),
115 'guest' => __('Logged-out Visitors (Guests)', 'notificationx'),
116 ],
117 is_array($wp_roles) ? $wp_roles : []
118 );
119 $fields['targeting']['fields']['targeting_user_roles'] = [
120 'label' => __('Set Target Audience', 'notificationx'),
121 'name' => 'targeting_user_roles',
122 'type' => 'select',
123 'priority' => 20,
124 'is_pro' => true,
125 'default' => ['all_users'],
126 'options' => GlobalFields::get_instance()->normalize_fields($wp_roles_with_default),
127 'multiple' => true,
128 'info' => InfoTooltipManager::get_instance()->render('targeting'),
129 ];
130
131 return $fields;
132 }
133
134 /**
135 * Enforce country- and user-role-based targeting for every notification.
136 *
137 * Runs on `nx_show_on_exclude` (applied in FrontEnd::get_notifications_ids()
138 * for all sources), so it works for any notification type that has these
139 * settings, not just the Notification Bar.
140 *
141 * @param bool $exclude Current exclusion status.
142 * @param array $settings Notification settings.
143 * @return bool True if the notification should be excluded.
144 */
145 public function show_on_exclude($exclude, $settings) {
146 // Already excluded by an earlier handler — nothing to add.
147 if ($exclude) {
148 return true;
149 }
150
151 // 1. Country Targeting
152 if (!empty($settings['country_targeting']) && is_array($settings['country_targeting'])) {
153 // The better-select stores each selection as a {value,label} object;
154 // fall back to plain strings for legacy/imported settings.
155 $countryValues = array_column($settings['country_targeting'], 'value');
156 if (empty($countryValues)) {
157 $countryValues = array_filter($settings['country_targeting'], 'is_string');
158 }
159 $normalizedCountries = array_map('strtoupper', $countryValues);
160
161 // Only enforce when a specific country list is set — 'ALL' means every
162 // country, so there is nothing to filter.
163 if (!in_array('ALL', $normalizedCountries, true)) {
164 $visitor_country = strtoupper((string) Helper::nx_get_visitor_country_code());
165
166 // Fail open: if the visitor's country can't be resolved (geo lookup
167 // failed/rate-limited, CLI, or local testing) do NOT hide the
168 // notification. Silently hiding a country-targeted notification for
169 // everyone during an API outage is worse than showing it slightly
170 // wider, so an unknown country simply skips country filtering.
171 if ('' !== $visitor_country && !in_array($visitor_country, $normalizedCountries, true)) {
172 return true;
173 }
174 }
175 }
176
177 // 2. User Role Targeting
178 if (!empty($settings['targeting_user_roles']) &&
179 !in_array('all_users', $settings['targeting_user_roles'])) {
180
181 // For logged out users
182 if (!is_user_logged_in() && !in_array('guest', $settings['targeting_user_roles'])) {
183 return true;
184 }
185
186 // For logged in users
187 if (is_user_logged_in()) {
188 $user = wp_get_current_user();
189 $user_roles = (array) $user->roles;
190
191 $has_targeted_role = false;
192 foreach ($user_roles as $role) {
193 if (in_array($role, $settings['targeting_user_roles'])) {
194 $has_targeted_role = true;
195 break;
196 }
197 }
198
199 if (!$has_targeted_role) {
200 return true;
201 }
202 }
203 }
204
205 return $exclude;
206 }
207
208 /**
209 * REST handler that powers the country-search AJAX for the
210 * country_targeting field (routed by field, for every type).
211 *
212 * @param array $params Request params.
213 * @return array
214 */
215 public static function restResponse($params) {
216 if (empty($params['inputValue'])) {
217 return [];
218 }
219 return array_values(GlobalFields::get_instance()->normalize_fields(Helper::nx_get_all_country($params['inputValue'])));
220 }
221 }
222