PluginProbe
Boxzilla – WordPress Popup Builder / 3.4.11
Boxzilla – WordPress Popup Builder v3.4.11
3.4.11 3.4.10 3.4.9 3.4.3 3.4.4 3.4.5 3.4.6 3.4.7 3.4.8 trunk 3.0 3.0.1 3.0.2 3.0.3 3.1 3.1.1 3.1.10 3.1.11 3.1.12 3.1.13 3.1.14 3.1.15 3.1.16 3.1.17 3.1.18 All 72 releases
boxzilla / src / class-loader.php

class-loader.php in Boxzilla – WordPress Popup Builder 3.4.11, at src/class-loader.php

352 lines 10.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Boxzilla;
4
5 if (! defined('ABSPATH')) {
6 exit;
7 }
8
9 class BoxLoader
10 {
11 /**
12 * @var Plugin
13 */
14 private $plugin;
15
16 /**
17 * @var array
18 */
19 private $box_ids_to_load = [];
20
21 /**
22 * @var array
23 */
24 protected $options;
25
26 /**
27 * Constructor
28 *
29 * @param Plugin $plugin
30 * @param array $options
31 */
32 public function __construct(Plugin $plugin, array $options)
33 {
34 $this->plugin = $plugin;
35 $this->options = $options;
36 }
37
38 /**
39 * Initializes the plugin, runs on `wp` hook.
40 */
41 public function init()
42 {
43 $this->box_ids_to_load = $this->filter_boxes();
44
45 // Only add other hooks if necessary
46 add_action('wp_head', [$this, 'print_preload_js'], 10, 0);
47 if (count($this->box_ids_to_load) > 0) {
48 add_action('wp_footer', [ $this, 'print_boxes_content' ], 1);
49 add_action('wp_enqueue_scripts', [ $this, 'load_assets' ], 90);
50 }
51 }
52
53 /**
54 * Prints the preload API so that the Boxzilla JS API can be used before Boxzilla itself is loaded
55 * This allows us to defer the Boxzilla script itself.
56 */
57 public function print_preload_js(): void
58 {
59 echo '<script>';
60 include BOXZILLA_DIR . '/assets/js/preload.js';
61 echo '</script>';
62 }
63
64 /**
65 * Get global rules for all boxes
66 *
67 * @return array
68 */
69 protected function get_filter_rules(): array
70 {
71 $rules = get_option('boxzilla_rules', []);
72 return is_array($rules) ? $rules : [];
73 }
74
75
76 /**
77 * Match a string against an array of patterns, glob-style.
78 *
79 * @param string $string
80 * @param array $patterns
81 * @param bool $contains
82 * @return bool
83 */
84 protected function match_patterns($string, array $patterns, $contains = false)
85 {
86 $string = strtolower($string);
87
88 foreach ($patterns as $pattern) {
89 $pattern = rtrim($pattern, '/');
90 $pattern = strtolower($pattern);
91
92 if ($contains) {
93 // contains means we should do a simple occurrence check
94 // does not support wildcards
95 $match = strpos($string, $pattern) !== false;
96 } elseif (function_exists('fnmatch')) {
97 $match = fnmatch($pattern, $string);
98 } else {
99 $match = ( $pattern === $string );
100 }
101
102 if ($match) {
103 return true;
104 }
105 }
106
107 return false;
108 }
109
110 /**
111 * @return string
112 */
113 protected function get_request_url()
114 {
115 return rtrim(wp_unslash($_SERVER['REQUEST_URI'] ?? ''), '/');
116 }
117
118 /**
119 * Check if this rule passes (conditional matches expected value)
120 *
121 * @param string $condition
122 * @param string $value
123 * @param boolean $qualifier
124 *
125 * @return bool
126 */
127 protected function match_rule($condition, $value, $qualifier = true)
128 {
129 $matched = false;
130
131 // cast value to array & trim whitespace or excess comma's
132 $values = array_map('trim', explode(',', rtrim(trim($value), ',')));
133
134 switch ($condition) {
135 case 'everywhere':
136 $matched = true;
137 break;
138
139 case 'is_url':
140 $url = $this->get_request_url();
141 $matched = $this->match_patterns($url, $values, $qualifier === 'contains' || $qualifier === 'not_contains');
142 break;
143
144 case 'is_referer':
145 if (! empty($_SERVER['HTTP_REFERER'])) {
146 $referer = wp_unslash($_SERVER['HTTP_REFERER']);
147 $matched = $this->match_patterns($referer, $values, $qualifier === 'contains' || $qualifier === 'not_contains');
148 }
149 break;
150
151 case 'is_post_type':
152 $post_type = (string) get_post_type();
153 $matched = in_array($post_type, $values, true);
154 break;
155
156 case 'is_single':
157 case 'is_post':
158 // convert to empty string if array with just empty string in it
159 $value = ( $values === [ '' ] ) ? '' : $values;
160 $matched = is_single($value);
161 break;
162
163 case 'is_post_in_category':
164 $matched = is_singular() && has_category($values);
165 break;
166
167 case 'is_page':
168 $matched = is_page($values);
169 break;
170
171 case 'is_post_with_tag':
172 $matched = is_singular() && has_tag($values);
173 break;
174
175 case 'is_user_logged_in':
176 $matched = is_user_logged_in();
177 break;
178 }
179
180 /**
181 * Filters whether a given box rule matches the condition and expected value.
182 *
183 * The dynamic portion of the hook, `$condition`, refers to the condition being matched.
184 *
185 * @param boolean $matched
186 * @param array $values
187 */
188 $matched = apply_filters('boxzilla_box_rule_matches_' . $condition, $matched, $values);
189
190 // if qualifier is set to false, we need to reverse this value here.
191 if (! $qualifier || $qualifier === 'not_contains') {
192 $matched = ! $matched;
193 }
194
195 return $matched;
196 }
197
198 /**
199 * Checks which boxes should be loaded for this request.
200 *
201 * @return array
202 */
203 private function filter_boxes()
204 {
205 $box_ids_to_load = [];
206 $rules = $this->get_filter_rules();
207
208 foreach ($rules as $box_id => $box_rules) {
209 $matched = false;
210 $comparision = isset($box_rules['comparision']) ? $box_rules['comparision'] : 'any';
211
212 // loop through all rules for all boxes
213 foreach ($box_rules as $rule) {
214 // skip faulty values (and comparision rule)
215 if (! is_array($rule) || empty($rule['condition'])) {
216 continue;
217 }
218
219 $qualifier = isset($rule['qualifier']) ? $rule['qualifier'] : true;
220 $matched = $this->match_rule($rule['condition'], $rule['value'], $qualifier);
221
222 // break out of loop if we've already matched
223 if ($comparision === 'any' && $matched) {
224 break;
225 }
226
227 // no need to continue if this rule didn't match
228 if ($comparision === 'all' && ! $matched) {
229 break;
230 }
231 }
232
233 // value of $matched at this point determines whether box should be loaded
234 $load_box = $matched;
235
236 /**
237 * Filters whether a box should be loaded into the page HTML.
238 *
239 * The dynamic portion of the hook, `$box_id`, refers to the ID of the box. Return true if you want to output the box.
240 *
241 * @param boolean $load_box
242 */
243 $load_box = apply_filters('boxzilla_load_box_' . $box_id, $load_box);
244
245 /**
246 * Filters whether a box should be loaded into the page HTML.
247 *
248 * @param boolean $load_box
249 * @param int $box_id
250 */
251 $load_box = apply_filters('boxzilla_load_box', $load_box, $box_id);
252
253 // if matched, box should be loaded on this page
254 if ($load_box) {
255 $box_ids_to_load[] = $box_id;
256 }
257 }
258
259 /**
260 * Filters which boxes should be loaded on this page, expects an array of post ID's.
261 *
262 * @param array $box_ids_to_load
263 */
264 return apply_filters('boxzilla_load_boxes', $box_ids_to_load);
265 }
266
267 /**
268 * Load plugin styles
269 */
270 public function load_assets()
271 {
272 wp_enqueue_style('boxzilla', $this->plugin->url('/assets/css/styles.css'), [], $this->plugin->version());
273 wp_enqueue_script('boxzilla', $this->plugin->url('/assets/js/script.js'), [], $this->plugin->version(), [
274 'strategy' => 'defer',
275 ]);
276
277 // create boxzilla_Global_Options object
278 $plugin_options = $this->options;
279 $boxes = $this->get_matched_boxes();
280
281 $data = [
282 'testMode' => (bool) $plugin_options['test_mode'],
283 'boxes' => (array) array_map(
284 function (Box $box) {
285 return $box->get_client_options();
286 },
287 $boxes
288 ),
289 ];
290 wp_add_inline_script('boxzilla', 'var boxzilla_options = ' . wp_json_encode($data) . ';', 'before');
291 do_action('boxzilla_load_assets', $this);
292 }
293
294 public function print_boxes_content()
295 {
296 $boxes = $this->get_matched_boxes();
297 if (empty($boxes)) {
298 return;
299 }
300
301 echo '<div style="display: none;">';
302 foreach ($boxes as $box) {
303 printf('<div id="boxzilla-box-%d-content">%s</div>', absint($box->ID), $box->get_content()); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
304 }
305 echo '</div>';
306 }
307
308 /**
309 * Get an array of Box objects. These are the boxes that will be loaded for the current request.
310 *
311 * @return Box[]
312 */
313 public function get_matched_boxes()
314 {
315 static $boxes;
316
317 if ($boxes === null) {
318 if (count($this->box_ids_to_load) === 0) {
319 return [];
320 }
321
322 // query Box posts
323 $q = new \WP_Query();
324 $posts = $q->query(
325 [
326 'post_type' => 'boxzilla-box',
327 'post_status' => 'publish',
328 'post__in' => $this->box_ids_to_load,
329 'posts_per_page' => count($this->box_ids_to_load),
330 'ignore_sticky_posts' => true,
331 'no_found_rows' => true,
332 ]
333 );
334
335 // create `Box` instances out of \WP_Post instances
336 $boxes = [];
337 foreach ($posts as $key => $post) {
338 $box = new Box($post);
339
340 // skip boxes without any content
341 if ($box->get_content() === '') {
342 continue;
343 }
344
345 $boxes[ $key ] = $box;
346 }
347 }
348
349 return $boxes;
350 }
351 }
352