PluginProbe
FireBox – WooCommerce Popup Builder, Exit Intent Popup, Email Optin & Cart Abandonment / 2.1.23
FireBox – WooCommerce Popup Builder, Exit Intent Popup, Email Optin & Cart Abandonment v2.1.23
3.1.13 3.1.12 3.1.11 3.1.10 3.1.9 3.1.8 3.1.7 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 All 122 releases
← All changes | Inc/Core/Helpers/BoxHelper.php +93 -435 trunk2.1.23 View file →
@@ -1,12 +1,12 @@
1 1 <?php
2 2 /**
3 3 * @package FireBox
4 - * @version 3.1.13 Free
4 + * @version 2.1.23 Free
5 5 *
6 6 * @author FirePlugins <info@fireplugins.com>
7 7 * @link https://www.fireplugins.com
8 - * @copyright Copyright © 2026 FirePlugins All Rights Reserved
8 + * @copyright Copyright © 2024 FirePlugins All Rights Reserved
9 9 * @license GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
10 10 */
11 11
12 12 namespace FireBox\Core\Helpers;
@@ -20,405 +20,56 @@
20 20
21 21 class BoxHelper
22 22 {
23 23 /**
24 - * Option holding the cached campaign lists, keyed by status/limit.
25 - *
26 - * An autoloaded option rather than a transient on purpose: a transient with an expiry is
27 - * stored non-autoloaded, so reading it costs 2 queries per request on stock WordPress —
28 - * exactly what the campaign query it replaces costs. Autoloaded means WordPress has
29 - * already loaded it by the time Boxes::render() runs. There is no TTL either: the option
30 - * is deleted on every campaign write, so the cache is always current.
31 - *
32 - * @var string
33 - */
34 - const BOXES_CACHE_OPTION = 'firebox_boxes_cache';
35 -
36 - /**
37 - * Returns the prefix every FireBox cookie name is built from.
38 - *
39 - * Cookies are set with path=/, so on a subdirectory network every site of
40 - * example.com/store, example.com/blog … shares one cookie namespace. Post
41 - * IDs restart per site, so campaign 42 exists on both and one campaign's
42 - * "already seen" cookie would suppress an unrelated campaign on another
43 - * site. The blog ID in the name is what keeps them apart.
44 - *
45 - * Single sites — and the main site of a network, which owns the unprefixed
46 - * namespace already — keep the original prefix, so upgrading does not show
47 - * a campaign again to visitors who had dismissed it.
48 - *
49 - * @return string
50 - */
51 - public static function cookiePrefix()
52 - {
53 - if (!is_multisite() || is_main_site())
54 - {
55 - return 'firebox_';
56 - }
57 -
58 - return 'firebox_' . get_current_blog_id() . '_';
59 - }
60 -
61 - /**
62 - * The path FireBox cookies are written to.
63 - *
64 - * WordPress derives COOKIEPATH from the site's home URL, so it already
65 - * points at exactly the part of the domain this site occupies — the whole
66 - * domain for a normal install, /store/ for that site of a subdirectory
67 - * network. It is what WooCommerce and AffiliateWP scope their cookies with,
68 - * and it keeps a network's sites from writing over each other.
69 - *
70 - * @return string
71 - */
72 - public static function cookiePath()
73 - {
74 - return \FPFramework\Helpers\CookieScope::path();
75 - }
76 -
77 - /**
78 - * The domain FireBox cookies are written to.
79 - *
80 - * Empty unless the site defines COOKIE_DOMAIN, which is how a site tells
81 - * WordPress its cookies belong to something other than the current host —
82 - * shared between www and non-www, or across the subdomains of a network.
83 - * Ignoring it means writing a cookie the site cannot read back.
84 - *
85 - * @return string
86 - */
87 - public static function cookieDomain()
88 - {
89 - return \FPFramework\Helpers\CookieScope::domain();
90 - }
91 -
92 - /**
93 - * Paths a cookie of ours may have been written to before.
94 - *
95 - * Cookies are keyed by name *and* path, so a cookie written at '/' is a
96 - * different cookie from the same name at '/store/'. FireBox wrote every
97 - * cookie to '/' until it started honouring COOKIEPATH, and expiring only
98 - * the new path would leave the old one behind — still sent, still
99 - * suppressing the campaign it belongs to.
100 - *
101 - * @return array
102 - */
103 - public static function cookiePathsToClear()
104 - {
105 - return \FPFramework\Helpers\CookieScope::pathsToClear();
106 - }
107 -
108 - /**
109 24 * Get box meta
110 - *
25 + *
111 26 * @param int $id
112 - *
27 + *
113 28 * @return array
114 29 */
115 30 public static function getMeta($id)
116 31 {
117 - $meta = get_post_meta($id, 'firebox_meta', false);
118 - $meta = isset($meta[0]) && is_array($meta[0]) ? $meta[0] : [];
119 -
120 - return $meta;
32 + return get_post_meta($id, 'fpframework_meta_settings', true);
121 33 }
122 34
123 35 /**
124 - * Attaches inline JavaScript that runs once the FireBox runtime has created all
125 - * campaign instances.
126 - *
127 - * The script is attached in the "before" position on purpose: WordPress downgrades a
128 - * defer-strategy script to a blocking one as soon as it carries an "after" inline
129 - * script, so a single campaign with custom code would make the whole runtime
130 - * parse-blocking. Printing before the runtime keeps defer intact.
131 - *
132 - * Waiting for DOMContentLoaded is what makes that safe: firebox-main is deferred, so
133 - * the browser executes it once parsing is done — at which point FireBox.onReady()
134 - * runs immediately (readyState is already "interactive") and builds every campaign
135 - * instance. DOMContentLoaded fires after all deferred scripts, so by the time this
136 - * code runs the instances exist.
137 - *
138 - * @param string $js
139 - *
140 - * @return void
141 - */
142 - public static function addInlineScript($js)
143 - {
144 - if (!is_string($js) || trim($js) === '')
145 - {
146 - return;
147 - }
148 -
149 - $wrapped = 'document.addEventListener("DOMContentLoaded", function(){' . $js . '});';
150 -
151 - wp_add_inline_script('firebox-main', $wrapped, 'before');
152 - }
153 -
154 - /**
155 - * Whether any of the given campaigns uses session-dependent behavior:
156 - * the Pageviews/Time on Site display conditions, or an impressions
157 - * limit scoped to the visitor's session (fpfsid).
158 - *
159 - * These are stamped and evaluated against JS-set session cookies, so
160 - * when in use the frontend runtime must be enqueued on every page.
161 - * Skipped entirely when no campaign needs them, so other sites load no
162 - * extra JS or cookies.
163 - *
164 - * @param \WP_Query $boxes
165 - *
166 - * @return bool
167 - */
168 - public static function sessionConditionsInUse($boxes)
169 - {
170 - // The answer only changes when a campaign is written to, so keep it in an
171 - // autoloaded option instead of re-scanning every campaign's meta per page view.
172 - $cached = get_option('firebox_session_conditions_in_use');
173 -
174 - if ($cached === 'yes' || $cached === 'no')
175 - {
176 - return $cached === 'yes';
177 - }
178 -
179 - $inUse = self::computeSessionConditionsInUse($boxes);
180 -
181 - update_option('firebox_session_conditions_in_use', $inUse ? 'yes' : 'no', true);
182 -
183 - return $inUse;
184 - }
185 -
186 - /**
187 - * Drops the cached session-conditions flag so it is recomputed on the next
188 - * frontend request. Hooked on every write path that can change the answer.
189 - *
190 - * @return void
191 - */
192 - public static function invalidateSessionConditionsFlag()
193 - {
194 - delete_option('firebox_session_conditions_in_use');
195 - }
196 -
197 - /**
198 - * Drops every campaign-derived cache: the session-conditions flag and the
199 - * cached box list. Hooked on each write path that can change either answer.
200 - *
201 - * @return void
202 - */
203 - public static function invalidateCaches()
204 - {
205 - self::invalidateSessionConditionsFlag();
206 - self::invalidateBoxesCache();
207 - }
208 -
209 - /**
210 - * Registers the invalidation hooks for the campaign-derived caches.
211 - *
212 - * @return void
213 - */
214 - public static function registerCacheInvalidation()
215 - {
216 - add_action('save_post_firebox', [self::class, 'invalidateCaches']);
217 -
218 - // These fire for every post type, so only react to campaigns — otherwise
219 - // ordinary content edits would keep busting the campaign caches.
220 - $onPostChange = function($post_id, $post = null)
221 - {
222 - if (self::isCampaign($post_id, $post))
223 - {
224 - self::invalidateCaches();
225 - }
226 - };
227 -
228 - add_action('deleted_post', $onPostChange, 10, 2);
229 - add_action('trashed_post', $onPostChange, 10, 1);
230 - add_action('untrashed_post', $onPostChange, 10, 1);
231 -
232 - // firebox_meta is also written outside the save_post flow (Gutenberg REST meta
233 - // updates, the campaign importer, duplication).
234 - $onMetaWrite = function($meta_id, $post_id, $meta_key)
235 - {
236 - if ($meta_key === 'firebox_meta')
237 - {
238 - self::invalidateCaches();
239 - }
240 - };
241 -
242 - add_action('added_postmeta', $onMetaWrite, 10, 3);
243 - add_action('updated_postmeta', $onMetaWrite, 10, 3);
244 - }
245 -
246 - /**
247 - * Whether the given post is a campaign. `deleted_post` passes the post object
248 - * along because the row is already gone by the time it fires.
249 - *
250 - * @param int $post_id
251 - * @param \WP_Post $post
252 - *
253 - * @return bool
254 - */
255 - private static function isCampaign($post_id, $post = null)
256 - {
257 - if ($post instanceof \WP_Post)
258 - {
259 - return $post->post_type === 'firebox';
260 - }
261 -
262 - return get_post_type($post_id) === 'firebox';
263 - }
264 -
265 - /**
266 - * Scans the given campaigns for session-dependent behavior.
267 - *
268 - * @param \WP_Query $boxes
269 - *
270 - * @return bool
271 - */
272 - private static function computeSessionConditionsInUse($boxes)
273 - {
274 - if (!$boxes || empty($boxes->posts))
275 - {
276 - return false;
277 - }
278 -
279 - foreach ($boxes->posts as $box)
280 - {
281 - $meta = self::getMeta($box->ID);
282 -
283 - if (!$meta)
284 - {
285 - continue;
286 - }
287 -
288 - // Impressions limit scoped to the browser session
289 - $impressionsPeriod = isset($meta['assign_impressions_param_type']) ? $meta['assign_impressions_param_type'] : '';
290 -
291 - if ($impressionsPeriod === 'custom')
292 - {
293 - $impressionsPeriod = isset($meta['assign_impressions_param_custom_period']) ? $meta['assign_impressions_param_custom_period'] : '';
294 - }
295 -
296 - if ($impressionsPeriod === 'session')
297 - {
298 - return true;
299 - }
300 -
301 - // Session-dependent display conditions
302 - $metaJson = wp_json_encode($meta);
303 -
304 - if (strpos($metaJson, 'Pageviews') !== false || strpos($metaJson, 'TimeOnSite') !== false)
305 - {
306 - return true;
307 - }
308 - }
309 -
310 - return false;
311 - }
312 -
313 - /**
314 36 * Gets all Boxes.
315 - *
316 - * Only the campaign IDs are cached, in an autoloaded option so the list also
317 - * survives across requests on hosts without a persistent object cache — where
318 - * wp_cache is per-request and the query therefore ran on every page view. The
319 - * autoloaded options are already in memory by the time this runs, so a cache hit
320 - * costs no query of its own.
321 - *
322 - * There is no TTL: the cache is dropped on every campaign write, which also means
323 - * editors see their changes immediately.
324 - *
37 + *
325 38 * @param array $status
326 39 * @param int $limit
327 - *
328 - * @return \WP_Query
40 + *
41 + * @return array
329 42 */
330 43 public static function getAllBoxes($status = ['publish'], $limit = -1)
331 44 {
332 - $key = self::getBoxesCacheKey($status, $limit);
333 - $cache = get_option(self::BOXES_CACHE_OPTION, []);
334 - $cache = is_array($cache) ? $cache : [];
45 + // cache key
46 + $hash = md5('firebox_getAllBoxes_' . implode(',', $status));
335 47
336 - if (isset($cache[$key]) && is_array($cache[$key]))
48 + // check cache
49 + if ($data = wp_cache_get($hash))
337 50 {
338 - return self::hydrateBoxesQuery($cache[$key]);
339 - }
340 -
51 + return $data;
52 + }
53 +
341 54 $args = [
342 55 'post_status' => $status,
343 56 'post_type' => 'firebox',
344 - 'posts_per_page' => $limit,
345 - 'no_found_rows' => true,
346 - 'update_post_term_cache' => false,
347 - 'cache_results' => true
57 + 'posts_per_page' => $limit
348 58 ];
349 -
59 +
350 60 // Get the query.
351 61 $query = new \WP_Query($args);
352 62
353 63 wp_reset_postdata();
354 64
355 - $cache[$key] = wp_list_pluck($query->posts, 'ID');
65 + // set cache
66 + wp_cache_set($hash, $query);
356 67
357 - update_option(self::BOXES_CACHE_OPTION, $cache, true);
358 -
359 68 return $query;
360 69 }
361 70
362 71 /**
363 - * Rebuilds a WP_Query from a cached list of campaign IDs.
364 - *
365 - * @param array $ids
366 - *
367 - * @return \WP_Query
368 - */
369 - private static function hydrateBoxesQuery($ids)
370 - {
371 - $query = new \WP_Query();
372 -
373 - // A hand-built query has no query vars of its own, and the loop reads several
374 - // while setting up each post. "fields => all" states that $posts already holds
375 - // full post objects, which is what it takes for the loop to use them as-is.
376 - $query->query_vars = $query->fill_query_vars(['fields' => 'all']);
377 -
378 - $posts = [];
379 -
380 - if ($ids)
381 - {
382 - // Two queries, both by primary key, priming the same post and postmeta
383 - // caches the original WP_Query did.
384 - _prime_post_caches($ids, false, true);
385 -
386 - // array_filter drops campaigns deleted since the cache was written.
387 - $posts = array_values(array_filter(array_map('get_post', $ids)));
388 - }
389 -
390 - $query->posts = $posts;
391 - $query->post_count = count($posts);
392 - $query->found_posts = $query->post_count;
393 -
394 - return $query;
395 - }
396 -
397 - /**
398 - * The cache entry a box list is stored under.
399 - *
400 - * @param array $status
401 - * @param int $limit
402 - *
403 - * @return string
404 - */
405 - private static function getBoxesCacheKey($status, $limit)
406 - {
407 - return md5(implode(',', (array) $status) . '_' . (int) $limit);
408 - }
409 -
410 - /**
411 - * Drops every cached box list.
412 - *
413 - * @return void
414 - */
415 - public static function invalidateBoxesCache()
416 - {
417 - delete_option(self::BOXES_CACHE_OPTION);
418 - }
419 -
420 - /**
421 72 * Retrieves all boxes in a key => value array of ID => title
422 73 *
423 74 * @return array
424 75 */
@@ -470,9 +121,9 @@
470 121 }
471 122
472 123 $boxes = firebox()->tables->box->getResults([
473 124 'where' => [
474 - 'ID' => ' NOT IN (' . absint($id) . ')',
125 + 'ID' => ' NOT IN (' . esc_sql($id) . ')',
475 126 'post_status' => " = 'publish'",
476 127 'post_type' => " = 'firebox'"
477 128 ]
478 129 ]);
@@ -504,9 +155,9 @@
504 155 $box = (int) $box;
505 156
506 157 $box = firebox()->tables->box->getResults([
507 158 'where' => [
508 - 'ID' => " = '" . absint($box) . "'"
159 + 'ID' => " = '" . esc_sql($box) . "'"
509 160 ]
510 161 ]);
511 162
512 163 return isset($box[0]) ? $box[0] : [];
@@ -529,9 +180,9 @@
529 180 $box = (int) $box;
530 181
531 182 $box = firebox()->tables->box->getResults([
532 183 'where' => [
533 - 'ID' => " = '" . absint($box) . "'"
184 + 'ID' => " = '" . esc_sql($box) . "'"
534 185 ]
535 186 ]);
536 187
537 188 if (!$box)
@@ -582,9 +233,9 @@
582 233 // cache key
583 234 $cache_key = md5('fboxSettings');
584 235
585 236 // check cache
586 - if ($params = wp_cache_get($cache_key, 'firebox'))
237 + if ($params = wp_cache_get($cache_key))
587 238 {
588 239 return $params;
589 240 }
590 241
@@ -591,12 +242,65 @@
591 242 // get params
592 243 $params = get_option('firebox_settings');
593 244
594 245 // set cache
595 - wp_cache_set($cache_key, $params, 'firebox', 5 * MINUTE_IN_SECONDS);
246 + wp_cache_set($cache_key, $params);
596 247
597 248 return $params;
598 249 }
250 +
251 + /**
252 + * Returns the box template from the URL using the "template" query string.
253 + *
254 + * @return array
255 + */
256 + public static function getBoxFromTemplateURL()
257 + {
258 + if (!isset($_GET['fpf_use_template'])) //phpcs:ignore WordPress.Security.NonceVerification.Recommended
259 + {
260 + return;
261 + }
262 +
263 + // $template must be an integer
264 + $template = isset($_GET['template']) ? intval($_GET['template']) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
265 + if (empty($template) || $template === 'blank')
266 + {
267 + return;
268 + }
269 +
270 + $local_template_path = \FPFramework\Helpers\WPHelper::getPluginUploadsDirectory('firebox', 'templates') . '/template.json';
271 + if (!file_exists($local_template_path))
272 + {
273 + return;
274 + }
275 +
276 + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
277 + $local_template = file_get_contents($local_template_path);
278 + if (!$local_template = json_decode($local_template, true))
279 + {
280 + return;
281 + }
282 +
283 + if (!isset($local_template['id']))
284 + {
285 + return;
286 + }
287 +
288 + if (!isset($local_template['template']))
289 + {
290 + return;
291 + }
292 +
293 + if ((int) $local_template['id'] !== (int) $template)
294 + {
295 + return;
296 + }
297 +
298 + // Find images and save them locally.
299 + $local_template['template']['post_content'] = \FPFramework\Helpers\WPHelper::downloadAndReplaceImages($local_template['template']['post_content']);
300 +
301 + return $local_template;
302 + }
599 303
600 304 /**
601 305 * Duplicates a box
602 306 *
@@ -608,10 +312,10 @@
608 312 {
609 313 // get box
610 314 $box = firebox()->tables->box->getResults([
611 315 'where' => [
612 - 'ID' => " = '" . absint($box_id) . "'",
613 - 'post_status' => " = '" . sanitize_key(get_post_status($box_id)) . "'",
316 + 'ID' => " = '" . esc_sql($box_id) . "'",
317 + 'post_status' => " = '" . esc_sql(get_post_status($box_id)) . "'",
614 318 'post_type' => " = 'firebox'"
615 319 ],
616 320 'limit' => 1
617 321 ]);
@@ -626,17 +330,8 @@
626 330 $box->ID = '';
627 331 $box->post_title = 'Copy of ' . $box->post_title;
628 332 $box->post_status = 'draft';
629 333
630 - /**
631 - * The copy belongs to whoever duplicates, never the source author — the same
632 - * rule the importer applies. The PHP and custom-JavaScript gates are a
633 - * function of the post author, so keeping the source author would let a copy
634 - * of a higher-privileged user's campaign keep running code its new owner is
635 - * not allowed to write.
636 - */
637 - $box->post_author = get_current_user_id();
638 -
639 334 $factory = new \FPFramework\Base\Factory();
640 335
641 336 $tz = wp_timezone();
642 337 $date_without_tz = $factory->getDate();
@@ -653,14 +348,11 @@
653 348 // insert new box
654 349 $new_box_id = firebox()->tables->box->insert($box);
655 350
656 351 // add meta options for new box
657 - // TODO: In the future, use "firebox_meta". This is a temporary fix for backwards compatibility.
658 - $checkMeta = (array) $meta;
659 - $meta_key = isset($checkMeta['width']) ? 'firebox_meta' : 'fpframework_meta_settings';
660 - update_post_meta($new_box_id, $meta_key, wp_slash($meta));
352 + update_post_meta($new_box_id, 'fpframework_meta_settings', wp_slash($meta));
661 353
662 - return $new_box_id;
354 + return true;
663 355 }
664 356
665 357 /**
666 358 * Reset Box Stats
@@ -681,22 +373,20 @@
681 373 firebox()->tables->boxlog->deleteRaw('WHERE box IN (' . implode(',', $box_ids) . ')');
682 374 }
683 375
684 376 /**
685 - * Builds the export payload (boxes + meta) and a suggested filename for the given box ids,
686 - * without emitting any headers or output. Shared by exportBoxes() (legacy file-download
687 - * action) and the Campaigns REST export endpoint.
688 - *
377 + * Exports boxes
378 + *
689 379 * @param array $box_ids
690 - *
691 - * @return array|null ['filename' => string, 'exported' => array] or null if no boxes matched
380 + *
381 + * @return string
692 382 */
693 - public static function getExportPayload($box_ids)
383 + public static function exportBoxes($box_ids)
694 384 {
695 385 // get boxes
696 386 $boxes = firebox()->tables->box->getResults([
697 387 'where' => [
698 - 'ID' => ' IN (' . implode(',', array_map('intval', $box_ids)) . ')',
388 + 'ID' => ' IN (' . implode(',', esc_sql($box_ids)) . ')',
699 389 'post_type' => " = 'firebox'"
700 390 ]
701 391 ]);
702 392
@@ -703,13 +393,13 @@
703 393 $boxes = (array) $boxes;
704 394
705 395 if (!count($boxes))
706 396 {
707 - return null;
397 + return;
708 398 }
709 399
710 400 $exported = [];
711 -
401 +
712 402 $filename = firebox()->_('FB_PLUGIN_NAME') . ' Items';
713 403
714 404 // name for 1 box
715 405 if (count($boxes) == 1)
@@ -714,12 +404,10 @@
714 404 // name for 1 box
715 405 if (count($boxes) == 1)
716 406 {
717 407 $name = mb_strtolower(html_entity_decode($boxes['0']->post_title));
718 - // preg_replace() returns null when PCRE fails (backtrack limit, invalid UTF-8),
719 - // and passing null on is deprecated since PHP 8.1. Keep the previous value.
720 - $name = preg_replace('#[^a-z0-9_-]#', '_', $name) ?? $name;
721 - $name = trim(preg_replace('#__+#', '_', $name) ?? $name, '_-');
408 + $name = preg_replace('#[^a-z0-9_-]#', '_', $name);
409 + $name = trim(preg_replace('#__+#', '_', $name), '_-');
722 410
723 411 $filename = firebox()->_('FB_PLUGIN_NAME') . ' Item (' . $name . ')';
724 412 }
725 413
@@ -732,31 +420,8 @@
732 420 'meta' => $meta
733 421 ];
734 422 }
735 423
736 - return [
737 - 'filename' => $filename,
738 - 'exported' => $exported,
739 - ];
740 - }
741 -
742 - /**
743 - * Exports boxes
744 - *
745 - * @param array $box_ids
746 - *
747 - * @return string
748 - */
749 - public static function exportBoxes($box_ids)
750 - {
751 - if (!$payload = self::getExportPayload($box_ids))
752 - {
753 - return;
754 - }
755 -
756 - $filename = $payload['filename'];
757 - $exported = $payload['exported'];
758 -
759 424 // SET DOCUMENT HEADER
760 425 $userAgent = isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '';
761 426 if (preg_match('#Opera(/| )([0-9].[0-9]{1,2})#', $userAgent))
762 427 {
@@ -790,13 +455,8 @@
790 455 }
791 456
792 457 // PRINT STRING
793 458 echo wp_json_encode($exported);
794 -
795 - if (ob_get_level())
796 - {
797 - @ob_end_flush();
798 - }
799 459 die();
800 460 }
801 461
802 462 /**
@@ -817,9 +477,9 @@
817 477 'select' => [
818 478 'date as last_date_viewed'
819 479 ],
820 480 'where' => [
821 - 'box' => ' = ' . absint($id),
481 + 'box' => ' = ' . esc_sql($id),
822 482 ],
823 483 'orderby' => ' date desc',
824 484 'limit' => 1
825 485 ]);
@@ -825,7 +485,5 @@
825 485 ]);
826 486
827 487 return isset($last_date_viewed[0]->last_date_viewed) ? get_date_from_gmt($last_date_viewed[0]->last_date_viewed) : null;
828 488 }
829 -
830 -
831 -}
489 +}