PluginProbe
HurryTimer – An Scarcity and Urgency Countdown Timer for WordPress & WooCommerce / 2.2.16
HurryTimer – An Scarcity and Urgency Countdown Timer for WordPress & WooCommerce v2.2.16
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
← All changes | includes/Campaign.php +367 -1460 trunk2.2.16 View file →
@@ -1,13 +1,10 @@
1 1 <?php
2 2
3 3 namespace Hurrytimer;
4 4
5 -use DateInterval;
6 -use DatePeriod;
7 -use DateTime;
8 -use Hurrytimer\Dependencies\Carbon\Carbon;
9 -use Hurrytimer\Dependencies\Carbon\CarbonPeriod;
5 +use Carbon\Carbon;
6 +use Carbon\CarbonPeriod;
10 7 use Hurrytimer\Utils\Helpers;
11 8
12 9 class Campaign
13 10 {
@@ -42,12 +39,9 @@
42 39 *
43 40 * @var int
44 41 */
45 42 public $recurringDuration;
46 - public $recurringPauseDuration = ['days' => 0, 'hours' => 0, 'minutes' => 0, 'seconds' => 0];
47 43
48 - public $recurringDurationOption = 'none';
49 -
50 44 /**
51 45 * Recurrence start date/time
52 46 *
53 47 * @var string
@@ -94,11 +88,19 @@
94 88 * Recurrence allowed days
95 89 *
96 90 * @var array
97 91 */
98 - public $recurringDays = [0, 1, 2, 3, 4, 5, 6];
92 + public $recurringDays = [ 0, 1, 2, 3, 4, 5, 6 ];
99 93
100 94 /**
95 + * Recurrence timezone
96 + *
97 + * @todo Not used
98 + * @var mixed|string|void
99 + */
100 + public $recurringTimezone;
101 +
102 + /**
101 103 * Restart option after expiration.
102 104 *
103 105 * @see \Hurrytimer\CampaignRestart
104 106 *
@@ -104,9 +106,8 @@
104 106 *
105 107 * @var int
106 108 */
107 109 public $restart;
108 - public $restartDuration = ['days' => 0, 'hours' => 0, 'minutes' => 0, 'seconds' => 0];
109 110
110 111 /**
111 112 * Headline text.
112 113 *
@@ -157,10 +158,8 @@
157 158 * @var string
158 159 */
159 160 public $daysVisibility = C::YES;
160 161
161 - public $monthsVisibility = C::NO;
162 -
163 162 /**
164 163 * Show/hide hours block.
165 164 *
166 165 * @var string
@@ -181,9 +180,9 @@
181 180 */
182 181 public $secondsVisibility = C::YES;
183 182
184 183 /**
185 - * Regular campaign end date/time.
184 + * Regular compaign end datetime.
186 185 *
187 186 * @var string
188 187 */
189 188 public $endDatetime;
@@ -193,15 +192,14 @@
193 192 *
194 193 * @var array
195 194 */
196 195 public $labels
197 - = [
198 - 'months' => 'months',
199 - 'days' => 'days',
200 - 'hours' => 'hrs',
201 - 'minutes' => 'mins',
202 - 'seconds' => 'secs',
203 - ];
196 + = [
197 + 'days' => 'days',
198 + 'hours' => 'hrs',
199 + 'minutes' => 'mins',
200 + 'seconds' => 'secs',
201 + ];
204 202
205 203 /**
206 204 * Digit color.
207 205 *
@@ -271,12 +269,8 @@
271 269 * @var int
272 270 */
273 271 public $wcProductsSelectionType;
274 272
275 - /**
276 - * WooCommerce conditions.
277 - * @var array $wcConditions
278 - */
279 273 public $wcConditions;
280 274
281 275 /**
282 276 * Timer block border color.
@@ -305,9 +299,9 @@
305 299 *
306 300 * @var int
307 301 */
308 302
309 - public $blockSize = 'auto';
303 + public $blockSize = 50;
310 304
311 305 /**
312 306 * Block background color.
313 307 *
@@ -397,19 +391,10 @@
397 391 *
398 392 * @var array
399 393 */
400 394 public $stickyBarPages = [];
401 - public $stickyIncludeUrls = [];
402 - public $stickyExcludeUrls = [];
403 395
404 396 /**
405 - * Sticky exclude pages.
406 - *
407 - * @var array
408 - */
409 - public $stickyExcludePages = [];
410 -
411 - /**
412 397 * Where to display the sticky bar option
413 398 *
414 399 * @var string
415 400 */
@@ -421,36 +406,28 @@
421 406 * @var string
422 407 */
423 408 public $stickyBarDismissible = C::YES;
424 409
425 -
426 410 /**
427 - * If dismissed, re-open sticky bar after x days.
428 - * @var int
429 - */
430 - public $stickyBarDismissTimeout = 7;
431 -
432 - /**
433 411 * CTA settings.
434 412 *
435 413 * @var array
436 414 */
437 415 public $callToAction
438 - = [
439 - 'enabled' => C::NO,
440 - 'new_tab' => C::NO,
441 - 'url' => '',
442 - 'text' => 'Learn More',
443 - 'text_size' => 15,
444 - 'text_color' => '#fff',
445 - 'bg_color' => '#000',
446 - 'y_padding' => 10,
447 - 'x_padding' => 15,
448 - 'border_radius' => 3,
449 - 'spacing' => 5,
416 + = [
417 + 'enabled' => C::NO,
418 + 'new_tab' => C::NO,
419 + 'url' => '',
420 + 'text' => 'Learn More',
421 + 'text_size' => 15,
422 + 'text_color' => '#fff',
423 + 'bg_color' => '#000',
424 + 'y_padding' => 10,
425 + 'x_padding' => 15,
426 + 'border_radius' => 3,
427 + 'spacing' => 5,
428 + ];
450 429
451 - ];
452 -
453 430 /**
454 431 * Campaign elements display
455 432 * values: block,inline
456 433 *
@@ -486,48 +463,13 @@
486 463 * @var integer
487 464 */
488 465 public $campaignYPadding = 10;
489 466
490 - /**
491 - * Force evergreen timer to reset on reload.
492 - */
493 - public $reloadReset = false;
494 -
495 -
496 - /**
497 - *
498 - */
499 -
500 - public $detectionMethods = [
501 - C::DETECTION_METHOD_COOKIE,
502 - C::DETECTION_METHOD_IP,
503 - ];
504 -
505 - public $timezoneType = 'site';
506 - public $timezone;
507 -
508 -
509 - public $recurringMonthlyDayType = C::RECURRING_MONTHLY_DAY_OF_MONTH;
510 -
511 -
512 - public $recurringUnselectedDaysAction = 'skip';
513 -
514 - public $evergreenEndDay = 0;
515 - public $evergreenEndTime = '';
516 - public $evergreenEndType = 'duration';
517 -
518 - /**
519 - * Regular mode date ranges.
520 - * Each entry: ['start' => 'Y-m-d h:i A', 'end' => 'Y-m-d h:i A']
521 - * When only one range exists (or empty), falls back to the single endDatetime field.
522 - *
523 - * @var array
524 - */
525 - public $dateRanges = [];
526 -
527 - public function __construct($id)
467 + public function __construct( $id )
528 468 {
529 - $this->recurringStartTime = Carbon::now(hurryt_tz($this->getTimezone()))->format('Y-m-d h:i A');
469 + // Default recurring timezone.
470 + $this->recurringTimezone = Utils\Helpers::getSiteTimezone();
471 + $this->recurringStartTime = Carbon::now( hurryt_tz() )->format( 'Y-m-d h:i A' );
530 472 $this->id = $id;
531 473 }
532 474
533 475 /**
@@ -534,146 +476,74 @@
534 476 * Returns compaign post iD.
535 477 *
536 478 * @return int
537 479 */
538 - public function get_id()
480 + public function getId()
539 481 {
540 482 return $this->id;
541 483 }
542 484
543 -
544 - /**
545 - * Returns true if the current sticky bar can be displayed on the given page.
546 - *
547 - * @param $pageId
548 - *
549 - * @return bool
550 - * @throws \Exception
551 - */
552 - public function show_sticky_on_page($pageId)
485 + public function showStickyBarOn( $pageId )
553 486 {
554 - if ($this->enableSticky === C::NO) {
487 + if ( $this->enableSticky === C::NO ) {
555 488 return true;
556 489 }
557 490
558 - switch ($this->getStickyBarDisplayOn()):
559 -
491 + $can_show = false;
492 + switch ( $this->getStickyBarDisplayOn() ) {
493 + case 'all_pages':
494 + $can_show = true;
495 + break;
560 496 case 'wc_products_pages':
561 - if (!empty($pageId) && function_exists('is_product') && is_product()) {
562 -
497 + if ( function_exists( 'is_product' ) && is_product() ) {
563 498 $wc_campaign = new WCCampaign();
564 - if (($wc_campaign->has_campaign($this, $pageId))
565 - && $wc_campaign->met_conditions($this, $pageId)
566 - ) {
567 - return apply_filters('hurryt_show_sticky_bar', true, $this->get_id());
568 - }
499 + $can_show = ( $wc_campaign->hasActiveCampaign( $this, $pageId ) );
500 + } else {
501 + $can_show = false;
569 502 }
570 -
571 - return apply_filters('hurryt_show_sticky_bar', false, $this->get_id());
572 -
503 + break;
573 504 case 'specific_pages':
574 - $pageIds = $this->getStickyBarPages();
505 + $pages = $this->getStickyBarPages();
506 + $can_show = in_array( $pageId, $pages );
507 + break;
508 + }
575 509
576 - if (!empty($pageId) && !empty($pageIds) && in_array($pageId, $pageIds)) {
577 - return apply_filters('hurryt_show_sticky_bar', true, $this->get_id());
578 - }
510 + return apply_filters( 'hurryt_show_sticky_bar', $can_show, $this->getId() );
579 511
580 - foreach ($this->stickyIncludeUrls as $url) {
581 - $current_url = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/';
582 -
583 - if (Helpers::are_urls_match($url, $current_url)) {
584 - return apply_filters('hurryt_show_sticky_bar', true, $this->get_id());
585 - break;
586 - }
587 - }
588 - return apply_filters('hurryt_show_sticky_bar', false, $this->get_id());
589 -
590 - case 'exclude_pages':
591 - $pageIds = $this->getStickyExcludePages();
592 - if (!empty($pageId) && !empty($pageIds) && in_array($pageId, $pageIds)) {
593 - return apply_filters('hurryt_show_sticky_bar', false, $this->get_id());
594 - }
595 -
596 - foreach ($this->stickyExcludeUrls as $url) {
597 - $current_url = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/';
598 - if (Helpers::are_urls_match($current_url, $url)) {
599 - return apply_filters('hurryt_show_sticky_bar', false, $this->get_id());
600 - break;
601 - }
602 - }
603 -
604 - return apply_filters('hurryt_show_sticky_bar', true, $this->get_id());
605 - case 'all_pages':
606 - return apply_filters('hurryt_show_sticky_bar', true, $this->get_id());
607 - default:
608 - return apply_filters('hurryt_show_sticky_bar', false, $this->get_id());
609 - endswitch;
610 512 }
611 513
612 - /**
613 - * Returns sticky bar pages.
614 - *
615 - * @return array
616 - */
617 514 public function getStickyBarPages()
618 515 {
619 - $pagesIds = $this->get_prop('sticky_bar_pages');
516 + $pagesIds = $this->getRawSetting( 'sticky_bar_pages' );
620 517
621 - return array_filter(array_map('intval', $pagesIds));
518 + return array_filter( array_map( 'intval', $pagesIds ) );
622 519 }
623 520
624 - public function getStickyExcludePages()
521 + public function setStickyBarPages( $value )
625 522 {
626 - $page_ids = $this->get_prop('sticky_exclude_pages');
627 -
628 - return array_filter(array_map('intval', $page_ids));
523 + $this->storeRawSetting( 'sticky_bar_pages', $value );
629 524 }
630 525
631 - /**
632 - * Set sticky bar pages.
633 - *
634 - * @param $value
635 - */
636 - public function setStickyBarPages($value)
637 - {
638 - $this->set_prop('sticky_bar_pages', $value);
639 - }
640 -
641 - public function setStickyExcludePages($value)
642 - {
643 - $this->set_prop('sticky_exclude_pages', $value);
644 - }
645 -
646 - public function setStickyBarDismissTimeout($value)
647 - {
648 - $this->set_prop('sticky_bar_dismiss_timeout', intval($value));
649 - }
650 -
651 - public function getStickyBarDismissTimeout()
652 - {
653 - return intval($this->get_prop('sticky_bar_dismiss_timeout'));
654 - }
655 -
656 526 public function getStickyBarDisplayOn()
657 527 {
658 528
659 529 // backward compat.
660 - $all_pages = $this->get_prop('sticky_bar_show_on_all_pages', false);
530 + $all_pages = $this->getRawSetting( 'sticky_bar_show_on_all_pages', false );
661 531
662 - if ($all_pages === C::YES) {
663 - $this->setStickyBarDisplayOn('all_pages');
532 + if ( $all_pages === C::YES ) {
533 + $this->setStickyBarDisplayOn( 'all_pages' );
664 534 }
665 535
666 - $this->delete_prop('sticky_bar_show_on_all_pages');
536 + $this->deleteRawSetting( 'sticky_bar_show_on_all_pages' );
667 537
668 - $value = $this->get_prop('_hurryt_sticky_bar_display_on', false);
538 + $value = $this->getRawSetting( '_hurryt_sticky_bar_display_on', false );
669 539
670 - return !empty($value) ? $value : $this->stickyBarDisplayOn;
540 + return ! empty( $value ) ? $value : $this->stickyBarDisplayOn;
671 541 }
672 542
673 - public function setStickyBarDisplayOn($value)
543 + public function setStickyBarDisplayOn( $value )
674 544 {
675 - $this->set_prop('_hurryt_sticky_bar_display_on', $value);
545 + $this->storeRawSetting( '_hurryt_sticky_bar_display_on', $value );
676 546 }
677 547
678 548 /**
679 549 * Get raw setting.
@@ -682,33 +552,30 @@
682 552 * @param boolean
683 553 *
684 554 * @return mixed
685 555 */
686 - public function get_prop($name, $useDefault = true)
556 + public function getRawSetting( $name, $useDefault = true )
687 557 {
688 -
689 - $value = get_post_meta($this->id, $name, true);
690 - if (!empty($value)) {
558 + $value = get_post_meta( $this->id, $name, true );
559 + if ( ! empty( $value ) ) {
691 560 return $value;
692 561 }
693 - if ($useDefault) {
694 - $name = str_replace('_hurryt_', '', $name);
695 - return $this->{Helpers::snakeToCamelCase($name)};
562 + if ( $useDefault ) {
563 + return $this->{Helpers::snakeToCamelCase( $name )};
696 564 }
697 565
698 566 return $value;
699 567 }
700 568
701 - public function set_prop($name, $value)
569 + public function storeRawSetting( $name, $value )
702 570 {
703 - $value = !empty($value) ? $value : '';
704 - $_name = str_replace('_hurryt_', '', $name);
705 - $getterMethod = Helpers::snakeToCamelCase($_name);
706 - if (empty($value) && method_exists($this, $getterMethod)) {
571 + $value = ! empty( $value ) ? $value : '';
572 + $getterMethod = Helpers::snakeToCamelCase( $name );
573 + if ( empty( $value ) && method_exists( $this, $getterMethod ) ) {
707 574 $value = $this->{$getterMethod}();
708 575 }
709 576
710 - update_post_meta($this->id, $name, $value);
577 + update_post_meta( $this->id, $name, $value );
711 578 }
712 579
713 580 /**
714 581 * Bulk save for compaign settings.
@@ -714,265 +581,99 @@
714 581 * Bulk save for compaign settings.
715 582 *
716 583 * @param $data
717 584 */
718 - public function storeSettings($data)
585 + public function storeSettings( $data )
719 586 {
720 - if (!hurrytimer_allow_unfiltered_html()) {
721 - // Exclude fields that have their own sanitization to avoid double-processing
722 - // which can strip valid HTML (e.g. <a> tags in headline).
723 - $html_fields = ['headline'];
724 - $reserved = array_intersect_key($data, array_flip($html_fields));
725 - $data = wp_kses_post_deep(array_diff_key($data, array_flip($html_fields)));
726 - $data = array_merge($data, $reserved);
727 - }
728 -
729 - foreach ($data as $prop => $value) {
730 - $method = Helpers::snakeToCamelCase("set_{$prop}");
731 - if (method_exists($this, $method)) {
732 - if($prop === 'timezone') {
733 - $this->setTimezone($value, $data['timezone_type']);
734 - }
735 - else{
736 - $this->$method($value ?: $this->$prop);
737 - }
738 - } elseif (property_exists(__CLASS__, Helpers::snakeToCamelCase($prop))) {
739 - $this->set_prop($prop, $value);
587 + foreach ( $data as $prop => $value ) {
588 + $method = Helpers::snakeToCamelCase( "set_{$prop}" );
589 + if ( method_exists( $this, $method ) ) {
590 + $this->$method( $value ?: $this->$prop );
591 + } elseif ( property_exists( __CLASS__, Helpers::snakeToCamelCase( $prop ) ) ) {
592 + $this->storeRawSetting( $prop, $value );
740 593 }
741 594 }
742 - if (!isset($data['wc_conditions'])) {
743 - $this->setWcConditions([]);
595 + if ( ! isset( $data[ 'wc_conditions' ] ) ) {
596 + $this->setWcConditions( [] );
744 597 }
745 - if (!isset($data['sticky_bar_pages'])) {
746 - $this->setStickyBarPages([]);
598 + if ( ! isset( $data[ 'sticky_bar_pages' ] ) ) {
599 + $this->setStickyBarPages( [] );
747 600 }
748 - // Only use multiple deadlines if the toggle is active
749 - if (isset($data['use_multiple_deadlines']) && $data['use_multiple_deadlines'] === '0') {
750 - $this->setDateRanges([]);
751 - }
752 -
753 - CSS_Builder::get_instance()->generate_css();
601 + self::buildCss();
754 602 }
755 603
756 604 //removeIf(pro)
757 -
758 605 /**
759 606 * @param int $mode
760 - * ::PROP
761 607 */
762 - public function setMode($mode)
763 - {
764 - if ($mode == C::MODE_RECURRING) {
765 - return;
766 - }
767 - $this->set_prop('mode', $mode);
768 - }
608 + public function setMode($mode)
609 + {
610 + if($mode == C::MODE_RECURRING){
611 + return;
612 + }
613 + $this->storeRawSetting('mode',$mode);
614 + }
769 615 //endRemoveIf(pro)
770 -
771 616 /**
772 - * TODO: Settings should load automatically upon instance creation.
617 + * Build User CSS,
618 + * Then merge it with base one.
619 + *
620 + * @return void
773 621 */
774 - public function loadSettings()
622 + public static function buildCss()
775 623 {
776 624
777 - $reflection = new \ReflectionObject($this);
778 - // TODO: Refactor prop loading to `$this->get_{prop}()`
779 - foreach ($reflection->getProperties(\ReflectionProperty::IS_PUBLIC) as $prop) {
780 - $name = $prop->getName();
781 - $method = 'get' . ucfirst($name);
782 - if (method_exists($this, $method)) {
783 - $this->{$name} = $this->$method();
784 - } else {
785 - $this->{$name} = $this->get_prop(Helpers::camelToSnakeCase($name));
786 - }
787 - }
625 + // Build and return CSS
626 + ob_start();
627 + include HURRYT_DIR . 'includes/css.php';
628 + $css = ob_get_clean();
629 + $base = file_get_contents( HURRYT_DIR . '/assets/css/base.css' );
788 630
631 + // Merge built and base css.
632 + $base .= $css;
633 +
634 + // Save to public file.
635 + file_put_contents( HURRYT_DIR . '/assets/css/hurrytimer.css', $base );
789 636 }
790 637
791 - public function getHeadline()
638 + private function setRecurringDates( $dates )
792 639 {
793 - $content = $this->get_prop('_hurryt_headline', false);
794 -
795 - // User has set a custom headline
796 - if( !empty( $content ) ){ return $content; }
797 -
798 - // Backward compat.
799 - if (empty($content) && version_compare(get_option('hurrytimer_version'), '2.3.0', '<=')) {
800 - return get_the_title($this->id);
801 - }
802 -
803 - // Show default headline.
804 - return $this->headline;
640 + foreach ( $dates as $d ) {
641 + add_post_meta( $this->getId(), '_hurryt_recurring_dates', $d );
642 + }
805 643 }
806 644
807 - public function setHeadline($value)
645 + public function loadSettings()
808 646 {
809 - $value = $this->sanitize_headline_html( $value );
810 - $this->set_prop('_hurryt_headline', $value);
811 - }
812 647
813 - /**
814 - * Custom HTML sanitizer for the headline field.
815 - *
816 - * Allowed tags: span, div, a, input, br, s, p, strong, em
817 - * - style, class, id allowed on visual tags (CSS values are preserved as-is
818 - * except javascript:/expression()/vbscript: patterns are removed)
819 - * - <a>: href (blocked if javascript:), target, rel
820 - * - <input>: type, value, style (no event handlers)
821 - * - All on* event handler attributes stripped from every tag
822 - * - Dangerous tags (script, iframe, object, embed, form, link, meta) stripped entirely
823 - */
824 - private function sanitize_headline_html( $value ) {
825 - // Strip dangerous block-level tags and their contents.
826 - $value = preg_replace(
827 - '#<(script|iframe|object|embed|form|link|meta)[^>]*>.*?</\1>#is',
828 - '',
829 - $value
830 - );
831 - // Also strip self-closing variants.
832 - $value = preg_replace(
833 - '#<(script|iframe|object|embed|form|link|meta)[^>]*/?\s*>#is',
834 - '',
835 - $value
836 - );
837 -
838 - $allowed_tags = [ 'span', 'div', 'a', 'input', 'img', 'i', 'u', 'b', 'small', 'sup', 'sub', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'br', 's', 'p', 'strong', 'em' ];
839 -
840 - // Use DOMDocument to walk and filter the fragment.
841 - $doc = new \DOMDocument();
842 - // Suppress warnings from malformed HTML; wrap in a root node.
843 - @$doc->loadHTML(
844 - '<?xml encoding="utf-8"?><div id="__hurrytimer_root__">' . $value . '</div>',
845 - LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD
846 - );
847 -
848 - $root = $doc->getElementById('__hurrytimer_root__');
849 - if ( ! $root ) {
850 - return '';
648 + $reflection = new \ReflectionObject( $this );
649 + foreach ( $reflection->getProperties( \ReflectionProperty::IS_PUBLIC ) as $prop ) {
650 + $name = $prop->getName();
651 + $method = 'get' . ucfirst( $name );
652 + if ( method_exists( $this, $method ) ) {
653 + $this->{$name} = $this->$method();
654 + } else {
655 + $this->{$name} = $this->getRawSetting( Helpers::camelToSnakeCase( $name ) );
656 + }
851 657 }
852 -
853 - $this->_sanitize_headline_node( $root, $allowed_tags );
854 -
855 - // Serialize only the inner HTML of our wrapper.
856 - $html = '';
857 - foreach ( $root->childNodes as $child ) {
858 - $html .= $doc->saveHTML( $child );
859 - }
860 -
861 - return $html;
862 658 }
863 659
864 660 /**
865 - * Recursively sanitize a DOM node for the headline field.
661 + * Returns compaign headline.
662 + *
663 + * @return mixed
866 664 */
867 - private function _sanitize_headline_node( \DOMNode $node, array $allowed_tags ) {
868 - $remove = [];
869 -
870 - foreach ( $node->childNodes as $child ) {
871 - if ( $child->nodeType === XML_ELEMENT_NODE ) {
872 - $tag = strtolower( $child->nodeName );
873 -
874 - if ( ! in_array( $tag, $allowed_tags, true ) ) {
875 - // Replace disallowed tag with its children (unwrap).
876 - $remove[] = [ 'node' => $child, 'unwrap' => true ];
877 - continue;
878 - }
879 -
880 - // Sanitize attributes.
881 - $attrs_to_remove = [];
882 - foreach ( $child->attributes as $attr ) {
883 - $name = strtolower( $attr->name );
884 - $val = $attr->value;
885 -
886 - // Strip all event handlers.
887 - if ( strncmp( $name, 'on', 2 ) === 0 ) {
888 - $attrs_to_remove[] = $name;
889 - continue;
890 - }
891 -
892 - // Per-tag attribute rules.
893 - if ( $tag === 'a' ) {
894 - if ( ! in_array( $name, [ 'href', 'target', 'rel', 'style', 'class', 'id' ], true ) ) {
895 - $attrs_to_remove[] = $name;
896 - continue;
897 - }
898 - if ( $name === 'href' && preg_match( '#^\s*javascript:#i', $val ) ) {
899 - $attrs_to_remove[] = $name;
900 - continue;
901 - }
902 - } elseif ( $tag === 'img' ) {
903 - if ( ! in_array( $name, [ 'src', 'alt', 'width', 'height', 'style', 'class', 'id' ], true ) ) {
904 - $attrs_to_remove[] = $name;
905 - continue;
906 - }
907 - if ( $name === 'src' ) {
908 - // Block javascript: protocol
909 - if ( preg_match( '#^\s*javascript\s*:#i', $val ) ) {
910 - $attrs_to_remove[] = $name;
911 - continue;
912 - }
913 - // Block data: URIs except safe image types (no SVG — can contain scripts)
914 - if ( preg_match( '#^\s*data\s*:#i', $val ) && ! preg_match( '#^\s*data\s*:\s*image/(png|jpe?g|gif|webp)\s*[;,]#i', $val ) ) {
915 - $attrs_to_remove[] = $name;
916 - continue;
917 - }
918 - }
919 - } elseif ( $tag === 'input' ) {
920 - if ( ! in_array( $name, [ 'type', 'value', 'style', 'class', 'id' ], true ) ) {
921 - $attrs_to_remove[] = $name;
922 - continue;
923 - }
924 - } else {
925 - // Visual tags: allow style, class, id only.
926 - if ( ! in_array( $name, [ 'style', 'class', 'id' ], true ) ) {
927 - $attrs_to_remove[] = $name;
928 - continue;
929 - }
930 - }
931 -
932 - // Sanitize style values: remove JS injection patterns.
933 - if ( $name === 'style' ) {
934 - $val = preg_replace( '#javascript\s*:#i', '', $val );
935 - $val = preg_replace( '#expression\s*\(#i', '', $val );
936 - $val = preg_replace( '#vbscript\s*:#i', '', $val );
937 - $child->setAttribute( 'style', $val );
938 - }
939 - }
940 -
941 - foreach ( $attrs_to_remove as $a ) {
942 - $child->removeAttribute( $a );
943 - }
944 -
945 - // Recurse.
946 - $this->_sanitize_headline_node( $child, $allowed_tags );
947 - }
948 - }
949 -
950 - // Unwrap or remove collected nodes.
951 - foreach ( $remove as $item ) {
952 - $n = $item['node'];
953 - if ( $item['unwrap'] ) {
954 - while ( $n->firstChild ) {
955 - $node->insertBefore( $n->firstChild, $n );
956 - }
957 - }
958 - if ( $n->parentNode ) {
959 - $n->parentNode->removeChild( $n );
960 - }
961 - }
665 + public function getHeadline()
666 + {
667 + return $this->id ? get_the_title( $this->id ) : $this->headline;
962 668 }
963 669
964 - public function is_published()
670 + public function isPublished()
965 671 {
966 - return get_post_status($this->id) === "publish";
672 + return get_post_status( $this->id ) === "publish";
967 673 }
968 674
969 - /**
970 - * Check if WooCommerce is enabled for this campaign.
971 - *
972 - * @return bool
973 - */
974 - public function is_wc_enabled()
675 + public function isWcEnabled()
975 676 {
976 677 return $this->getWcEnable() === C::YES;
977 678 }
978 679
@@ -977,81 +678,59 @@
977 678 }
978 679
979 680 /**
980 681 * Check current countdown timer mode
981 - * ::PROP
982 682 *
983 683 * @return bool
984 684 */
985 - public function is_evergreen()
685 + public function isEvergreen()
986 686 {
987 - return $this->get_prop('mode') == C::MODE_EVERGREEN;
687 + return $this->getRawSetting( 'mode' ) == C::MODE_EVERGREEN;
988 688 }
989 689
990 - public function is_one_time()
690 + public function isRegular()
991 691 {
992 - return $this->get_prop('mode') == C::MODE_REGULAR;
692 + return $this->getRawSetting( 'mode' ) == C::MODE_REGULAR;
993 693 }
994 694
995 - public function is_recurring()
695 + public function isRecurring()
996 696 {
997 - return $this->get_prop('mode') == C::MODE_RECURRING;
998 - }
697 + return $this->getRawSetting( 'mode' ) == C::MODE_RECURRING;
999 698
1000 - public function getTimezone()
1001 - {
1002 -
1003 - if($this->timezoneType === 'site') {
1004 - return '';
1005 - }
1006 - return '';
1007 -
1008 699 }
1009 - public function setTimezone($timezone, $timezone_type)
1010 - {
1011 - if($timezone_type === 'site') {
1012 - $timezone = '';
1013 - }
1014 700
1015 - $this->set_prop('timezone', $timezone);
1016 - }
1017 701 /**
1018 702 * Save compaign endtime for regular mode.
1019 703 *
1020 704 * @param $date
1021 705 */
1022 - public function setEndDatetime($date)
706 + public function setEndDatetime( $date )
1023 707 {
1024 - // Sanitize the date input to prevent XSS
1025 - $date = sanitize_text_field($date);
1026 - $this->set_prop('end_datetime', $date ?: $this->defaultEndDatetime());
708 + $this->storeRawSetting( 'end_datetime', $date ?: $this->defaultEndDatetime() );
1027 709 }
1028 710
1029 711 /**
1030 712 * Returns default end datetime for regular mode.
1031 - * ::PROP
1032 713 *
1033 714 * @return false|string
1034 715 */
1035 716 private function defaultEndDatetime()
1036 717 {
1037 - return date('Y-m-d h:i A', strtotime('+1 week'));
718 + return date( 'Y-m-d h:i A', strtotime( '+1 week' ) );
1038 719 }
1039 720
1040 721 /**
1041 722 * Save digit color.
1042 - * ::PROP
1043 723 *
1044 724 * @param $color
1045 725 */
1046 - public function setDigitColor($color)
726 + public function setDigitColor( $color )
1047 727 {
1048 - $this->set_prop('digit_color', $color);
728 + $this->storeRawSetting( 'digit_color', $color );
1049 729 }
1050 730
1051 731 /**
1052 732 * Returns headline visibility.
1053 - * ::PROP
1054 733 *
1055 734 * @return mixed
1056 735 */
1057 736
@@ -1058,89 +737,84 @@
1058 737 public function getHeadlineVisibility()
1059 738 {
1060 739 $meta = 'headline_visibility';
1061 740
1062 - $value = $this->get_prop($meta, false);
1063 - if ($value === C::NO || $value === C::YES) {
741 + $value = $this->getRawSetting( $meta, false );
742 + if ( $value === C::NO || $value === C::YES ) {
1064 743 return $value;
1065 744 }
1066 745
1067 746 $legacyMeta = 'display_headline';
1068 - $legacyValue = filter_var($this->get_prop_legacy($legacyMeta), FILTER_VALIDATE_BOOLEAN);
747 + $legacyValue = filter_var( $this->getLegacySetting( $legacyMeta ),
748 + FILTER_VALIDATE_BOOLEAN );
1069 749 $legacyValue = $legacyValue ? C::YES : C::NO;
1070 750
1071 - $this->set_prop($meta, $legacyValue);
751 + $this->storeRawSetting( $meta, $legacyValue );
1072 752
1073 - $this->delete_prop($legacyMeta);
753 + $this->deleteRawSetting( $legacyMeta );
1074 754
1075 - return $this->get_prop($meta);
755 + return $this->getRawSetting( $meta );
1076 756 }
1077 757
1078 758 /**
1079 759 * Returns digit color.
1080 760 * Backward compat with older versions.
1081 - * ::PROP
1082 761 *
1083 762 * @return mixed
1084 763 */
1085 764 public function getDigitColor()
1086 765 {
1087 - $value = $this->get_prop('digit_color', false);
1088 - if ($value) {
766 + $value = $this->getRawSetting( 'digit_color', false );
767 + if ( $value ) {
1089 768 return $value;
1090 769 }
1091 770
1092 - $legacy = $this->get_prop_legacy('text_color');
1093 - if (!$legacy) {
771 + $legacy = $this->getLegacySetting( 'text_color' );
772 + if ( ! $legacy ) {
1094 773 return $this->digitColor;
1095 774 }
1096 775
1097 - $this->setDigitColor($legacy);
1098 - if (!$this->get_prop('label_color', false)) {
1099 - $this->setLabelColor($legacy);
776 + $this->setDigitColor( $legacy );
777 + if ( ! $this->getRawSetting( 'label_color', false ) ) {
778 + $this->setLabelColor( $legacy );
1100 779 }
1101 780
1102 - $this->delete_prop('text_color');
781 + $this->deleteRawSetting( 'text_color' );
1103 782
1104 - return $this->get_prop('digit_color');
783 + return $this->getRawSetting( 'digit_color' );
1105 784 }
1106 785
1107 - /**
1108 - * ::PROP
1109 - *
1110 - * @param $color
1111 - */
1112 - public function setLabelColor($color)
786 + public function setLabelColor( $color )
1113 787 {
1114 - $this->set_prop('label_color', $color);
788 + $this->storeRawSetting( 'label_color', $color );
1115 789 }
1116 790
1117 791 /**
1118 792 * Get label color
1119 - * ::PROP
1120 793 *
1121 794 * @return mixed
1122 795 */
1123 796 public function getLabelColor()
1124 797 {
1125 - $value = $this->get_prop('label_color', false);
1126 - if ($value) {
798 + $value = $this->getRawSetting( 'label_color', false );
799 + if ( $value ) {
1127 800 return $value;
1128 801 }
1129 802
1130 - $legacy = $this->get_prop_legacy('text_color');
1131 - if (!$legacy) {
803 + $legacy = $this->getLegacySetting( 'text_color' );
804 + if ( ! $legacy ) {
1132 805 return $this->labelColor;
1133 806 }
1134 807
1135 - $this->setLabelColor($legacy);
1136 - if (!$this->get_prop('digit_color', false)) {
1137 - $this->setDigitColor($legacy);
808 + $this->setLabelColor( $legacy );
809 + if ( ! $this->getRawSetting( 'digit_color', false ) ) {
810 + $this->setDigitColor( $legacy );
1138 811 }
1139 812
1140 - $this->delete_prop('text_color');
813 + $this->deleteRawSetting( 'text_color' );
1141 814
1142 - return $this->get_prop('label_color');
815 + return $this->getRawSetting( 'label_color' );
816 +
1143 817 }
1144 818
1145 819 /**
1146 820 *
@@ -1145,9 +819,8 @@
1145 819 /**
1146 820 *
1147 821 * Return timer digit size.
1148 822 * Backward comapt. with older versions.
1149 - * ::PROP
1150 823 *
1151 824 * @return int
1152 825 * @since 1.2.4
1153 826 */
@@ -1153,126 +826,77 @@
1153 826 */
1154 827 public function getDigitSize()
1155 828 {
1156 829 $meta = 'digit_size';
1157 - $value = $this->get_prop($meta, false);
1158 - if (!empty($value)) {
830 + $value = $this->getRawSetting( $meta, false );
831 + if ( ! empty( $value ) ) {
1159 832 return $value;
1160 833 }
1161 - $legacy = $this->get_prop_legacy('text_size');
834 + $legacy = $this->getLegacySetting( 'text_size' );
1162 835
1163 - if (empty($legacy)) {
836 + if ( empty( $legacy ) ) {
1164 837 return $this->digitSize;
1165 838 }
1166 839
1167 - $this->setDigitSize($legacy);
840 + $this->setDigitSize( $legacy );
1168 841
1169 - $this->delete_prop('text_size');
842 + $this->deleteRawSetting( 'text_size' );
1170 843
1171 - return $this->get_prop('digit_size');
844 + return $this->getRawSetting( 'digit_size' );
1172 845 }
1173 846
1174 847 /**
1175 848 * Save timer digit size.
1176 - * ::PROP
1177 849 *
1178 850 * @param $size
1179 851 */
1180 - public function setDigitSize($size)
852 + public function setDigitSize( $size )
1181 853 {
1182 - $this->set_prop('digit_size', $size);
854 + $this->storeRawSetting( 'digit_size', $size );
1183 855 }
1184 856
1185 857 /**
1186 858 * Save timer label size.
1187 - * ::PROP
1188 859 *
1189 860 * @param $size
1190 861 */
1191 - public function setLabelSize($size)
862 + public function setLabelSize( $size )
1192 863 {
1193 - $this->set_prop('label_size', $size);
864 + $this->storeRawSetting( 'label_size', $size );
1194 865 }
1195 866
1196 -
1197 - /**
1198 - * ::PROP
1199 - *
1200 - * @param $size
1201 - */
1202 - public function setHeadlineSize($size)
867 + public function setHeadlineSize( $size )
1203 868 {
1204 - $this->set_prop('headline_size', $size);
869 + $this->storeRawSetting( 'headline_size', $size );
1205 870 }
1206 871
1207 872 /**
1208 873 * Returns evergreen duration.
1209 - * ::PROP
1210 874 *
1211 875 * @return array
1212 876 */
1213 877 public function getDuration()
1214 878 {
1215 - $duration = (array)$this->get_prop('duration');
1216 - return array_pad(array_map('intval', $duration), 4, 0);
1217 - }
879 + $default = [ 0, 0, 0, 0 ];
880 + $duration = $this->getRawSetting( 'duration' );
881 + if ( is_array( $duration ) ) {
882 + $duration = array_merge( $duration, $default );
1218 883
1219 -
1220 -
1221 - public function getRestartDuration($asSeconds = false)
1222 - {
1223 - $duration = (array)$this->get_prop('hurryt_restart_duration', false);
1224 -
1225 - $duration = wp_parse_args($duration, $this->restartDuration);
1226 - foreach ($duration as $unit => $value) {
1227 - $duration[$unit] = (int)$value;
884 + return array_map( 'intval', $duration );
1228 885 }
1229 886
1230 - if ($asSeconds) {
1231 - $duration = $duration['days'] * DAY_IN_SECONDS +
1232 - $duration['hours'] * HOUR_IN_SECONDS +
1233 - $duration['minutes'] * MINUTE_IN_SECONDS +
1234 - $duration['seconds'];
1235 - }
1236 - return $duration;
887 + return $default;
1237 888 }
1238 889
1239 -
1240 - public function getRecurringPauseDuration($asSeconds = false)
890 + public function setRecurringDuration( $value )
1241 891 {
1242 - $duration = (array)$this->get_prop('_hurryt_recurring_pause_duration', false);
1243 -
1244 - $duration = wp_parse_args($duration, $this->recurringPauseDuration);
1245 - foreach ($duration as $unit => $value) {
1246 - $duration[$unit] = (int)$value;
1247 - }
1248 -
1249 - if ($asSeconds) {
1250 - $duration = $duration['days'] * DAY_IN_SECONDS +
1251 - $duration['hours'] * HOUR_IN_SECONDS +
1252 - $duration['minutes'] * MINUTE_IN_SECONDS +
1253 - $duration['seconds'];
1254 - }
1255 - return $duration;
892 + $this->storeRawSetting( '_hurryt_recurring_duration', $value );
1256 893 }
1257 894
1258 - public function setRestartDuration($duration_array)
895 + public function setRecurringPauseDuration( $value )
1259 896 {
1260 - $this->set_prop('hurryt_restart_duration', $duration_array);
897 + $this->storeRawSetting( '_hurryt_recurring_pause_duration', $value );
1261 898 }
1262 - /**
1263 - * ::PROP
1264 - *
1265 - * @param $value
1266 - */
1267 - public function setRecurringDuration($value)
1268 - {
1269 - $this->set_prop('_hurryt_recurring_duration', $value);
1270 - }
1271 - public function setRecurringPauseDuration($value)
1272 - {
1273 - $this->set_prop('_hurryt_recurring_pause_duration', $value);
1274 - }
1275 899
1276 900 /**
1277 901 * Returns evergreen duration.
1278 902 *
@@ -1279,11 +903,15 @@
1279 903 * @return array
1280 904 */
1281 905 public function getRecurringDuration()
1282 906 {
907 + $default = [ 0, 0, 0, 0 ];
908 + $duration = $this->getRawSetting( '_hurryt_recurring_duration', false );
909 + if ( is_array( $duration ) ) {
910 + return array_map( 'intval', array_merge( $duration, $default ) );
911 + }
1283 912
1284 - $duration = (array)$this->get_prop('_hurryt_recurring_duration', false);
1285 - return array_pad(array_map('intval', $duration), 4, 0);
913 + return $default;
1286 914 }
1287 915
1288 916 /**
1289 917 * Returns actions array.
@@ -1291,28 +919,28 @@
1291 919 * @return array
1292 920 */
1293 921 public function getActions()
1294 922 {
1295 - $legacy = $this->get_prop_legacy('end_action');
1296 - if ($legacy) {
1297 - $redirectUrl = $this->get_prop_legacy('redirect_url');
923 + $legacy = $this->getLegacySetting( 'end_action' );
924 + if ( $legacy ) {
925 + $redirectUrl = $this->getLegacySetting( 'redirect_url' );
1298 926 $actions = [
1299 927 [
1300 - 'id' => intval($legacy),
928 + 'id' => intval( $legacy ),
1301 929 'redirectUrl' => $redirectUrl,
1302 930 ],
1303 931 ];
1304 - $this->set_prop('actions', $actions);
1305 - $this->delete_prop('end_action');
1306 - $this->delete_prop('redirect_url');
932 + $this->storeRawSetting( 'actions', $actions );
933 + $this->deleteRawSetting( 'end_action' );
934 + $this->deleteRawSetting( 'redirect_url' );
1307 935
1308 - return $this->mergeActions($actions);
936 + return $this->mergeActions( $actions );
1309 937 }
1310 938
1311 - return $this->mergeActions($this->get_prop('actions'));
939 + return $this->mergeActions( $this->getRawSetting( 'actions' ) );
1312 940 }
1313 941
1314 - private function mergeActions($actions)
942 + private function mergeActions( $actions )
1315 943 {
1316 944 $defaults = [
1317 945 [
1318 946 'id' => C::ACTION_NONE,
@@ -1317,58 +945,23 @@
1317 945 [
1318 946 'id' => C::ACTION_NONE,
1319 947 'redirectUrl' => '',
1320 948 'message' => '',
1321 - 'coupon' => '',
1322 949 'wcStockStatus' => '',
1323 950 ],
1324 951 ];
1325 952
1326 - if (empty($actions) || !is_array($actions)) {
953 + if ( count( $actions ) === 0 ) {
1327 954 return $defaults;
1328 955 }
1329 956
1330 - return array_map(function ($action) use ($defaults) {
1331 - // Ensure we have an array
1332 - $action = (array)$action;
1333 -
1334 - // Parse with defaults to ensure all keys exist
1335 - $action = wp_parse_args($action, $defaults[0]);
1336 -
1337 - // Cast ID to integer
1338 - $action['id'] = (int)$action['id'];
957 + return array_map( function ( $action ) use ( $defaults ) {
958 + $action[ 'id' ] = (int)$action[ 'id' ];
1339 959
1340 - // Clean up message HTML if it exists
1341 - if (!empty($action['message'])) {
1342 - $action['message'] = preg_replace_callback(
1343 - '#<style(\s=?[\S]*)?>([\s\S]*)?<\/style>#',
1344 - function ($matches) {
1345 - return isset($matches[1], $matches[2])
1346 - ? '<style' . $matches[1] . '>' . preg_replace('/\<br(\s*)?\/?\>/i', '', $matches[2]) . '</style>'
1347 - : '';
1348 - },
1349 - $action['message']
1350 - );
1351 - }
1352 -
1353 - return $action;
1354 - }, $actions);
960 + return array_merge( $defaults[ 0 ], $action );
961 + }, $actions );
1355 962 }
1356 963
1357 -
1358 - public function get_action($needle)
1359 - {
1360 - $result = array_filter($this->actions, function ($action) use ($needle) {
1361 - return $needle === (int)$action['id'];
1362 - });
1363 -
1364 - if (empty($result)) {
1365 - return false;
1366 - }
1367 -
1368 - return array_shift($result);
1369 - }
1370 -
1371 964 /**
1372 965 * Returns evergreen duration in seconds.
1373 966 *
1374 967 * @param array $duration
@@ -1374,17 +967,16 @@
1374 967 * @param array $duration
1375 968 *
1376 969 * @return int
1377 970 */
1378 - public function durationInSeconds($duration = [])
971 + public function durationArrayToSeconds( $duration = [] )
1379 972 {
1380 - list($d, $h, $m, $s) = empty($duration) ? $this->getDuration() : $duration;
973 + list( $d, $h, $m, $s ) = empty( $duration ) ? $this->getDuration() : $duration;
1381 974
1382 - return
1383 - $d * DAY_IN_SECONDS +
1384 - $h * HOUR_IN_SECONDS +
1385 - $m * MINUTE_IN_SECONDS +
1386 - $s;
975 + return $d * DAY_IN_SECONDS +
976 + $h * HOUR_IN_SECONDS +
977 + $m * MINUTE_IN_SECONDS +
978 + $s;
1387 979 }
1388 980
1389 981 /**
1390 982 * Returns end datetime.
@@ -1392,9 +984,9 @@
1392 984 * @return string
1393 985 */
1394 986 public function getEndDatetime()
1395 987 {
1396 - return $this->get_prop('end_datetime') ?: $this->defaultEndDatetime();
988 + return $this->getRawSetting( 'end_datetime' ) ?: $this->defaultEndDatetime();
1397 989 }
1398 990
1399 991 /**
1400 992 * Return true if the recurrence is expired.
@@ -1400,68 +992,40 @@
1400 992 * Return true if the recurrence is expired.
1401 993 *
1402 994 * @return bool
1403 995 */
1404 - public function is_recurring_expired()
996 + public function isRecurringExpired()
1405 997 {
1406 - $endDate = $this->getRecurrenceEndDate();
1407 -
1408 - if (!($endDate instanceof Carbon)) {
998 + $deadline = $this->getRecurringDeadline();
999 + if ( ! ( $deadline instanceof Carbon ) ) {
1409 1000 return false;
1410 1001 }
1411 1002
1412 - $now = Carbon::now(hurryt_tz($this->timezone));
1003 + $now = Carbon::now( hurryt_tz() );
1413 1004
1414 - return $now->greaterThan($endDate);
1005 + return $now->isAfter( $deadline );
1415 1006 }
1416 1007
1417 - /**
1418 - * Calculate next recurrence.
1419 - *
1420 - * @return mixed
1421 - * @throws \Exception
1422 - */
1423 - function timeToNextRecurrence()
1008 + function getTimeToNextRecurrence()
1424 1009 {
1425 1010 $this->loadSettings();
1011 + $interval = absint( $this->recurringInterval );
1426 1012
1427 - if ($this->isMonthlyRecurring()) {
1428 - $current = $this->getRecurrenceEndDate();
1013 + if ( $this->recurringFrequency === C::RECURRING_DAILY ) {
1014 + $frequencyInSeconds = DAY_IN_SECONDS;
1015 + } elseif ( $this->recurringFrequency === C::RECURRING_WEEKLY ) {
1016 + $frequencyInSeconds = WEEK_IN_SECONDS;
1429 1017
1430 - $next = $this->getRecurrenceEndDate(true);
1018 + } elseif ( $this->recurringFrequency === C::RECURRING_HOURLY ) {
1019 + $frequencyInSeconds = HOUR_IN_SECONDS;
1431 1020
1432 - $duration = $this->durationInSeconds($this->getRecurringDuration());
1433 - $use_custom = $this->recurringDurationOption === 'custom' && $duration > 0;
1434 -
1435 - if ($use_custom) {
1436 - $time_remaining = $next->diffInSeconds($current);
1437 - } else {
1438 - $time_remaining = $this->getRecurringPauseDuration(true);
1439 - }
1440 - return $time_remaining;
1021 + } else {
1022 + $frequencyInSeconds = MINUTE_IN_SECONDS;
1441 1023 }
1442 1024
1443 - list($duration, $pause, $period) = $this->getRecurringDurationInSeconds();
1025 + $duration = $this->durationArrayToSeconds( $this->getRecurringDuration() );
1444 1026
1445 - $t = $pause ?: 0;
1446 -
1447 - $waitBeforeRestart = $this->getRecurringPauseDuration(true);
1448 -
1449 - $t += $waitBeforeRestart;
1450 -
1451 - if (!$this->has_skipped_days()) {
1452 - return $t;
1453 - }
1454 -
1455 - if ($this->isWeeklyRecurring()) {
1456 - return $t;
1457 - }
1458 -
1459 - if ($this->recurringUnselectedDaysAction === 'hide') {
1460 - return $t;
1461 - }
1462 -
1463 - return $waitBeforeRestart;
1027 + return max( 0, ( $frequencyInSeconds * $interval ) - $duration );
1464 1028 }
1465 1029
1466 1030 /**
1467 1031 * Return true if the current campaign can recur today.
@@ -1467,347 +1031,99 @@
1467 1031 * Return true if the current campaign can recur today.
1468 1032 *
1469 1033 * @return bool
1470 1034 */
1471 - public function can_recur_today()
1035 + public function canRecurToday()
1472 1036 {
1473 1037 $this->loadSettings();
1038 + $day = absint( Carbon::now( hurryt_tz() )->format( 'w' ) );
1039 + $recur = in_array( $day, array_map( 'absint', $this->recurringDays ) );
1474 1040
1475 - $day = absint(Carbon::now(hurryt_tz($this->timezone))->format('w'));
1476 -
1477 - $recur = in_array($day, array_map('absint', $this->recurringDays));
1478 -
1479 - return apply_filters('hurryt_recur_today', $recur, $this->get_id());
1041 + return apply_filters( 'hurryt_recur_today', $recur, $this->getId() );
1480 1042 }
1481 1043
1482 - public function isMonthlyRecurring()
1044 + /**
1045 + * Return current recurrence date
1046 + *
1047 + * @return \Carbon\CarbonInterface|null
1048 + */
1049 + public function getRecurringCurrentDate()
1483 1050 {
1484 - return $this->recurringFrequency === C::RECURRING_MONTHLY;
1485 - }
1051 + $this->loadSettings();
1052 + $dtStart = Carbon::parse( $this->recurringStartTime, hurryt_tz() );
1053 + $now = Carbon::now( hurryt_tz() );
1486 1054
1487 - public function isDayOfWeekRecurring()
1488 - {
1055 + $range = CarbonPeriod::since( $dtStart );
1056 + $interval = absint( $this->recurringInterval );
1489 1057
1490 - return $this->recurringMonthlyDayType === C::RECURRING_MONTHLY_DAY_OF_WEEK;
1491 - }
1058 + if ( $this->recurringFrequency === C::RECURRING_DAILY ) {
1059 + $range->days( $interval );
1060 + } elseif ( $this->recurringFrequency === C::RECURRING_WEEKLY ) {
1061 + $range->weeks( $interval );
1062 + } elseif ( $this->recurringFrequency === C::RECURRING_HOURLY ) {
1063 + $range->hours( $interval );
1064 + } else {
1065 + $range->minutes( $interval );
1066 + }
1492 1067
1493 - public function isDayOfMonthRecurring()
1494 - {
1495 - return $this->recurringMonthlyDayType === C::RECURRING_MONTHLY_DAY_OF_MONTH;
1496 - }
1068 + // Recurs forever
1069 + if ( absint( $this->recurringEnd ) === C::RECURRING_END_NEVER ) {
1070 + $range->until( $now );
1071 + // Recurs until
1072 + } elseif ( absint( $this->recurringEnd ) === C::RECURRING_END_TIME ) {
1073 + $range->until( Carbon::parse( $this->recurringUntil, hurryt_tz() ) );
1497 1074
1498 - public function shouldEndRecurringByDate()
1499 - {
1500 - return absint($this->recurringEnd) === C::RECURRING_END_TIME;
1501 - }
1502 - public function shouldEndRecurringByRecurrences()
1503 - {
1504 - return (int)$this->recurringEnd === C::RECURRING_END_OCCURRENCES;
1505 - }
1075 + $range->addFilter( function ( $date ) use ( $now ) {
1076 + /**
1077 + * @var Carbon $date
1078 + */
1079 + return $date->isBefore( $now );
1080 + } );
1506 1081
1507 - public function shouldRecurForever()
1508 - {
1509 - return (int)$this->recurringEnd === C::RECURRING_END_NEVER;
1510 - }
1082 + // Recurs N times.
1083 + } elseif ( absint( $this->recurringEnd ) === C::RECURRING_END_OCCURRENCES ) {
1511 1084
1512 - public function getRecurringRecurrences()
1513 - {
1514 - $num = (int)$this->recurringCount;
1515 - return ++$num;
1516 - }
1085 + // Virtual endDate
1086 + $range->until( Carbon::now( hurryt_tz() )->addCenturies( 1 ) );
1517 1087
1088 + // Limit to the number of occurences in `$this->recurringCount`
1089 + $range->setRecurrences( absint( $this->recurringCount ) );
1518 1090
1519 - public function getRecurringPeriodEndDate()
1520 - {
1521 - list($d, $h, $m, $s) = $this->getRecurringDuration();
1522 - $now = Carbon::now(hurryt_tz($this->timezone));
1523 - $startDate = Carbon::parse($this->recurringStartTime, hurryt_tz($this->timezone));
1524 - $interval = (int)$this->recurringInterval;
1525 - $endDate = $now->copy();
1091 + // Set the endDate to the last occurence date
1092 + $range->setEndDate( $range->last() );
1093 + $range->addFilter( function ( $date ) use ( $now ) {
1526 1094
1527 - switch ($this->recurringFrequency) {
1528 - case C::RECURRING_DAILY:
1529 - $endDate->minute = $startDate->minute;
1530 - $endDate->addDays(max($interval, $d))->addHours($interval)->addMinutes($m)->addSeconds($s);
1531 - break;
1532 - case C::RECURRING_WEEKLY:
1533 - $endDate->minute = $startDate->minute;
1534 - $endDate->addWeeks($interval)->addHours($h)->addMinutes($m)->addSeconds($s);
1535 - break;
1536 - case C::RECURRING_HOURLY:
1537 - $endDate->minute = $startDate->minute;
1538 - $endDate->addHours(max($interval, $h))->addMinutes($m)->addSeconds($s);
1539 - break;
1540 - case C::RECURRING_MINUTELY:
1541 - $minutes = max($interval, $m);
1542 - $endDate->addMinutes($minutes)->addSeconds($s);
1543 - break;
1544 - }
1095 + /**
1096 + * @var Carbon $date
1097 + */
1098 + return $date->isBefore( $now );
1099 + } );
1545 1100
1546 - return $endDate;
1547 - }
1548 -
1549 - public function getRecurringFrequencyInSeconds()
1550 - {
1551 - $freq = 0;
1552 - switch ($this->recurringFrequency) {
1553 - case C::RECURRING_WEEKLY:
1554 - $freq = WEEK_IN_SECONDS;
1555 - break;
1556 - case C::RECURRING_DAILY:
1557 - $freq = DAY_IN_SECONDS;
1558 - break;
1559 - case C::RECURRING_HOURLY:
1560 - $freq = HOUR_IN_SECONDS;
1561 - break;
1562 - case C::RECURRING_MINUTELY:
1563 - $freq = MINUTE_IN_SECONDS;
1564 - break;
1565 1101 }
1566 1102
1567 - return $freq * (int)$this->recurringInterval;
1103 + return $range->last();
1568 1104 }
1569 - /**
1570 - * @return array [duration, pause, period] in seconds
1571 - */
1572 - public function getRecurringDurationInSeconds()
1573 - {
1574 - $duration = $this->durationInSeconds($this->getRecurringDuration());
1575 - $period = $this->getRecurringFrequencyInSeconds();
1576 - $pause = $period - $duration;
1577 - return [$duration, max(0, $pause), $period];
1578 - }
1579 1105
1580 1106 /**
1581 - * Returns the recurrence end date based on the current date/time.
1582 - *
1583 - * @param bool $next Return the next recurrence end date.
1584 - *
1585 - * @return Carbon
1107 + * Get the next/current deadline based on the current date.
1108 + *
1109 + * @return null|Carbon
1586 1110 */
1587 -
1588 - public function getRecurrenceEndDate($next = false)
1111 + public function getRecurringDeadline()
1589 1112 {
1590 -
1591 - $cached_key = $next ? 'recur_end_date__next' : 'recur_end_date';
1592 - $cached = wp_cache_get($cached_key, 'hurrytimer');
1593 - if ($cached instanceof Carbon) {
1594 - return $cached;
1113 + $date = $this->getRecurringCurrentDate();
1114 + if ( ! $date ) {
1115 + return null;
1595 1116 }
1117 + $date = clone Carbon::instance( $date );
1596 1118
1597 - try {
1119 + $duration = $this->durationArrayToSeconds( $this->getRecurringDuration() );
1598 1120
1599 - $this->loadSettings();
1600 - $now = Carbon::now(hurryt_tz($this->timezone));
1601 - $startDate = Carbon::parse($this->recurringStartTime, hurryt_tz($this->timezone));
1602 - $interval = (int)$this->recurringInterval;
1121 + $date->addSeconds( $duration );
1603 1122
1604 -
1605 - // Handle monthly recurring
1606 - if ($this->isMonthlyRecurring()) {
1607 - $start_date_modified = $startDate->copy();
1608 -
1609 - if ($startDate->day >= 28) {
1610 - $start_date_modified->startOfMonth();
1611 - }
1612 -
1613 - $endDate = $now->copy()->addMonths($interval);
1614 -
1615 - if ($next) {
1616 - $endDate->addMonths($interval);
1617 - }
1618 -
1619 - if ($this->shouldEndRecurringByDate()) {
1620 - $endDate = Carbon::parse($this->recurringUntil, hurryt_tz($this->timezone));
1621 - }
1622 -
1623 - if ($this->isDayOfWeekRecurring()) {
1624 - $endDate = $endDate->addMonths($interval);
1625 - }
1626 -
1627 - $period = CarbonPeriod::create($start_date_modified, 'P' . $interval . 'M', $endDate);
1628 -
1629 - if ($this->shouldEndRecurringByRecurrences()) {
1630 - $period->setRecurrences($this->getRecurringRecurrences());
1631 - }
1632 -
1633 - if ($period->count() === 0) {
1634 - return $period->getEndDate();
1635 - }
1636 -
1637 - $dates = iterator_to_array($period);
1638 -
1639 - $dates = array_map(function ($date) use ($startDate) {
1640 - $is_overflow = $startDate->day >= $date->daysInMonth;
1641 - if ($this->isDayOfMonthRecurring() && $is_overflow) {
1642 - $date->endOfMonth();
1643 - }
1644 - if ($this->isDayOfWeekRecurring()) {
1645 - $date->modifyWeekOfMonth($startDate);
1646 - }
1647 - $date->setTimeFrom($startDate);
1648 -
1649 - return $date;
1650 - }, $dates);
1651 -
1652 -
1653 - $out_date = end($dates) ?: $period->getEndDate();
1654 -
1655 - reset($dates);
1656 -
1657 - foreach ($dates as $i => $date) {
1658 - $duration = $this->durationInSeconds($this->getRecurringDuration());
1659 - $custom = $this->recurringDurationOption === 'custom' && $duration > 0;
1660 -
1661 - if ($custom && !$next) {
1662 - $custom_end_date = $date->copy()->addSeconds($duration);
1663 -
1664 - if ($custom_end_date->greaterThan($now)) {
1665 - $out_date = $custom_end_date;
1666 -
1667 - break;
1668 - }
1669 -
1670 - $next_date = isset($dates[$i + 1]) ? $dates[$i + 1] : $period->getEndDate();
1671 -
1672 - if ($now->lessThan($next_date)) {
1673 - $out_date = $custom_end_date;
1674 - break;
1675 - }
1676 -
1677 - continue;
1678 - }
1679 -
1680 - if ($date->greaterThan($now)) {
1681 - $pause_duration = $this->getRecurringPauseDuration(true);
1682 -
1683 - if ($next && !$custom) {
1684 - $out_date = isset($dates[$i + 1]) ? $dates[$i + 1] : $out_date;
1685 -
1686 - if ($pause_duration > 0) {
1687 - $out_date = $date;
1688 - }
1689 - } else {
1690 - $out_date = $date;
1691 - $prev_end_date = isset($dates[$i - 1]) ? $dates[$i - 1] : null;
1692 - if ($prev_end_date && $prev_end_date->copy()->diffInSeconds($now) < $pause_duration) {
1693 - $out_date = $prev_end_date;
1694 - }
1695 - }
1696 -
1697 - break;
1698 - }
1699 - }
1700 - if ($this->shouldEndRecurringByDate() && $out_date->lessThan($now)) {
1701 - // TODO: Use the last recurrence instead of `getEndDate()`
1702 - $out_date = $period->getEndDate();
1703 - }
1704 -
1705 - wp_cache_set($cached_key, $out_date, 'hurrytimer');
1706 -
1707 - return $out_date;
1708 - } // Monthly recurring
1709 -
1710 -
1711 - // Prevent minutely recurring from affecting performance
1712 - // by updating the start date year and month to current ones.
1713 - if ($this->isMinutelyRecurring()) {
1714 - $startDate->year = $now->year;
1715 - $startDate->month = $now->month;
1716 - $startDate->day = $now->day;
1717 - }
1718 -
1719 -
1720 - list($duration, $pause) = $this->getRecurringDurationInSeconds();
1721 -
1722 - $period = CarbonPeriod::start($startDate, true);
1723 -
1724 - $period->seconds($duration + $pause);
1725 -
1726 - if ($this->shouldEndRecurringByRecurrences()) {
1727 - $period->setRecurrences($this->getRecurringRecurrences());
1728 - } else {
1729 - $endDate = $this->getRecurringPeriodEndDate();
1730 - $dateperiod_interval = new DateInterval('PT' .($duration + $pause) . 'S');
1731 - $period = new DatePeriod($startDate->toDateTime(), $dateperiod_interval, $endDate->addDays(7 * $interval)->toDateTime());
1732 - // $period->until($endDate->addDays(7 * $interval));
1733 -
1734 - }
1735 -
1736 - $date = null;
1737 -
1738 - $i = 0;
1739 -
1740 - $pause_duration = $this->getRecurringPauseDuration(true);
1741 -
1742 - foreach ($period as $start) {
1743 -
1744 - $start = Carbon::instance($start);
1745 -
1746 - $end = $start->copy()->addSeconds($this->getRecurringFrequencyInSeconds() - 1);
1747 -
1748 - if ($now->isBetween($start, $end->copy()->addSeconds($pause_duration))) {
1749 - $date = $start->copy()->addSeconds($duration - 1);
1750 - if (!in_array($date->format('w'), $this->recurringDays)) {
1751 - continue;
1752 - }
1753 - break;
1754 - }
1755 -
1756 - if ($now->isBefore($end->copy()->addSeconds($pause_duration))) {
1757 - $date = $start;
1758 - if (!in_array($date->format('w'), $this->recurringDays)) {
1759 - continue;
1760 - }
1761 - break;
1762 - }
1763 - }
1764 -
1765 - if (empty($date)) {
1766 - return $period->getEndDate();
1767 - }
1768 -
1769 - $stop_date = Carbon::parse($this->recurringUntil, hurryt_tz($this->timezone));
1770 -
1771 - if ($this->shouldEndRecurringByDate() && $stop_date->lessThan($date)) {
1772 - return $stop_date;
1773 - }
1774 -
1775 - return $date;
1776 - } catch (\Exception $e) {
1777 - return false;
1778 - }
1123 + return $date;
1779 1124 }
1780 1125
1781 -
1782 - function should_hide_today($date, $now)
1783 - {
1784 - return ($date->format('w') === $now->format('w')) && ($this->isWeeklyRecurring() || $this->recurringUnselectedDaysAction === 'hide');
1785 - }
1786 - function has_skipped_days()
1787 - {
1788 - $recur_days = array_map('intval', $this->recurringDays);
1789 - return count($recur_days) < 7;
1790 - }
1791 - function can_recur_on($date)
1792 - {
1793 - $recur_days = array_map('intval', $this->recurringDays);
1794 -
1795 - if (in_array($date->format('w'), $recur_days)) {
1796 - return true;
1797 - }
1798 -
1799 - if (($date->isStartOfDay() && in_array($this->get_previous_day($date->format('w')), $recur_days))) {
1800 - return true;
1801 - }
1802 -
1803 - return false;
1804 - }
1805 - private function get_previous_day($current_day)
1806 - {
1807 - $current_day === 0 ? 6 : ($current_day - 1);
1808 - }
1809 -
1810 1126 /**
1811 1127 * Returns compaign publish datetime.
1812 1128 *
1813 1129 * @return mixed
@@ -1813,57 +1129,55 @@
1813 1129 * @return mixed
1814 1130 */
1815 1131 public function getStartTimestamp()
1816 1132 {
1817 - return get_the_date($this->id);
1133 + return get_the_date( $this->id );
1818 1134 }
1819 1135
1820 1136 /**
1821 1137 * Returns position in WooCommerce product page.
1822 - * ::PROP
1823 1138 *
1824 1139 * @return mixed
1825 1140 */
1826 1141 public function getWcPosition()
1827 1142 {
1828 - $legacy = $this->get_prop_legacy('position');
1143 + $legacy = $this->getLegacySetting( 'position' );
1829 1144
1830 - if (!$legacy) {
1831 - return $this->get_prop('wc_position');
1145 + if ( ! $legacy ) {
1146 + return $this->getRawSetting( 'wc_position' );
1832 1147 }
1833 - $this->set_prop('wc_position', $legacy);
1834 - $this->delete_prop('position');
1148 + $this->storeRawSetting( 'wc_position', $legacy );
1149 + $this->deleteRawSetting( 'position' );
1835 1150
1836 1151 return $legacy;
1837 1152 }
1838 1153
1839 - private function get_prop_legacy($name)
1154 + private function getLegacySetting( $name )
1840 1155 {
1841 - return get_post_meta($this->id, $name, true);
1156 + return get_post_meta( $this->id, $name, true );
1842 1157 }
1843 1158
1844 1159 /**
1845 1160 * Returns true if WooCommerce integration is enabled.
1846 - * ::PROP
1847 1161 *
1848 1162 * @return mixed
1849 1163 */
1850 1164 public function getWcEnable()
1851 1165 {
1852 - $value = $this->get_prop('wc_enable', false);
1166 + $value = $this->getRawSetting( 'wc_enable', false );
1853 1167
1854 - if ($value === C::YES || $value === C::NO) {
1168 + if ( $value === C::YES || $value === C::NO ) {
1855 1169 return $value;
1856 1170 }
1857 1171
1858 - $legacy = filter_var($value, FILTER_VALIDATE_BOOLEAN);
1859 - if ($legacy) {
1860 - $this->set_prop('wc_enable', C::YES);
1172 + $legacy = filter_var( $value, FILTER_VALIDATE_BOOLEAN );
1173 + if ( $legacy ) {
1174 + $this->storeRawSetting( 'wc_enable', C::YES );
1861 1175 } else {
1862 - $this->set_prop('wc_enable', C::NO);
1176 + $this->storeRawSetting( 'wc_enable', C::NO );
1863 1177 }
1864 1178
1865 - return $this->get_prop('wc_enable');
1179 + return $this->getRawSetting( 'wc_enable' );
1866 1180 }
1867 1181
1868 1182 /**
1869 1183 * Returns true if label should be displayed.
@@ -1872,24 +1186,21 @@
1872 1186 */
1873 1187 public function getLabelVisibility()
1874 1188 {
1875 1189 $meta = 'label_visibility';
1876 - $value = $this->get_prop($meta, false);
1877 - if ($value === C::NO || $value === C::YES) {
1190 + $value = $this->getRawSetting( $meta, false );
1191 + if ( $value === C::NO || $value === C::YES ) {
1878 1192 return $value;
1879 1193 }
1880 1194
1881 - $legacy = filter_var(
1882 - $this->get_prop_legacy('display_labels'),
1883 - FILTER_VALIDATE_BOOLEAN
1884 - );
1195 + $legacy = filter_var( $this->getLegacySetting( 'display_labels' ),
1196 + FILTER_VALIDATE_BOOLEAN );
1885 1197 $legacy = $legacy ? C::YES : C::NO;
1198 + $this->storeRawSetting( $meta, $legacy );
1199 + $this->deleteRawSetting( 'display_labels' );
1886 1200
1887 - $this->set_prop($meta, $legacy);
1201 + return $this->getRawSetting( $meta );
1888 1202
1889 - $this->delete_prop('display_labels');
1890 -
1891 - return $this->get_prop($meta);
1892 1203 }
1893 1204
1894 1205 /**
1895 1206 *
@@ -1898,20 +1209,11 @@
1898 1209 * @return int
1899 1210 */
1900 1211 public function getRestart()
1901 1212 {
1902 - return $this->get_prop('restart') ?: C::RESTART_NONE;
1213 + return $this->getRawSetting( 'restart' ) ?: C::RESTART_NONE;
1903 1214 }
1904 1215
1905 - // removeIf(pro)
1906 - public function setRestart($value)
1907 - {
1908 - if ($value == C::RESTART_AFTER_DURATION) {
1909 - return;
1910 - }
1911 - $this->set_prop('restart', $value);
1912 - }
1913 - // endRemoveIf(pro)
1914 1216 /**
1915 1217 * Returns WooCommerce products selection type.
1916 1218 *
1917 1219 * @return string
@@ -1917,14 +1219,14 @@
1917 1219 * @return string
1918 1220 */
1919 1221 public function getWcProductsSelectionType()
1920 1222 {
1921 - $legacy = $this->get_prop_legacy('products_type');
1922 - if (!$legacy) {
1923 - return $this->get_prop('wc_products_selection_type');
1223 + $legacy = $this->getLegacySetting( 'products_type' );
1224 + if ( ! $legacy ) {
1225 + return $this->getRawSetting( 'wc_products_selection_type' );
1924 1226 }
1925 - $this->set_prop('wc_products_selection_type', $legacy);
1926 - $this->delete_prop('products_type');
1227 + $this->storeRawSetting( 'wc_products_selection_type', $legacy );
1228 + $this->deleteRawSetting( 'products_type' );
1927 1229
1928 1230 return $legacy;
1929 1231 }
1930 1232
@@ -1932,62 +1234,51 @@
1932 1234 * Delete setting item.
1933 1235 *
1934 1236 * @param $name
1935 1237 */
1936 - public function delete_prop($name)
1238 + public function deleteRawSetting( $name )
1937 1239 {
1938 - delete_post_meta($this->id, $name);
1240 + delete_post_meta( $this->id, $name );
1939 1241 }
1940 1242
1941 1243 /**
1942 1244 * Returns products selection IDs.
1943 - * ::PROP
1944 1245 *
1945 1246 * @return array
1946 1247 */
1947 1248 public function getWcProductsSelection()
1948 1249 {
1949 - $result = $this->get_prop_legacy('products');
1950 - if (!$result) {
1951 - $result = $this->get_prop('wc_products_selection');
1952 - } else {
1953 - $this->set_prop('wc_products_selection', $result);
1954 - $this->delete_prop('products');
1250 + $legacy = $this->getLegacySetting( 'products' );
1251 + if ( ! $legacy ) {
1252 + return $this->getRawSetting( 'wc_products_selection' );
1955 1253 }
1254 + $this->storeRawSetting( 'wc_products_selection', $legacy );
1255 + $this->deleteRawSetting( 'products' );
1956 1256
1957 - return is_array($result) ? $result : [];
1257 + return $legacy;
1958 1258 }
1959 1259
1960 1260 /**
1961 1261 * Store custom labels.
1962 - * ::PROP
1963 1262 *
1964 1263 * @param $labels
1965 1264 */
1966 - public function setLabels($labels)
1265 + public function setLabels( $labels )
1967 1266 {
1968 - // Sanitize each label to prevent XSS
1969 - if (is_array($labels)) {
1970 - $labels = array_map('sanitize_text_field', $labels);
1971 - }
1972 - $labels = wp_parse_args($labels, $this->labels);
1973 - update_post_meta($this->id, 'labels', $labels);
1267 + $labels = array_merge( $this->labels, array_filter( $labels ) );
1268 + update_post_meta( $this->id, 'labels', $labels );
1974 1269 }
1975 1270
1976 - public function getLabels()
1977 - {
1978 - return wp_parse_args($this->get_prop('labels'), $this->labels);
1979 - }
1980 -
1981 1271 /**
1982 1272 * Returns true if compaign can be published.
1983 1273 *
1984 1274 * @return bool
1985 1275 */
1986 - public function is_active()
1276 + public function isActive()
1987 1277 {
1988 - return get_post_status($this->id) === "publish"
1989 - || get_post_status($this->id) === "future";
1278 + return get_post_status( $this->id ) === "publish"
1279 + || get_post_status( $this->id ) === "future";
1280 +
1990 1281 }
1991 1282
1992 1283 /**
1993 1284 * Returns trus if the compaign is scheduled.
@@ -1993,21 +1284,19 @@
1993 1284 * Returns trus if the compaign is scheduled.
1994 1285 *
1995 1286 * @return bool
1996 1287 */
1997 - public function is_scheduled()
1288 + public function isScheduled()
1998 1289 {
1999 - $scheduled = get_post_status($this->get_id()) === "future";
2000 - if ($scheduled) {
1290 + $scheduled = get_post_status( $this->getId() ) === "future";
1291 + if ( $scheduled ) {
2001 1292 return true;
2002 1293 }
2003 1294
2004 - if ($this->is_recurring()) {
2005 - $start_date = Carbon::parse($this->recurringStartTime, hurryt_tz($this->timezone));
2006 - $now = Carbon::now(hurryt_tz($this->timezone));
2007 - $now->second = $start_date->second;
2008 - if ($now->lessThan($start_date)) {
2009 -
1295 + if ( $this->isRecurring() ) {
1296 + $start_date = Carbon::parse( $this->recurringStartTime, hurryt_tz() );
1297 + $now = Carbon::now( hurryt_tz() );
1298 + if ( $now->isBefore( $start_date ) ) {
2010 1299 return true;
2011 1300 }
2012 1301 }
2013 1302
@@ -2020,176 +1309,24 @@
2020 1309 * @param string|null $date
2021 1310 *
2022 1311 * @return bool
2023 1312 */
2024 - public function is_one_time_expired($date = null)
1313 + public function isExpired( $date = null )
2025 1314 {
2026 - $endDate = $date === null ? date_create($this->endDatetime) : $date;
2027 - $today = date_create(current_time("mysql"));
1315 + $endDate = $date === null ? date_create( $this->endDatetime ) : $date;
1316 + $today = date_create( current_time( "mysql" ) );
2028 1317
2029 1318 return $endDate < $today;
2030 1319 }
2031 1320
2032 - /**
2033 - * Check if a regular campaign with multiple deadlines has all deadlines expired.
2034 - *
2035 - * @return bool
2036 - */
2037 - public function is_all_date_ranges_expired()
1321 + public function isStickyDismissed()
2038 1322 {
2039 - $ranges = $this->getDateRanges();
2040 - if (empty($ranges)) {
2041 - return false; // No ranges means single-date mode, handled by is_one_time_expired
2042 - }
2043 -
2044 - $now = Carbon::now(hurryt_tz($this->timezone));
2045 -
2046 - foreach ($ranges as $range) {
2047 - if (empty($range['end'])) {
2048 - continue;
2049 - }
2050 - $end = Carbon::parse($range['end'], hurryt_tz($this->timezone));
2051 - if ($end->greaterThan($now)) {
2052 - return false;
2053 - }
2054 - }
2055 -
2056 - return true;
1323 + return isset( $_COOKIE[ CookieDetection::COOKIE_PREFIX . $this->getId() . '_dismissed' ] )
1324 + && $this->stickyBarDismissible === C::YES
1325 + && $this->enableSticky === C::YES;
2057 1326 }
2058 1327
2059 1328 /**
2060 - * Get the next upcoming deadline from the date ranges list.
2061 - *
2062 - * @return Carbon|null
2063 - */
2064 - public function getActiveDateRange()
2065 - {
2066 - $this->loadSettings();
2067 - $ranges = $this->getDateRanges();
2068 -
2069 - if (empty($ranges)) {
2070 - return null;
2071 - }
2072 -
2073 - $now = Carbon::now(hurryt_tz($this->timezone));
2074 -
2075 - // Collect and sort end dates chronologically
2076 - $endDates = [];
2077 - foreach ($ranges as $range) {
2078 - if (empty($range['end'])) {
2079 - continue;
2080 - }
2081 - $endDates[] = Carbon::parse($range['end'], hurryt_tz($this->timezone));
2082 - }
2083 -
2084 - usort($endDates, function ($a, $b) {
2085 - return $a->timestamp - $b->timestamp;
2086 - });
2087 -
2088 - // Return the first deadline that hasn't passed yet
2089 - foreach ($endDates as $end) {
2090 - if ($end->greaterThan($now)) {
2091 - return $end;
2092 - }
2093 - }
2094 -
2095 - return null;
2096 - }
2097 -
2098 - /**
2099 - * Get all future deadlines (end dates that haven't expired yet), sorted chronologically.
2100 - *
2101 - * @return array Each entry: ['end' => Carbon, 'headline' => string]
2102 - */
2103 - public function getFutureDateRanges()
2104 - {
2105 - $this->loadSettings();
2106 - $ranges = $this->getDateRanges();
2107 -
2108 - if (empty($ranges)) {
2109 - return [];
2110 - }
2111 -
2112 - $now = Carbon::now(hurryt_tz($this->timezone));
2113 - $result = [];
2114 -
2115 - foreach ($ranges as $range) {
2116 - if (empty($range['end'])) {
2117 - continue;
2118 - }
2119 - $end = Carbon::parse($range['end'], hurryt_tz($this->timezone));
2120 -
2121 - if ($end->greaterThan($now)) {
2122 - $result[] = [
2123 - 'end' => $end,
2124 - 'headline' => isset($range['headline']) ? $range['headline'] : '',
2125 - ];
2126 - }
2127 - }
2128 -
2129 - usort($result, function ($a, $b) {
2130 - return $a['end']->timestamp - $b['end']->timestamp;
2131 - });
2132 -
2133 - return $result;
2134 - }
2135 -
2136 - /**
2137 - * Get date ranges array.
2138 - *
2139 - * @return array
2140 - */
2141 - public function getDateRanges()
2142 - {
2143 - $ranges = $this->get_prop('date_ranges', false);
2144 - if (!empty($ranges) && is_array($ranges)) {
2145 - return $ranges;
2146 - }
2147 - return [];
2148 - }
2149 -
2150 - /**
2151 - * Set date ranges array.
2152 - *
2153 - * @param array|string $value
2154 - */
2155 - public function setDateRanges($value)
2156 - {
2157 - if (is_string($value)) {
2158 - $value = json_decode(stripslashes($value), true);
2159 - }
2160 - if (!is_array($value)) {
2161 - $value = [];
2162 - }
2163 - // Filter out entries without an end date
2164 - $value = array_filter($value, function ($entry) {
2165 - return !empty($entry['end']);
2166 - });
2167 - // Re-index
2168 - $value = array_values($value);
2169 - // Use update_post_meta directly to allow saving an empty array
2170 - update_post_meta($this->get_id(), 'date_ranges', $value);
2171 - }
2172 -
2173 - /**
2174 - * Check if this regular campaign uses multiple deadlines.
2175 - *
2176 - * @return bool
2177 - */
2178 - public function hasMultipleDateRanges()
2179 - {
2180 - $ranges = $this->getDateRanges();
2181 - return count($ranges) > 1;
2182 - }
2183 -
2184 - public function is_sticky_dismissed()
2185 - {
2186 - return isset($_COOKIE[Cookie_Detection::COOKIE_PREFIX . $this->get_id() . '_dismissed'])
2187 - && $this->stickyBarDismissible === C::YES
2188 - && $this->enableSticky === C::YES;
2189 - }
2190 -
2191 - /**
2192 1329 * Returns compaign template wrapper.
2193 1330 *
2194 1331 * @param string
2195 1332 *
@@ -2194,262 +1331,32 @@
2194 1331 * @param string
2195 1332 *
2196 1333 * @return string
2197 1334 */
2198 - public function wrap_template($content, $options = [])
1335 + public function wrapTemplate( $content )
2199 1336 {
2200 - $campaignBuilder = new CampaignBuilder($this);
1337 + $campaignBuilder = new CampaignBuilder( $this );
2201 1338
2202 - return apply_filters('hurryt_campaign_content', $campaignBuilder->build($content, $options), $this->get_id());
1339 + return $campaignBuilder->build( $content );
2203 1340 }
2204 1341
2205 1342 /*
2206 1343 * Returns campaign template content.
2207 1344 */
2208 - public function build_template()
1345 + public function buildTemplate()
2209 1346 {
2210 - $campaignBuilder = new CampaignBuilder($this);
1347 + $campaignBuilder = new CampaignBuilder( $this );
2211 1348
2212 1349 return $campaignBuilder->template();
2213 1350 }
2214 1351
2215 - /**
2216 - * ::PROP
2217 - *
2218 - * @param $value
2219 - */
2220 - public function setWcConditions($value)
1352 + public function setWcConditions( $value )
2221 1353 {
2222 - $this->set_prop('_hurryt_wc_conditions', !empty($value) ? $value : []);
1354 + $this->storeRawSetting( '_hurryt_wc_conditions', ! empty( $value ) ? $value : [] );
2223 1355 }
2224 1356
2225 - /**
2226 - * ::PROP
2227 - *
2228 - * @return mixed
2229 - */
2230 1357 public function getWcConditions()
2231 1358 {
2232 - return $this->get_prop('_hurryt_wc_conditions', false);
2233 - }
2234 -
2235 -
2236 - public function setRecurringUnselectedDaysAction($value)
2237 - {
2238 - $this->set_prop('_hurryt_recurring_unselected_days_action', $value);
2239 - }
2240 -
2241 - public function getRecurringUnselectedDaysAction()
2242 - {
2243 - return $this->get_prop('_hurryt_recurring_unselected_days_action');
2244 - }
2245 -
2246 - public function is_running()
2247 - {
2248 - // TODO: add support for weekly campaigns.
2249 - if (
2250 - $this->is_recurring()
2251 - && !$this->can_recur_today()
2252 - && ($this->recurringUnselectedDaysAction === 'hide' || $this->isWeeklyRecurring())
2253 - ) {
2254 - return false;
2255 - }
2256 -
2257 - $not_running = !$this->is_active() || $this->is_scheduled()
2258 - || ($this->is_recurring() && empty($this->getRecurrenceEndDate()));
2259 -
2260 -
2261 - return !$not_running;
2262 - }
2263 -
2264 - /**
2265 - * Check if recurring or onetime campaign is expired.
2266 - *
2267 - * @return bool
2268 - */
2269 - public function is_expired()
2270 - {
2271 - if ($this->is_one_time() && $this->hasMultipleDateRanges()) {
2272 - return $this->is_all_date_ranges_expired();
2273 - }
2274 - return ($this->is_one_time() && $this->is_one_time_expired())
2275 - || ($this->is_recurring() && $this->is_recurring_expired());
2276 - }
2277 -
2278 - public function get_mode_slug()
2279 - {
2280 - switch ($this->mode) {
2281 - case C::MODE_EVERGREEN:
2282 - return 'evergreen';
2283 - case C::MODE_RECURRING:
2284 - return 'recurring';
2285 - case C::MODE_REGULAR:
2286 - return 'one_time';
2287 - }
2288 - }
2289 -
2290 - public function getReloadReset()
2291 - {
2292 - return filter_var($this->get_prop('reload_reset'), FILTER_VALIDATE_BOOLEAN);
2293 - }
2294 -
2295 - public function getDetectionMethods()
2296 - {
2297 - return array_map('intval', $this->get_prop('_hurryt_detection_methods'));
2298 - }
2299 -
2300 - public function setDetectionMethods($methods)
2301 - {
2302 - // removeIf(pro)
2303 - if (is_array($methods)) {
2304 - $methods = array_filter($methods, function ($m) {
2305 - return (int)$m !== C::DETECTION_METHOD_USER_SESSION;
2306 - });
2307 - }
2308 - // endRemoveIf(pro)
2309 - $this->set_prop('_hurryt_detection_methods', $methods);
2310 - }
2311 -
2312 -
2313 -
2314 - /**
2315 - * @return bool
2316 - */
2317 - private function isDailyRecurring()
2318 - {
2319 - return $this->recurringFrequency === C::RECURRING_DAILY;
2320 - }
2321 -
2322 - /**
2323 - * @return bool
2324 - */
2325 - private function isWeeklyRecurring()
2326 - {
2327 - return $this->recurringFrequency === C::RECURRING_WEEKLY;
2328 - }
2329 -
2330 - /**
2331 - * @return bool
2332 - */
2333 - private function isHourlyRecurring()
2334 - {
2335 - return $this->recurringFrequency === C::RECURRING_HOURLY;
2336 - }
2337 -
2338 - /**
2339 - * @return bool
2340 - */
2341 - private function isMinutelyRecurring()
2342 - {
2343 - return $this->recurringFrequency === C::RECURRING_MINUTELY;
2344 - }
2345 -
2346 - /**
2347 - * Get the end time for evergreen timers
2348 - *
2349 - * @return string|null
2350 - */
2351 - public function getEndTime()
2352 - {
2353 - return $this->get_prop('end_time');
2354 - }
2355 -
2356 - /**
2357 - * Set the end time for evergreen timers
2358 - *
2359 - * @param string $time
2360 - */
2361 - public function setEndTime($time)
2362 - {
2363 - $this->set_prop('end_time', $time);
2364 - }
2365 -
2366 - /**
2367 - * Get the cookie name for this campaign
2368 - *
2369 - * @return string
2370 - */
2371 - public function getCookieName()
2372 - {
2373 - return Cookie_Detection::cookieName($this->id);
2374 - }
2375 -
2376 - /**
2377 - * Get the reset token for this campaign
2378 - *
2379 - * @return string|null
2380 - */
2381 - public function getResetToken()
2382 - {
2383 - $evergreenCampaign = new EvergreenCampaign($this->id);
2384 - $evergreenCampaign->loadSettings();
2385 - return $evergreenCampaign->getInitiatedResetToken();
2386 - }
2387 -
2388 - /**
2389 - * Check if the campaign should reset
2390 - *
2391 - * @return bool
2392 - */
2393 - public function shouldReset()
2394 - {
2395 - $evergreenCampaign = new EvergreenCampaign($this->id);
2396 - $evergreenCampaign->loadSettings();
2397 - return $evergreenCampaign->shouldResetTimer();
2398 - }
2399 -
2400 - /**
2401 - * Get whether the campaign runs in background
2402 - *
2403 - * @return bool
2404 - */
2405 - public function getRunInBackground()
2406 - {
2407 - return $this->get_prop('run_in_background', false);
2408 - }
2409 -
2410 - /**
2411 - * Get the end day for evergreen timers
2412 - *
2413 - * @return int
2414 - */
2415 - public function getEvergreenEndDay()
2416 - {
2417 - return (int)$this->get_prop('evergreen_end_day', false) ?: 0;
2418 - }
2419 -
2420 - /**
2421 - * Set the end day for evergreen timers
2422 - *
2423 - * @param int $day
2424 - */
2425 - public function setEvergreenEndDay($day)
2426 - {
2427 - $this->set_prop('evergreen_end_day', (int)$day);
2428 - }
2429 -
2430 - public function getEvergreenEndTime()
2431 - {
2432 - return $this->get_prop('evergreen_end_time');
2433 - }
2434 -
2435 - public function setEvergreenEndTime($time)
2436 - {
2437 - $this->set_prop('evergreen_end_time', $time);
2438 - }
2439 -
2440 - public function getEvergreenEndType()
2441 - {
2442 - return $this->get_prop('evergreen_end_type');
2443 - }
2444 -
2445 - public function setEvergreenEndType($type){
2446 -
2447 - // removeIf(pro)
2448 - if(! hurrytimer_is_pro() ){
2449 - $type = 'duration';
2450 - }
2451 - // endRemoveIf(pro)
2452 - $this->set_prop('evergreen_end_type', $type);
1359 + return $this->getRawSetting( '_hurryt_wc_conditions', false );
2453 1360 }
2454 1361
2455 1362 }