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
← All changes | includes/Frontend.php +228 -53 2.0.02.6.2 View file →
@@ -1,5 +1,6 @@
1 1 <?php
2 +
2 3 namespace Hurrytimer;
3 4
4 5 /**
5 6 * The public-facing functionality of the plugin.
@@ -9,19 +10,8 @@
9 10 *
10 11 * @package Hurrytimer
11 12 * @subpackage Hurrytimer/public
12 13 */
13 -
14 -/**
15 - * The public-facing functionality of the plugin.
16 - *
17 - * Defines the plugin name, version, and two examples hooks for how to
18 - * enqueue the public-facing stylesheet and JavaScript.
19 - *
20 - * @package Hurrytimer
21 - * @subpackage Hurrytimer/public
22 - * @author Nabil Lemsieh <contact@nabillemsieh.com>
23 - */
24 14 class Frontend
25 15 {
26 16 /**
27 17 * The ID of this plugin.
@@ -43,51 +33,186 @@
43 33
44 34 /**
45 35 * Initialize the class and set its properties.
46 36 *
37 + * @param string $plugin_name The name of the plugin.
38 + * @param string $version The version of this plugin.
39 + *
47 40 * @since 1.0.0
48 41 *
49 - * @param string $plugin_name The name of the plugin.
50 - * @param string $version The version of this plugin.
51 42 */
52 - public function __construct($plugin_name, $version)
43 + public function __construct( $plugin_name, $version )
53 44 {
54 45 $this->plugin_name = $plugin_name;
55 46 $this->version = $version;
56 - $shortcode = new CampaignShortcode();
57 - add_shortcode('hurrytimer', [$shortcode, 'content']);
58 - add_action('wp', [$this, 'handleWCSingleProduct']);
59 - add_action('wp_ajax_action_change_stock_status', [$this, 'ajaxChangeStockStatus']);
60 - add_action('wp_ajax_nopriv_action_change_stock_status', [$this, 'ajaxChangeStockStatus']);
61 47 }
62 48
63 - function changeStockStatus()
49 + function init()
64 50 {
65 - check_ajax_referer('hurryt', 'ajax_nonce');
66 - if (isset($_POST['id']) || ! isset($_POST['stockStatus'])) {
67 - die(-1);
51 +
52 + // Enqueue CSS and JS.
53 + add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_styles' ] );
54 + add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
55 +
56 + // Display campaign in sticky bar.
57 + add_action( 'wp_footer', [ $this, 'render_sticky_bar' ] );
58 + add_action( 'wp_footer', [ $this, 'run_in_background' ] );
59 +
60 + // Display campaign on product page.
61 + add_action( 'wp', [ $this, 'render_on_product_page' ] );
62 +
63 + // Change stock status for recurring/onetime campaign via Ajax.
64 + add_action( 'wp_ajax_change_stock_status', [ $this, 'ajax_change_stock_status' ] );
65 + add_action( 'wp_ajax_nopriv_change_stock_status', [ $this, 'ajax_change_stock_status' ] );
66 +
67 + // Log evergreen campaign start time for each visitor.
68 + add_action( 'wp_ajax_hurryt/update_timestamp', [ $this, 'ajax_save_evergreen_start_time' ] );
69 + add_action( 'wp_ajax_nopriv_hurryt/update_timestamp', [ $this, 'ajax_save_evergreen_start_time' ] );
70 +
71 + // Return next recurrence.
72 + add_action( 'wp_ajax_next_recurrence', [ $this, 'ajax_next_recurrence' ] );
73 + add_action( 'wp_ajax_nopriv_next_recurrence', [ $this, 'ajax_next_recurrence' ] );
74 +
75 + // Check before rendering campaign.
76 + add_action( 'hurryt_pre_render', [ $this, 'pre_render_shortcode' ] );
77 +
78 + // Check actions for expired recurring and onetime campaigns
79 + // This only concerns shortcodes inserted in post editor.
80 + // Fallback to shortcode callback
81 + add_action( 'wp', [ $this, 'check_post_shortcode' ] );
82 +
83 + }
84 +
85 + function run_in_background()
86 + {
87 +
88 + // At this time, all campagins with the "Expire coupon" action
89 + // should run in background.
90 + $campaigns = get_posts( [
91 + 'post_type' => HURRYT_POST_TYPE,
92 + 'numberposts' => -1,
93 + 'post_status' => 'publish',
94 + ] );
95 +
96 + foreach ( $campaigns as $campaign ) {
97 + $campaign = new Campaign( $campaign->ID );
98 +
99 + $campaign->loadSettings();
100 + $action = $campaign->get_action( C::ACTION_EXPIRE_COUPON );
101 +
102 + if ( !empty( $action ) ) {
103 + echo do_shortcode( '[hurrytimer id=' . $campaign->get_id() . ' run_in_background=true]' );
104 + }
105 +
68 106 }
69 - $id = intval($_POST['id']);
70 - $stockStatus = sanitize_key($_POST['stockStatus']);
71 - $wcCampaign = new WCCampaign();
72 - $campaign = new Campaign($id);
107 + }
73 108
74 - $wcCampaign->changeStockStatus($campaign, $stockStatus);
109 + /**
110 + * Check if there is shortcode in the current post.
111 + */
112 + function check_post_shortcode()
113 + {
114 + global $post;
115 + if ( is_singular() && is_a( $post, 'WP_Post' )
116 + && has_shortcode( $post->post_content, 'hurrytimer' ) && !( defined( 'DOING_AJAX' ) && DOING_AJAX )
117 + ) {
118 + $campaigns_ids = hurryt_parse_campaigns( $post->post_content );
119 + foreach ( $campaigns_ids as $id ) {
120 + do_action( 'hurryt_pre_render', hurryt_get_campaign( $id ) );
121 + }
122 + }
123 + }
124 +
125 + /**
126 + * @param $campaign Campaign
127 + */
128 + function pre_render_shortcode( $campaign )
129 + {
130 + if ( $campaign->is_running() && $campaign->is_expired() ) {
131 + ( new ActionManager( $campaign ) )->run();
132 + }
133 + }
134 +
135 +
136 + public function ajax_next_recurrence()
137 + {
138 + check_ajax_referer( 'hurryt', 'nonce' );
139 + $campaign_id = absint( $_GET[ 'id' ] );
140 + if ( !get_post( $campaign_id )
141 + || get_post_type( $campaign_id ) !== HURRYT_POST_TYPE
142 + ) {
143 + die( -1 );
144 + }
145 +
146 + $campaign = new Campaign( $campaign_id );
147 + $endDate = $campaign->getRecurrenceEndDate();
148 + wp_send_json_success( [
149 + 'endTimestamp' => $endDate ? $endDate->getBrowserTimestamp() : null,
150 + ] );
151 + }
152 +
153 + /**
154 + * Uses ajax to save start time for the given visitor.
155 + * This used to bypass cookie cache.
156 + */
157 + public function ajax_save_evergreen_start_time()
158 + {
159 + check_ajax_referer( 'hurryt', 'nonce' );
160 + if ( !isset( $_POST[ 'timestamp' ] ) || !isset( $_POST[ 'cid' ] ) ) {
161 + wp_die();
162 + }
163 +
164 + $evergreen_campaign = new EvergreenCampaign( intval( $_POST[ 'cid' ] ) );
165 + $evergreen_campaign->setEndDate( filter_input( INPUT_POST, 'timestamp' ) );
166 + wp_die( 'Success!' );
167 + }
168 +
169 + public function render_sticky_bar()
170 + {
171 + $campaigns = get_posts( [
172 + 'post_type' => HURRYT_POST_TYPE,
173 + 'numberposts' => -1,
174 + 'post_status' => 'publish',
175 + 'meta_key' => 'enable_sticky',
176 + 'meta_value' => C::YES,
177 + 'suppress_filters' => false,
178 + ] );
179 + foreach ( $campaigns as $post ) {
180 + echo do_shortcode( sprintf( '[hurrytimer id="%d" sticky="true" ]', $post->ID ) );
181 + }
182 +
183 + }
184 +
185 + /**
186 + * Apply change stock status
187 + */
188 + public function ajax_change_stock_status()
189 + {
190 + check_ajax_referer( 'hurryt', 'nonce' );
191 + if ( !isset( $_POST[ 'campaign_id' ], $_POST[ 'status' ] ) ) {
192 + die( -1 );
193 + }
194 + $id = intval( $_POST[ 'campaign_id' ] );
195 + $status = sanitize_key( $_POST[ 'status' ] );
196 + $wc_campaign = new WCCampaign();
197 + $campaign = new Campaign( $id );
198 + $wc_campaign->change_stock_status( $campaign, $status );
75 199 die();
76 200 }
77 201
78 - public function handleWCSingleProduct()
202 + /**
203 + * Maybe display campaign on current product page.
204 + */
205 + public function render_on_product_page()
79 206 {
80 207 global $post;
81 -
82 - if ( Utils\Helpers::isWcActive() && is_product()) {
83 - $wcCampaign = new WCCampaign();
84 - $wcCampaign->run($post->ID);
208 + if ( hurryt_is_woocommerce_activated() && is_product() ) {
209 + $wc_campaign = new WCCampaign();
210 + $wc_campaign->run( $post->ID );
85 211 }
86 212
87 213 }
88 214
89 -
90 215 /**
91 216 * Register the stylesheets for the public-facing side of the site.
92 217 *
93 218 * @since 1.0.0
@@ -93,14 +218,10 @@
93 218 * @since 1.0.0
94 219 */
95 220 public function enqueue_styles()
96 221 {
97 -
98 - wp_enqueue_style(
99 - $this->plugin_name,
100 - HURRYT_URL . 'assets/css/hurrytimer.css', [], time()
101 - );
102 -
222 + // Using hashed filename for cache busting.
223 + wp_enqueue_style( $this->plugin_name, CSS_Builder::get_instance()->get_build_url() );
103 224 }
104 225
105 226 /**
106 227 * Register the JavaScript for the public-facing side of the site.
@@ -108,27 +229,48 @@
108 229 * @since 1.0.0
109 230 */
110 231 public function enqueue_scripts()
111 232 {
233 +
234 + wp_enqueue_script( 'jquery' );
235 +
112 236 wp_enqueue_script(
113 - 'jq-countdown',
114 - HURRYT_URL. 'assets/js/jquery.countdown.min.js',
115 - ['jquery'],
237 + 'hurryt-cookie',
238 + HURRYT_URL . 'assets/js/cookie.min.js',
239 + [],
240 + '3.14.1',
241 + true
242 + );
243 + wp_enqueue_script(
244 + 'hurryt-countdown',
245 + HURRYT_URL . 'assets/js/jquery.countdown.min.js',
246 + [ 'jquery' ],
116 247 '2.2.0',
117 248 true
118 249 );
119 -
120 250 wp_enqueue_script(
121 251 $this->plugin_name,
122 - HURRYT_URL. 'assets/js/hurrytimer.js',
123 - ['jq-countdown'],
124 - $this->version,
252 + HURRYT_URL . 'assets/js/hurrytimer.js',
253 + [ 'jquery', 'hurryt-countdown', 'hurryt-cookie' ],
254 + defined( 'WP_DEBUG' ) && WP_DEBUG ? time() : $this->version,
125 255 true
126 256 );
127 - wp_localize_script($this->plugin_name, 'hurrytimer_ajax_object', [
128 - 'ajax_url' => admin_url('admin-ajax.php'),
129 - 'ajax_nonce' => wp_create_nonce('hurryt'),
130 - 'actionsOptions' => [
257 +
258 +
259 + $disable_actions = hurryt_is_admin_area() && hurryt_settings()[ 'disable_actions' ];
260 + $disable_actions = filter_var( apply_filters( 'hurryt_disable_actions', $disable_actions ),
261 + FILTER_VALIDATE_BOOLEAN );
262 +
263 + wp_localize_script( $this->plugin_name, 'hurrytimer_ajax_object', [
264 + 'ajax_url' => admin_url( 'admin-ajax.php' ),
265 + 'ajax_nonce' => wp_create_nonce( 'hurryt' ),
266 + 'disable_actions' => $disable_actions,
267 + 'methods' => [
268 + 'COOKIE' => C::DETECTION_METHOD_COOKIE,
269 + 'IP' => C::DETECTION_METHOD_IP,
270 + 'USER_SESSION' => C::DETECTION_METHOD_USER_SESSION,
271 + ],
272 + 'actionsOptions' => [
131 273 'none' => C::ACTION_NONE,
132 274 'hide' => C::ACTION_HIDE,
133 275 'redirect' => C::ACTION_REDIRECT,
134 276 'stockStatus' => C::ACTION_CHANGE_STOCK_STATUS,
@@ -133,13 +275,46 @@
133 275 'redirect' => C::ACTION_REDIRECT,
134 276 'stockStatus' => C::ACTION_CHANGE_STOCK_STATUS,
135 277 'hideAddToCartButton' => C::ACTION_HIDE_ADD_TO_CART_BUTTON,
136 278 'displayMessage' => C::ACTION_DISPLAY_MESSAGE,
279 + 'expire_coupon' => C::ACTION_EXPIRE_COUPON,
137 280 ],
138 - 'restartOptions' => [
281 + 'restartOptions' => [
139 282 'none' => C::RESTART_NONE,
140 283 'immediately' => C::RESTART_IMMEDIATELY,
141 284 'afterReload' => C::RESTART_AFTER_RELOAD,
285 + 'after_duration' => C::RESTART_AFTER_DURATION,
142 286 ],
143 - ]);
287 + 'COOKIEPATH' => defined( 'COOKIEPATH' ) ? COOKIEPATH : '',
288 + 'COOKIE_DOMAIN' => defined( 'COOKIE_DOMAIN' ) ? COOKIE_DOMAIN : '',
289 + 'redirect_no_back' => apply_filters( 'hurryt_redirect_no_back', true ),
290 + 'expire_coupon_message' => $this->get_coupon_expired_message(),
291 + ] );
144 292 }
293 +
294 +
295 + /**
296 + * Get WooCommerce coupon expired message.
297 + *
298 + *
299 + * TODO: move to /integration/woocommerce
300 + *
301 + * @return string
302 + * @since 2.3
303 + */
304 + function get_coupon_expired_message()
305 + {
306 +
307 + if ( !hurryt_is_woocommerce_activated() ) {
308 + return '';
309 + }
310 +
311 + try {
312 + return ( new \WC_Coupon() )->get_coupon_error( \WC_Coupon::E_WC_COUPON_EXPIRED );
313 +
314 + } catch ( \Exception $e ) {
315 + return '';
316 + }
317 +
318 + }
319 +
145 320 }