PluginProbe
HurryTimer – An Scarcity and Urgency Countdown Timer for WordPress & WooCommerce / 2.6.2
HurryTimer – An Scarcity and Urgency Countdown Timer for WordPress & WooCommerce v2.6.2
2.15.0 trunk 1.0.0 1.0.1 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.1.2 2.1.3 2.1.5 2.1.6 2.1.7 2.1.8 2.10.0 All 62 releases
hurrytimer / includes / Campaign.php

Campaign.php in HurryTimer – An Scarcity and Urgency Countdown Timer for WordPress & WooCommerce 2.6.2, at includes/Campaign.php

1,828 lines 41.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Hurrytimer;
4
5 use Hurrytimer\Dependencies\Carbon\Carbon;
6 use Hurrytimer\Dependencies\Carbon\CarbonPeriod;
7 use Hurrytimer\Utils\Helpers;
8
9 class Campaign
10 {
11
12 /**
13 * Campaign custom post ID
14 *
15 * @var int
16 */
17 private $id;
18
19 /**
20 * Campaign mode.
21 *
22 * @see C.php
23 *
24 * @var int
25 */
26 public $mode = C::MODE_REGULAR;
27
28 /**
29 * Evergreen duration array.
30 *
31 * @see getDuration()
32 *
33 * @var array
34 */
35 public $duration;
36
37 /**
38 * Recurrence duration
39 *
40 * @var int
41 */
42 public $recurringDuration;
43
44 /**
45 * Recurrence start date/time
46 *
47 * @var string
48 */
49 public $recurringStartTime;
50
51 /**
52 * Recurrence end option
53 *
54 * @var int
55 */
56 public $recurringEnd = C::RECURRING_END_NEVER;
57
58 /**
59 * Recurrence frequency
60 *
61 * @see C
62 * @var string
63 */
64 public $recurringFrequency = C::RECURRING_DAILY;
65
66 /**
67 * Recurrence interval
68 *
69 * @var int
70 */
71 public $recurringInterval = 1;
72
73 /**
74 * Recurrence count
75 *
76 * @var int
77 */
78 public $recurringCount = 2;
79
80 /**
81 * Recurrence end date/time
82 *
83 * @var
84 */
85 public $recurringUntil;
86
87 /**
88 * Recurrence allowed days
89 *
90 * @var array
91 */
92 public $recurringDays = [0, 1, 2, 3, 4, 5, 6];
93
94 /**
95 * Restart option after expiration.
96 *
97 * @see \Hurrytimer\CampaignRestart
98 *
99 * @var int
100 */
101 public $restart;
102 public $restartDuration = ['days' => 0, 'hours' => 0, 'minutes' => 0, 'seconds' => 0];
103
104 /**
105 * Headline text.
106 *
107 * @var string
108 */
109 public $headline = "Hurry Up!";
110
111 /**
112 * Headline color.
113 *
114 * @var string
115 */
116 public $headlineColor = "#000";
117
118 /**
119 * Headline size.
120 *
121 * @var int
122 */
123 public $headlineSize = 30;
124
125 /**
126 * Headline position.
127 *
128 * @var int
129 */
130 public $headlinePosition = C::HEADLINE_POSITION_ABOVE_TIMER;
131
132 /**
133 * Headline visibility.
134 *
135 * @var boolean
136 */
137 public $headlineVisibility = C::YES;
138
139 public $headlineSpacing = 5;
140
141 /**
142 * Control labels visibility.
143 *
144 * @var string
145 */
146 public $labelVisibility = C::YES;
147
148 /**
149 * Show/hide days block.
150 *
151 * @var string
152 */
153 public $daysVisibility = C::YES;
154
155 public $monthsVisibility = C::NO;
156
157 /**
158 * Show/hide hours block.
159 *
160 * @var string
161 */
162 public $hoursVisibility = C::YES;
163
164 /**
165 * Show/hide minutes block.
166 *
167 * @var string
168 */
169 public $minutesVisibility = C::YES;
170
171 /**
172 * Show/hide seconds block.
173 *
174 * @var string
175 */
176 public $secondsVisibility = C::YES;
177
178 /**
179 * Regular campaign end date/time.
180 *
181 * @var string
182 */
183 public $endDatetime;
184
185 /**
186 * Labels texts.
187 *
188 * @var array
189 */
190 public $labels
191 = [
192 'months' => 'months',
193 'days' => 'days',
194 'hours' => 'hrs',
195 'minutes' => 'mins',
196 'seconds' => 'secs',
197 ];
198
199 /**
200 * Digit color.
201 *
202 * @var string
203 */
204 public $digitColor = "#000";
205
206 /**
207 * Digit size.
208 *
209 * @var int
210 */
211 public $digitSize = 35;
212
213 /**
214 * Redirect action url.
215 *
216 * @var string
217 */
218 public $redirectUrl;
219
220 /**
221 * Label size.
222 *
223 * @var int
224 */
225 public $labelSize = 12;
226
227 /**
228 * Label color.
229 *
230 * @var string
231 */
232 public $labelColor = "#000";
233
234 /**
235 * End action.
236 *
237 * @var array
238 */
239 public $actions = [];
240
241 /**
242 * WooCommerce compaign position in product page.
243 *
244 * @var int
245 */
246 public $wcPosition = C::WC_POSITION_ABOVE_TITLE;
247
248 /**
249 * Enable/disable woocommerce integration.
250 *
251 * @var string
252 */
253 public $wcEnable = C::NO;
254
255 /**
256 * WooCommerce products selection.
257 *
258 * @var array
259 */
260 public $wcProductsSelection;
261
262 /**
263 * WooCommerce products selection type.
264 *
265 * @var int
266 */
267 public $wcProductsSelectionType;
268
269 /**
270 * WooCommerce conditions.
271 * @var array $wcConditions
272 */
273 public $wcConditions;
274
275 /**
276 * Timer block border color.
277 *
278 * @var string
279 */
280 public $blockBorderColor = "";
281
282 /**
283 * Timer Block border width.
284 *
285 * @var int
286 */
287
288 public $blockBorderWidth = 0;
289
290 /**
291 * Timer block radius.
292 *
293 * @var int
294 */
295 public $blockBorderRadius = 0;
296
297 /**
298 * Block size.
299 *
300 * @var int
301 */
302
303 public $blockSize = 50;
304
305 /**
306 * Block background color.
307 *
308 * @var string
309 */
310 public $blockBgColor = '';
311
312 /**
313 * Block spacing.
314 *
315 * @var int
316 */
317 public $blockSpacing = 5;
318
319 /**
320 * Block padding.
321 *
322 * @var int
323 */
324 public $blockPadding = 0;
325
326 /**
327 * Block spearator visibility.
328 *
329 * @var boolean
330 */
331 public $blockSeparatorVisibility = C::YES;
332
333 /**
334 * Label case
335 *
336 * @var string
337 */
338 public $labelCase = C::TRANSFORM_UPPERCASE;
339
340 /**
341 * Custom CSS
342 *
343 * @var string
344 */
345 public $customCss = '';
346
347 /**
348 * Block elements display
349 * Values: block, inline
350 *
351 * @var string
352 */
353 public $blockDisplay = 'block';
354
355 /**
356 * Enable sticky bar.
357 */
358 public $enableSticky = C::NO;
359
360 /**
361 * Sticky bar background color.
362 *
363 * @var string
364 */
365 public $stickyBarBgColor = '#eee';
366
367 /**
368 * Sticky bar close button color.
369 *
370 * @var string
371 */
372 public $stickyBarCloseBtnColor = '#fff';
373
374 /**
375 * Sticky bar position.
376 * Values: top, bottom
377 *
378 * @var string
379 */
380 public $stickyBarPosition = 'top';
381
382 /**
383 * Sticky bar padding.
384 *
385 * @var integer
386 */
387 public $stickyBarPadding = 5;
388
389 /**
390 * Sticky bar display pages.
391 *
392 * @var array
393 */
394 public $stickyBarPages = [];
395 public $stickyIncludeUrls = [];
396 public $stickyExcludeUrls = [];
397
398 /**
399 * Sticky exclude pages.
400 *
401 * @var array
402 */
403 public $stickyExcludePages = [];
404
405 /**
406 * Where to display the sticky bar option
407 *
408 * @var string
409 */
410 public $stickyBarDisplayOn = 'all_pages';
411
412 /**
413 * Show sticky bar close button?
414 *
415 * @var string
416 */
417 public $stickyBarDismissible = C::YES;
418
419
420 /**
421 * If dismissed, re-open sticky bar after x days.
422 * @var int
423 */
424 public $stickyBarDismissTimeout = 7;
425
426 /**
427 * CTA settings.
428 *
429 * @var array
430 */
431 public $callToAction
432 = [
433 'enabled' => C::NO,
434 'new_tab' => C::NO,
435 'url' => '',
436 'text' => 'Learn More',
437 'text_size' => 15,
438 'text_color' => '#fff',
439 'bg_color' => '#000',
440 'y_padding' => 10,
441 'x_padding' => 15,
442 'border_radius' => 3,
443 'spacing' => 5,
444
445 ];
446
447 /**
448 * Campaign elements display
449 * values: block,inline
450 *
451 * @var string
452 */
453 public $campaignDisplay = 'block';
454
455 /**
456 * Campaign aligments
457 * values: center,left,right
458 *
459 * @var string
460 */
461 public $campaignAlign = 'center';
462
463 /**
464 * Campaign spacing.
465 *
466 * @var integer
467 */
468 public $campaignSpacing = 10;
469
470 /**
471 * Campaign horizontal padding.
472 *
473 * @var integer
474 */
475 public $campaignXPadding = 10;
476
477 /**
478 * Campaign vertical padding.
479 *
480 * @var integer
481 */
482 public $campaignYPadding = 10;
483
484 /**
485 * Force evergreen timer to reset on reload.
486 */
487 public $reloadReset = false;
488
489
490 /**
491 *
492 */
493
494 public $detectionMethods = [
495 C::DETECTION_METHOD_COOKIE,
496 C::DETECTION_METHOD_IP,
497 ];
498
499
500 public $recurringMonthlyDayType = C::RECURRING_MONTHLY_DAY_OF_MONTH;
501
502
503 public $recurringUnselectedDaysAction = 'hide';
504
505 public function __construct($id)
506 {
507 $this->recurringStartTime = Carbon::now(hurryt_tz())->format('Y-m-d h:i A');
508 $this->id = $id;
509 }
510
511 /**
512 * Returns compaign post iD.
513 *
514 * @return int
515 */
516 public function get_id()
517 {
518 return $this->id;
519 }
520
521
522 /**
523 * Returns true if the current sticky bar can be displayed on the given page.
524 *
525 * @param $pageId
526 *
527 * @return bool
528 * @throws \Exception
529 */
530 public function show_sticky_on_page($pageId)
531 {
532 if ($this->enableSticky === C::NO) {
533 return true;
534 }
535
536 switch ($this->getStickyBarDisplayOn()):
537
538 case 'wc_products_pages':
539 if (!empty($pageId) && function_exists('is_product') && is_product()) {
540
541 $wc_campaign = new WCCampaign();
542 if (($wc_campaign->has_campaign($this, $pageId))
543 && $wc_campaign->met_conditions($this, $pageId)
544 ) {
545 return apply_filters('hurryt_show_sticky_bar', true, $this->get_id());
546 }
547 }
548
549 return apply_filters('hurryt_show_sticky_bar', false, $this->get_id());
550
551 case 'specific_pages':
552 $pageIds = $this->getStickyBarPages();
553
554 if (!empty($pageId) && !empty($pageIds) && in_array($pageId, $pageIds, true)) {
555 return apply_filters('hurryt_show_sticky_bar', true, $this->get_id());
556 }
557
558 foreach ($this->stickyIncludeUrls as $url) {
559 $current_url = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/';
560
561 if (Helpers::are_urls_match($url, $current_url)) {
562 return apply_filters('hurryt_show_sticky_bar', true, $this->get_id());
563 break;
564 }
565 }
566 return apply_filters('hurryt_show_sticky_bar', false, $this->get_id());
567
568 case 'exclude_pages':
569 $pageIds = $this->getStickyExcludePages();
570
571 if (!empty($pageId) && !empty($pageIds) && in_array($pageId, $pageIds, true)) {
572 return apply_filters('hurryt_show_sticky_bar', false, $this->get_id());
573 }
574
575 foreach ($this->stickyExcludeUrls as $url) {
576 $current_url = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/';
577 if (Helpers::are_urls_match($current_url, $url)) {
578 return apply_filters('hurryt_show_sticky_bar', false, $this->get_id());
579 break;
580 }
581 }
582
583 return apply_filters('hurryt_show_sticky_bar', true, $this->get_id());
584 case 'all_pages':
585 return apply_filters('hurryt_show_sticky_bar', true, $this->get_id());
586 default:
587 return apply_filters('hurryt_show_sticky_bar', false, $this->get_id());
588 endswitch;
589 }
590
591 /**
592 * Returns sticky bar pages.
593 *
594 * @return array
595 */
596 public function getStickyBarPages()
597 {
598 $pagesIds = $this->get_prop('sticky_bar_pages');
599
600 return array_filter(array_map('intval', $pagesIds));
601 }
602
603 public function getStickyExcludePages()
604 {
605 $page_ids = $this->get_prop('sticky_exclude_pages');
606
607 return array_filter(array_map('intval', $page_ids));
608 }
609
610 /**
611 * Set sticky bar pages.
612 *
613 * @param $value
614 */
615 public function setStickyBarPages($value)
616 {
617 $this->set_prop('sticky_bar_pages', $value);
618 }
619
620 public function setStickyExcludePages($value)
621 {
622 $this->set_prop('sticky_exclude_pages', $value);
623 }
624
625 public function setStickyBarDismissTimeout($value)
626 {
627 $this->set_prop('sticky_bar_dismiss_timeout', intval($value));
628 }
629
630 public function getStickyBarDismissTimeout()
631 {
632 return intval($this->get_prop('sticky_bar_dismiss_timeout'));
633 }
634
635 public function getStickyBarDisplayOn()
636 {
637
638 // backward compat.
639 $all_pages = $this->get_prop('sticky_bar_show_on_all_pages', false);
640
641 if ($all_pages === C::YES) {
642 $this->setStickyBarDisplayOn('all_pages');
643 }
644
645 $this->delete_prop('sticky_bar_show_on_all_pages');
646
647 $value = $this->get_prop('_hurryt_sticky_bar_display_on', false);
648
649 return !empty($value) ? $value : $this->stickyBarDisplayOn;
650 }
651
652 public function setStickyBarDisplayOn($value)
653 {
654 $this->set_prop('_hurryt_sticky_bar_display_on', $value);
655 }
656
657 /**
658 * Get raw setting.
659 *
660 * @param string $name
661 * @param boolean
662 *
663 * @return mixed
664 */
665 public function get_prop($name, $useDefault = true)
666 {
667
668 $value = get_post_meta($this->id, $name, true);
669 if (!empty($value)) {
670 return $value;
671 }
672 if ($useDefault) {
673 $name = str_replace('_hurryt_', '', $name);
674 return $this->{Helpers::snakeToCamelCase($name)};
675 }
676
677 return $value;
678 }
679
680 public function set_prop($name, $value)
681 {
682 $value = !empty($value) ? $value : '';
683 $_name = str_replace('_hurryt_', '', $name);
684 $getterMethod = Helpers::snakeToCamelCase($_name);
685 if (empty($value) && method_exists($this, $getterMethod)) {
686 $value = $this->{$getterMethod}();
687 }
688
689 update_post_meta($this->id, $name, $value);
690 }
691
692 /**
693 * Bulk save for compaign settings.
694 *
695 * @param $data
696 */
697 public function storeSettings($data)
698 {
699 foreach ($data as $prop => $value) {
700 $method = Helpers::snakeToCamelCase("set_{$prop}");
701 if (method_exists($this, $method)) {
702 $this->$method($value ?: $this->$prop);
703 } elseif (property_exists(__CLASS__, Helpers::snakeToCamelCase($prop))) {
704 $this->set_prop($prop, $value);
705 }
706 }
707 if (!isset($data['wc_conditions'])) {
708 $this->setWcConditions([]);
709 }
710 if (!isset($data['sticky_bar_pages'])) {
711 $this->setStickyBarPages([]);
712 }
713
714 CSS_Builder::get_instance()->build();
715 }
716
717 //removeIf(pro)
718
719 /**
720 * @param int $mode
721 * ::PROP
722 */
723 public function setMode($mode)
724 {
725 if ( $mode == C::MODE_RECURRING ) {
726 return;
727 }
728 $this->set_prop('mode', $mode);
729 }
730 //endRemoveIf(pro)
731
732 /**
733 * TODO: Settings should load automatically upon instance creation.
734 */
735 public function loadSettings()
736 {
737
738 $reflection = new \ReflectionObject($this);
739 // TODO: Refactor prop loading to `$this->get_{prop}()`
740 foreach ($reflection->getProperties(\ReflectionProperty::IS_PUBLIC) as $prop) {
741 $name = $prop->getName();
742 $method = 'get' . ucfirst($name);
743 if (method_exists($this, $method)) {
744 $this->{$name} = $this->$method();
745 } else {
746 $this->{$name} = $this->get_prop(Helpers::camelToSnakeCase($name));
747 }
748 }
749 }
750
751
752 public function getHeadline()
753 {
754 if (empty($this->id)) {
755 return $this->headline;
756 }
757 if (metadata_exists('post', $this->id, '_hurryt_headline')) {
758 return $this->get_prop('_hurryt_headline');
759 }
760
761 return get_the_title($this->id);
762 }
763
764 public function setHeadline($value)
765 {
766 $this->set_prop('_hurryt_headline', $value);
767 }
768
769 public function is_published()
770 {
771 return get_post_status($this->id) === "publish";
772 }
773
774 /**
775 * Check if WooCommerce is enabled for this campaign.
776 *
777 * @return bool
778 */
779 public function is_wc_enabled()
780 {
781 return $this->getWcEnable() === C::YES;
782 }
783
784 /**
785 * Check current countdown timer mode
786 * ::PROP
787 *
788 * @return bool
789 */
790 public function is_evergreen()
791 {
792 return $this->get_prop('mode') == C::MODE_EVERGREEN;
793 }
794
795 public function is_one_time()
796 {
797 return $this->get_prop('mode') == C::MODE_REGULAR;
798 }
799
800 public function is_recurring()
801 {
802 return $this->get_prop('mode') == C::MODE_RECURRING;
803 }
804
805 /**
806 * Save compaign endtime for regular mode.
807 *
808 * @param $date
809 */
810 public function setEndDatetime($date)
811 {
812 $this->set_prop('end_datetime', $date ?: $this->defaultEndDatetime());
813 }
814
815 /**
816 * Returns default end datetime for regular mode.
817 * ::PROP
818 *
819 * @return false|string
820 */
821 private function defaultEndDatetime()
822 {
823 return date('Y-m-d h:i A', strtotime('+1 week'));
824 }
825
826 /**
827 * Save digit color.
828 * ::PROP
829 *
830 * @param $color
831 */
832 public function setDigitColor($color)
833 {
834 $this->set_prop('digit_color', $color);
835 }
836
837 /**
838 * Returns headline visibility.
839 * ::PROP
840 *
841 * @return mixed
842 */
843
844 public function getHeadlineVisibility()
845 {
846 $meta = 'headline_visibility';
847
848 $value = $this->get_prop($meta, false);
849 if ($value === C::NO || $value === C::YES) {
850 return $value;
851 }
852
853 $legacyMeta = 'display_headline';
854 $legacyValue = filter_var($this->get_prop_legacy($legacyMeta), FILTER_VALIDATE_BOOLEAN);
855 $legacyValue = $legacyValue ? C::YES : C::NO;
856
857 $this->set_prop($meta, $legacyValue);
858
859 $this->delete_prop($legacyMeta);
860
861 return $this->get_prop($meta);
862 }
863
864 /**
865 * Returns digit color.
866 * Backward compat with older versions.
867 * ::PROP
868 *
869 * @return mixed
870 */
871 public function getDigitColor()
872 {
873 $value = $this->get_prop('digit_color', false);
874 if ($value) {
875 return $value;
876 }
877
878 $legacy = $this->get_prop_legacy('text_color');
879 if (!$legacy) {
880 return $this->digitColor;
881 }
882
883 $this->setDigitColor($legacy);
884 if (!$this->get_prop('label_color', false)) {
885 $this->setLabelColor($legacy);
886 }
887
888 $this->delete_prop('text_color');
889
890 return $this->get_prop('digit_color');
891 }
892
893 /**
894 * ::PROP
895 *
896 * @param $color
897 */
898 public function setLabelColor($color)
899 {
900 $this->set_prop('label_color', $color);
901 }
902
903 /**
904 * Get label color
905 * ::PROP
906 *
907 * @return mixed
908 */
909 public function getLabelColor()
910 {
911 $value = $this->get_prop('label_color', false);
912 if ($value) {
913 return $value;
914 }
915
916 $legacy = $this->get_prop_legacy('text_color');
917 if (!$legacy) {
918 return $this->labelColor;
919 }
920
921 $this->setLabelColor($legacy);
922 if (!$this->get_prop('digit_color', false)) {
923 $this->setDigitColor($legacy);
924 }
925
926 $this->delete_prop('text_color');
927
928 return $this->get_prop('label_color');
929 }
930
931 /**
932 *
933 * Return timer digit size.
934 * Backward comapt. with older versions.
935 * ::PROP
936 *
937 * @return int
938 * @since 1.2.4
939 */
940 public function getDigitSize()
941 {
942 $meta = 'digit_size';
943 $value = $this->get_prop($meta, false);
944 if (!empty($value)) {
945 return $value;
946 }
947 $legacy = $this->get_prop_legacy('text_size');
948
949 if (empty($legacy)) {
950 return $this->digitSize;
951 }
952
953 $this->setDigitSize($legacy);
954
955 $this->delete_prop('text_size');
956
957 return $this->get_prop('digit_size');
958 }
959
960 /**
961 * Save timer digit size.
962 * ::PROP
963 *
964 * @param $size
965 */
966 public function setDigitSize($size)
967 {
968 $this->set_prop('digit_size', $size);
969 }
970
971 /**
972 * Save timer label size.
973 * ::PROP
974 *
975 * @param $size
976 */
977 public function setLabelSize($size)
978 {
979 $this->set_prop('label_size', $size);
980 }
981
982
983 /**
984 * ::PROP
985 *
986 * @param $size
987 */
988 public function setHeadlineSize($size)
989 {
990 $this->set_prop('headline_size', $size);
991 }
992
993 /**
994 * Returns evergreen duration.
995 * ::PROP
996 *
997 * @return array
998 */
999 public function getDuration()
1000 {
1001 $duration = (array)$this->get_prop('duration');
1002 return array_pad(array_map('intval', $duration), 4, 0);
1003 }
1004
1005
1006 public function getRestartDuration($asSeconds = false)
1007 {
1008 $duration = (array)$this->get_prop('hurryt_restart_duration', false);
1009
1010 $duration = wp_parse_args($duration, $this->restartDuration);
1011 foreach ($duration as $unit => $value) {
1012 $duration[$unit] = (int)$value;
1013 }
1014
1015 if ($asSeconds) {
1016 $duration = $duration['days'] * DAY_IN_SECONDS +
1017 $duration['hours'] * HOUR_IN_SECONDS +
1018 $duration['minutes'] * MINUTE_IN_SECONDS +
1019 $duration['seconds'];
1020 }
1021 return $duration;
1022 }
1023
1024 public function setRestartDuration($duration_array)
1025 {
1026 $this->set_prop('hurryt_restart_duration', $duration_array);
1027 }
1028 /**
1029 * ::PROP
1030 *
1031 * @param $value
1032 */
1033 public function setRecurringDuration($value)
1034 {
1035 $this->set_prop('_hurryt_recurring_duration', $value);
1036 }
1037
1038
1039 /**
1040 * Returns evergreen duration.
1041 *
1042 * @return array
1043 */
1044 public function getRecurringDuration()
1045 {
1046
1047 $duration = (array)$this->get_prop('_hurryt_recurring_duration', false);
1048 return array_pad(array_map('intval', $duration), 4, 0);
1049 }
1050
1051 /**
1052 * Returns actions array.
1053 *
1054 * @return array
1055 */
1056 public function getActions()
1057 {
1058 $legacy = $this->get_prop_legacy('end_action');
1059 if ($legacy) {
1060 $redirectUrl = $this->get_prop_legacy('redirect_url');
1061 $actions = [
1062 [
1063 'id' => intval($legacy),
1064 'redirectUrl' => $redirectUrl,
1065 ],
1066 ];
1067 $this->set_prop('actions', $actions);
1068 $this->delete_prop('end_action');
1069 $this->delete_prop('redirect_url');
1070
1071 return $this->mergeActions($actions);
1072 }
1073
1074 return $this->mergeActions($this->get_prop('actions'));
1075 }
1076
1077 private function mergeActions($actions)
1078 {
1079 $defaults = [
1080 [
1081 'id' => C::ACTION_NONE,
1082 'redirectUrl' => '',
1083 'message' => '',
1084 'coupon' => '',
1085 'wcStockStatus' => '',
1086 ],
1087 ];
1088
1089 if (count($actions) === 0) {
1090 return $defaults;
1091 }
1092
1093 return array_map(function ($action) use ($defaults) {
1094 $action['id'] = (int)$action['id'];
1095
1096 $action['message'] = preg_replace_callback('#<style(\s=?[\S]*)?>([\s\S]*)?<\/style>#', function ($matches) {
1097 return '<style' . $matches[1] . '>' . preg_replace('/\<br(\s*)?\/?\>/i', '', $matches[2]) . '</style>';
1098 }, $action['message']);
1099
1100 return array_merge($defaults[0], $action);
1101 }, $actions);
1102 }
1103
1104
1105 public function get_action($needle)
1106 {
1107 $result = array_filter($this->actions, function ($action) use ($needle) {
1108 return $needle === (int)$action['id'];
1109 });
1110
1111 if (empty($result)) {
1112 return false;
1113 }
1114
1115 return array_shift($result);
1116 }
1117
1118 /**
1119 * Returns evergreen duration in seconds.
1120 *
1121 * @param array $duration
1122 *
1123 * @return int
1124 */
1125 public function durationInSeconds($duration = [])
1126 {
1127 list($d, $h, $m, $s) = empty($duration) ? $this->getDuration() : $duration;
1128
1129 return
1130 $d * DAY_IN_SECONDS +
1131 $h * HOUR_IN_SECONDS +
1132 $m * MINUTE_IN_SECONDS +
1133 $s;
1134 }
1135
1136 /**
1137 * Returns end datetime.
1138 *
1139 * @return string
1140 */
1141 public function getEndDatetime()
1142 {
1143 return $this->get_prop('end_datetime') ?: $this->defaultEndDatetime();
1144 }
1145
1146 /**
1147 * Return true if the recurrence is expired.
1148 *
1149 * @return bool
1150 */
1151 public function is_recurring_expired()
1152 {
1153 $endDate = $this->getRecurrenceEndDate();
1154
1155 if (!($endDate instanceof Carbon)) {
1156 return false;
1157 }
1158
1159 $now = Carbon::now(hurryt_tz());
1160
1161 return $now->greaterThan($endDate);
1162 }
1163
1164 /**
1165 * Calculate next recurrence.
1166 *
1167 * @return mixed
1168 * @throws \Exception
1169 */
1170 function timeToNextRecurrence()
1171 {
1172 $this->loadSettings();
1173
1174 if ($this->isMonthlyRecurring()) {
1175 $current = $this->getRecurrenceEndDate();
1176 $next = $this->getRecurrenceEndDate(true);
1177 return absint($next->diffInSeconds($current));
1178 }
1179 list($duration, $pause, $period) = $this->getRecurringDurationInSeconds();
1180 return $pause ?: 0;
1181 }
1182
1183 /**
1184 * Return true if the current campaign can recur today.
1185 *
1186 * @return bool
1187 */
1188 public function can_recur_today()
1189 {
1190 $this->loadSettings();
1191
1192 $day = absint(Carbon::now(hurryt_tz())->format('w'));
1193
1194 $recur = in_array($day, array_map('absint', $this->recurringDays));
1195
1196 return apply_filters('hurryt_recur_today', $recur, $this->get_id());
1197 }
1198
1199 public function isMonthlyRecurring()
1200 {
1201 return $this->recurringFrequency === C::RECURRING_MONTHLY;
1202 }
1203
1204 public function isDayOfWeekRecurring()
1205 {
1206
1207 return $this->recurringMonthlyDayType === C::RECURRING_MONTHLY_DAY_OF_WEEK;
1208 }
1209
1210 public function isDayOfMonthRecurring()
1211 {
1212 return $this->recurringMonthlyDayType === C::RECURRING_MONTHLY_DAY_OF_MONTH;
1213 }
1214
1215 public function shouldEndRecurringByDate()
1216 {
1217 return absint($this->recurringEnd) === C::RECURRING_END_TIME;
1218 }
1219 public function shouldEndRecurringByRecurrences()
1220 {
1221 return (int)$this->recurringEnd === C::RECURRING_END_OCCURRENCES;
1222 }
1223
1224 public function shouldRecurForever()
1225 {
1226 return (int)$this->recurringEnd === C::RECURRING_END_NEVER;
1227 }
1228
1229 public function getRecurringRecurrences()
1230 {
1231 $num = (int)$this->recurringCount;
1232 return ++$num;
1233 }
1234
1235
1236 public function getRecurringPeriodEndDate()
1237 {
1238 list($d, $h, $m, $s) = $this->getRecurringDuration();
1239 $now = Carbon::now(hurryt_tz());
1240 $startDate = Carbon::parse($this->recurringStartTime, hurryt_tz());
1241 $interval = (int)$this->recurringInterval;
1242 $endDate = $now->copy();
1243
1244 switch ($this->recurringFrequency) {
1245 case C::RECURRING_DAILY:
1246 $endDate->minute = $startDate->minute;
1247 $endDate->addDays(max($interval, $d))->addHours($interval)->addMinutes($m)->addSeconds($s);
1248 break;
1249 case C::RECURRING_WEEKLY:
1250 $endDate->minute = $startDate->minute;
1251 $endDate->addWeeks($interval)->addHours($h)->addMinutes($m)->addSeconds($s);
1252 break;
1253 case C::RECURRING_HOURLY:
1254 $endDate->minute = $startDate->minute;
1255 $endDate->addHours(max($interval, $h))->addMinutes($m)->addSeconds($s);
1256 break;
1257 case C::RECURRING_MINUTELY:
1258 $minutes = max($interval, $m);
1259 $endDate->addMinutes($minutes)->addSeconds($s);
1260 break;
1261 }
1262
1263 return $endDate;
1264 }
1265
1266 public function getRecurringFrequencyInSeconds()
1267 {
1268 $freq = 0;
1269 switch ($this->recurringFrequency) {
1270 case C::RECURRING_WEEKLY:
1271 $freq = WEEK_IN_SECONDS;
1272 break;
1273 case C::RECURRING_DAILY:
1274 $freq = DAY_IN_SECONDS;
1275 break;
1276 case C::RECURRING_HOURLY:
1277 $freq = HOUR_IN_SECONDS;
1278 break;
1279 case C::RECURRING_MINUTELY:
1280 $freq = MINUTE_IN_SECONDS;
1281 break;
1282 }
1283
1284 return $freq * (int)$this->recurringInterval;
1285 }
1286 /**
1287 * @return array [duration, pause, period] in seconds
1288 */
1289 public function getRecurringDurationInSeconds()
1290 {
1291 $duration = $this->durationInSeconds($this->getRecurringDuration());
1292 $period = $this->getRecurringFrequencyInSeconds();
1293 $pause = $period - $duration;
1294 return [$duration, max(0, $pause), $period];
1295 }
1296
1297 /**
1298 * Returns the recurrence end date based on the current date/time.
1299 *
1300 * @return Carbon
1301 */
1302 public function getRecurrenceEndDate($next = false)
1303 {
1304 try {
1305 $this->loadSettings();
1306 $now = Carbon::now(hurryt_tz());
1307 $startDate = Carbon::parse($this->recurringStartTime, hurryt_tz());
1308 $interval = (int)$this->recurringInterval;
1309
1310
1311 // Handle monthly recurring
1312 if ($this->isMonthlyRecurring()) {
1313
1314 $start_date_modified = $startDate->copy();
1315
1316 if ($startDate->day >= 28) {
1317 $start_date_modified->startOfMonth();
1318 }
1319
1320 $endDate = $now->copy()->addMonths($interval);
1321
1322 if ($next) {
1323 $endDate->addMonths($interval);
1324 }
1325
1326 if ($this->shouldEndRecurringByDate()) {
1327 $endDate = Carbon::parse($this->recurringUntil, hurryt_tz());
1328 }
1329
1330 $period = CarbonPeriod::create($start_date_modified, 'P' . $interval . 'M', $endDate);
1331
1332 if ($this->shouldEndRecurringByRecurrences()) {
1333 $period->setRecurrences($this->getRecurringRecurrences());
1334 }
1335
1336 if ($period->count() === 0) {
1337 return $period->getEndDate();
1338 }
1339
1340 $date = $period->first();
1341 $i = 0;
1342
1343 while ($period->valid()) {
1344
1345 $date = $period->current();
1346
1347 $isOverflow = $startDate->day >= $date->daysInMonth;
1348
1349 if ($this->isDayOfMonthRecurring() && $isOverflow) {
1350 $date->endOfMonth();
1351 }
1352
1353 if ($this->isDayOfWeekRecurring()) {
1354 $date->modifyWeekOfMonth($startDate);
1355 }
1356
1357 $date->setTimeFrom($startDate);
1358
1359 if ($date->greaterThan($now)) {
1360
1361 if (!$next || $i === 1) {
1362 return $date;
1363 }
1364
1365 $i++;
1366 }
1367
1368 $period->next();
1369 }
1370
1371 if ($this->shouldEndRecurringByDate() && $date->lessThan($now)) {
1372 return $period->getEndDate();
1373 }
1374
1375 return $date;
1376 } // Monthly recurring
1377
1378
1379 // Prevent minutely recurring from affecting performance
1380 // by updating the start date year and month to current ones.
1381 if ($this->isMinutelyRecurring()) {
1382 $startDate->year = $now->year;
1383 $startDate->month = $now->month;
1384 $startDate->day = $now->day;
1385 }
1386
1387
1388 list($duration, $pause) = $this->getRecurringDurationInSeconds();
1389
1390 $period = CarbonPeriod::start($startDate);
1391
1392 $period->seconds($duration + $pause);
1393
1394 if ($this->shouldEndRecurringByRecurrences()) {
1395 $period->setRecurrences($this->getRecurringRecurrences());
1396 } else {
1397 $endDate = $this->getRecurringPeriodEndDate();
1398 $period->until($endDate->addDays(7 * $interval));
1399 }
1400
1401 $date = false;
1402 $skip = false;
1403 while ($period->valid()) {
1404
1405 $date = $period->current();
1406 $skip_day = $this->should_skip_day($date);
1407 if ($date->greaterThan($now)) {
1408
1409 if ($skip_day) {
1410
1411 $skip = true;
1412
1413 if ($this->recurringUnselectedDaysAction === 'hide' || $this->isWeeklyRecurring()) {
1414 return false;
1415 }
1416
1417 $period->next();
1418 continue;
1419 }
1420
1421 if (!$skip) {
1422 $date = $pause ? $date->subSeconds($pause) : $date;
1423 }
1424 break;
1425 }
1426
1427 $period->next();
1428 }
1429
1430 if (empty($date)) {
1431 return $period->getEndDate();
1432 }
1433
1434 $stop_date = Carbon::parse($this->recurringUntil, hurryt_tz());
1435
1436 if ($this->shouldEndRecurringByDate() && $stop_date->lessThan($date)) {
1437 return $stop_date;
1438 }
1439 return $date;
1440 } catch (\Exception $e) {
1441 return false;
1442 }
1443 }
1444
1445 function should_skip_day($date)
1446 {
1447 $recur_days = array_map('intval', $this->recurringDays);
1448 return !in_array($date->format('w'), $recur_days);
1449 }
1450
1451 /**
1452 * Returns compaign publish datetime.
1453 *
1454 * @return mixed
1455 */
1456 public function getStartTimestamp()
1457 {
1458 return get_the_date($this->id);
1459 }
1460
1461 /**
1462 * Returns position in WooCommerce product page.
1463 * ::PROP
1464 *
1465 * @return mixed
1466 */
1467 public function getWcPosition()
1468 {
1469 $legacy = $this->get_prop_legacy('position');
1470
1471 if (!$legacy) {
1472 return $this->get_prop('wc_position');
1473 }
1474 $this->set_prop('wc_position', $legacy);
1475 $this->delete_prop('position');
1476
1477 return $legacy;
1478 }
1479
1480 private function get_prop_legacy($name)
1481 {
1482 return get_post_meta($this->id, $name, true);
1483 }
1484
1485 /**
1486 * Returns true if WooCommerce integration is enabled.
1487 * ::PROP
1488 *
1489 * @return mixed
1490 */
1491 public function getWcEnable()
1492 {
1493 $value = $this->get_prop('wc_enable', false);
1494
1495 if ($value === C::YES || $value === C::NO) {
1496 return $value;
1497 }
1498
1499 $legacy = filter_var($value, FILTER_VALIDATE_BOOLEAN);
1500 if ($legacy) {
1501 $this->set_prop('wc_enable', C::YES);
1502 } else {
1503 $this->set_prop('wc_enable', C::NO);
1504 }
1505
1506 return $this->get_prop('wc_enable');
1507 }
1508
1509 /**
1510 * Returns true if label should be displayed.
1511 *
1512 * @return mixed
1513 */
1514 public function getLabelVisibility()
1515 {
1516 $meta = 'label_visibility';
1517 $value = $this->get_prop($meta, false);
1518 if ($value === C::NO || $value === C::YES) {
1519 return $value;
1520 }
1521
1522 $legacy = filter_var(
1523 $this->get_prop_legacy('display_labels'),
1524 FILTER_VALIDATE_BOOLEAN
1525 );
1526 $legacy = $legacy ? C::YES : C::NO;
1527
1528 $this->set_prop($meta, $legacy);
1529
1530 $this->delete_prop('display_labels');
1531
1532 return $this->get_prop($meta);
1533 }
1534
1535 /**
1536 *
1537 * Returns restart type.
1538 *
1539 * @return int
1540 */
1541 public function getRestart()
1542 {
1543 return $this->get_prop('restart') ?: C::RESTART_NONE;
1544 }
1545
1546 // removeIf(pro)
1547 public function setRestart($value)
1548 {
1549 if ($value == C::RESTART_AFTER_DURATION) {
1550 return;
1551 }
1552 $this->set_prop('restart', $value);
1553 }
1554 // endRemoveIf(pro)
1555 /**
1556 * Returns WooCommerce products selection type.
1557 *
1558 * @return string
1559 */
1560 public function getWcProductsSelectionType()
1561 {
1562 $legacy = $this->get_prop_legacy('products_type');
1563 if (!$legacy) {
1564 return $this->get_prop('wc_products_selection_type');
1565 }
1566 $this->set_prop('wc_products_selection_type', $legacy);
1567 $this->delete_prop('products_type');
1568
1569 return $legacy;
1570 }
1571
1572 /**
1573 * Delete setting item.
1574 *
1575 * @param $name
1576 */
1577 public function delete_prop($name)
1578 {
1579 delete_post_meta($this->id, $name);
1580 }
1581
1582 /**
1583 * Returns products selection IDs.
1584 * ::PROP
1585 *
1586 * @return array
1587 */
1588 public function getWcProductsSelection()
1589 {
1590 $result = $this->get_prop_legacy('products');
1591 if (!$result) {
1592 $result = $this->get_prop('wc_products_selection');
1593 } else {
1594 $this->set_prop('wc_products_selection', $result);
1595 $this->delete_prop('products');
1596 }
1597
1598 return is_array($result) ? $result : [];
1599 }
1600
1601 /**
1602 * Store custom labels.
1603 * ::PROP
1604 *
1605 * @param $labels
1606 */
1607 public function setLabels($labels)
1608 {
1609 $labels = wp_parse_args($labels, $this->labels);
1610 update_post_meta($this->id, 'labels', $labels);
1611 }
1612
1613 public function getLabels()
1614 {
1615 return wp_parse_args($this->get_prop('labels'), $this->labels);
1616 }
1617
1618 /**
1619 * Returns true if compaign can be published.
1620 *
1621 * @return bool
1622 */
1623 public function is_active()
1624 {
1625 return get_post_status($this->id) === "publish"
1626 || get_post_status($this->id) === "future";
1627 }
1628
1629 /**
1630 * Returns trus if the compaign is scheduled.
1631 *
1632 * @return bool
1633 */
1634 public function is_scheduled()
1635 {
1636 $scheduled = get_post_status($this->get_id()) === "future";
1637 if ($scheduled) {
1638 return true;
1639 }
1640
1641 if ($this->is_recurring()) {
1642 $start_date = Carbon::parse($this->recurringStartTime, hurryt_tz());
1643 $now = Carbon::now(hurryt_tz());
1644 $now->second = $start_date->second;
1645
1646 if ($now->lessThan($start_date)) {
1647 return true;
1648 }
1649 }
1650
1651 return $scheduled;
1652 }
1653
1654 /**
1655 * Returns true if fixed campaign datetime is expired.
1656 *
1657 * @param string|null $date
1658 *
1659 * @return bool
1660 */
1661 public function is_one_time_expired($date = null)
1662 {
1663 $endDate = $date === null ? date_create($this->endDatetime) : $date;
1664 $today = date_create(current_time("mysql"));
1665
1666 return $endDate < $today;
1667 }
1668
1669 public function is_sticky_dismissed()
1670 {
1671 return isset($_COOKIE[Cookie_Detection::COOKIE_PREFIX . $this->get_id() . '_dismissed'])
1672 && $this->stickyBarDismissible === C::YES
1673 && $this->enableSticky === C::YES;
1674 }
1675
1676 /**
1677 * Returns compaign template wrapper.
1678 *
1679 * @param string
1680 *
1681 * @return string
1682 */
1683 public function wrap_template($content, $options = [])
1684 {
1685 $campaignBuilder = new CampaignBuilder($this);
1686
1687 return apply_filters('hurryt_campaign_content', $campaignBuilder->build($content, $options), $this->get_id());
1688 }
1689
1690 /*
1691 * Returns campaign template content.
1692 */
1693 public function build_template()
1694 {
1695 $campaignBuilder = new CampaignBuilder($this);
1696
1697 return $campaignBuilder->template();
1698 }
1699
1700 /**
1701 * ::PROP
1702 *
1703 * @param $value
1704 */
1705 public function setWcConditions($value)
1706 {
1707 $this->set_prop('_hurryt_wc_conditions', !empty($value) ? $value : []);
1708 }
1709
1710 /**
1711 * ::PROP
1712 *
1713 * @return mixed
1714 */
1715 public function getWcConditions()
1716 {
1717 return $this->get_prop('_hurryt_wc_conditions', false);
1718 }
1719
1720
1721 public function setRecurringUnselectedDaysAction($value)
1722 {
1723 $this->set_prop('_hurryt_recurring_unselected_days_action', $value);
1724 }
1725
1726 public function getRecurringUnselectedDaysAction()
1727 {
1728 return $this->get_prop('_hurryt_recurring_unselected_days_action');
1729 }
1730
1731 public function is_running()
1732 {
1733 // TODO: add support for weekly campaigns.
1734 if (
1735 $this->is_recurring()
1736 && !$this->can_recur_today()
1737 && ($this->recurringUnselectedDaysAction === 'hide' || $this->isWeeklyRecurring())
1738 ) {
1739 return false;
1740 }
1741
1742 $not_running = !$this->is_active() || $this->is_scheduled()
1743 || ($this->is_recurring() && empty($this->getRecurrenceEndDate()));
1744
1745
1746 return !$not_running;
1747 }
1748
1749 /**
1750 * Check if recurring or onetime campaign is expired.
1751 *
1752 * @return bool
1753 */
1754 public function is_expired()
1755 {
1756 return ($this->is_one_time() && $this->is_one_time_expired())
1757 || ($this->is_recurring() && $this->is_recurring_expired());
1758 }
1759
1760 public function get_mode_slug()
1761 {
1762 switch ($this->mode) {
1763 case C::MODE_EVERGREEN:
1764 return 'evergreen';
1765 case C::MODE_RECURRING:
1766 return 'recurring';
1767 case C::MODE_REGULAR:
1768 return 'one_time';
1769 }
1770 }
1771
1772 public function getReloadReset()
1773 {
1774 return filter_var($this->get_prop('reload_reset'), FILTER_VALIDATE_BOOLEAN);
1775 }
1776
1777 public function getDetectionMethods()
1778 {
1779 return array_map('intval', $this->get_prop('_hurryt_detection_methods'));
1780 }
1781
1782 public function setDetectionMethods($methods)
1783 {
1784 // removeIf(pro)
1785 if (is_array($methods)) {
1786 $methods = array_filter($methods, function ($m) {
1787 return (int)$m !== C::DETECTION_METHOD_USER_SESSION;
1788 });
1789 }
1790 // endRemoveIf(pro)
1791 $this->set_prop('_hurryt_detection_methods', $methods);
1792 }
1793
1794
1795
1796 /**
1797 * @return bool
1798 */
1799 private function isDailyRecurring()
1800 {
1801 return $this->recurringFrequency === C::RECURRING_DAILY;
1802 }
1803
1804 /**
1805 * @return bool
1806 */
1807 private function isWeeklyRecurring()
1808 {
1809 return $this->recurringFrequency === C::RECURRING_WEEKLY;
1810 }
1811
1812 /**
1813 * @return bool
1814 */
1815 private function isHourlyRecurring()
1816 {
1817 return $this->recurringFrequency === C::RECURRING_HOURLY;
1818 }
1819
1820 /**
1821 * @return bool
1822 */
1823 private function isMinutelyRecurring()
1824 {
1825 return $this->recurringFrequency === C::RECURRING_MINUTELY;
1826 }
1827 }
1828