| 1 |
<?php |
| 2 |
/** |
| 3 |
* Webappick Promotion handler |
| 4 |
* @version 1.0.0 |
| 5 |
* @package Disco |
| 6 |
* @subpackage AppServices |
| 7 |
*/ |
| 8 |
|
| 9 |
namespace Disco\Libs\WebAppick\AppServices; |
| 10 |
|
| 11 |
if ( ! defined( 'ABSPATH' ) ) { |
| 12 |
die(); |
| 13 |
} |
| 14 |
|
| 15 |
/** |
| 16 |
* Class Promotions |
| 17 |
* Source Format |
| 18 |
* |
| 19 |
$promos = [ |
| 20 |
(object) [ |
| 21 |
// required; offer start |
| 22 |
'start' => '2019-11-20 00:00:01', |
| 23 |
// required; offer end |
| 24 |
'end' => '2019-12-04 23:59:00', |
| 25 |
// required; some unique hash for offer, use for hiding the offer when user click to close. |
| 26 |
'hash' => '9df37798-bd35-421b-b6f2-fbc095686efc', |
| 27 |
// Optional; Allow closing default is 0, set to 1 to allow user hide it |
| 28 |
'dismissible' => 1, // 0 |
| 29 |
// required; main content required, will be filtered with wp_kses_post() |
| 30 |
'content' => '<p>Biggest Sale of the year on this</p><h3>Black Friday & Cyber Monday</h3><p>Claim your discount on till 4th December</p>', |
| 31 |
// optional; wrapper padding |
| 32 |
wrapperPadding => '' |
| 33 |
// optional; image source url or data url |
| 34 |
'backgroundImage' => '', |
| 35 |
// optional; |
| 36 |
'backgroundRepeat' => '', |
| 37 |
// optional; |
| 38 |
'backgroundSize' => '', |
| 39 |
// optional; background color to apply with the background image |
| 40 |
'backgroundColor' => '#000', |
| 41 |
// optional; text color will be inherited by the content |
| 42 |
'color' => '#fff', |
| 43 |
// optional; can be empty |
| 44 |
'logo' => [ |
| 45 |
// required; image source url or data url |
| 46 |
'src' => '', |
| 47 |
// required; |
| 48 |
'alt' => 'Woo Feed Pro', |
| 49 |
], |
| 50 |
// optional; can be empty |
| 51 |
'button' => [ |
| 52 |
// required |
| 53 |
'label' => 'Save 30%', |
| 54 |
// required |
| 55 |
'url' => '#?utm_campaign=black_friday_&_cyber_monday&utm_medium=banner&utm_source=wp_dashboard', |
| 56 |
// optional; |
| 57 |
'backgroundColor' => '#F71560', |
| 58 |
// optional; |
| 59 |
'color' => '#FFF', |
| 60 |
// optional; |
| 61 |
'after' => '<span style="font-size: 12px; font-weight: 700; margin-top: 12px; display: block;">Coupon: BFCM2019</span>', // html content filtered with wp_kses_post() |
| 62 |
], |
| 63 |
], |
| 64 |
]; |
| 65 |
* |
| 66 |
*/ |
| 67 |
class Promotions { |
| 68 |
|
| 69 |
/** |
| 70 |
* CTXFeed\AppServices\Client |
| 71 |
* |
| 72 |
* @var Client |
| 73 |
*/ |
| 74 |
protected $client; |
| 75 |
|
| 76 |
/** |
| 77 |
* URL for Promotions source json file |
| 78 |
* @var string |
| 79 |
*/ |
| 80 |
private $promotionSrc; |
| 81 |
|
| 82 |
/** |
| 83 |
* Promotions |
| 84 |
* @var bool|object[] |
| 85 |
*/ |
| 86 |
private $promotions = false; |
| 87 |
/** |
| 88 |
* List of hidden promotions for current user |
| 89 |
* @var array |
| 90 |
*/ |
| 91 |
private $hiddenPromotions; |
| 92 |
/** |
| 93 |
* Current User Id |
| 94 |
* @var int |
| 95 |
*/ |
| 96 |
private $currentUser = 0; |
| 97 |
|
| 98 |
/** |
| 99 |
* Promotions constructor. |
| 100 |
* @param Client $client The Client. |
| 101 |
* @param string $data_source Data Source URL |
| 102 |
* @return void |
| 103 |
*/ |
| 104 |
public function __construct( Client $client, $data_source = null ) { |
| 105 |
$this->client = $client; |
| 106 |
if ( ! is_null( $data_source ) ) $this->promotionSrc = esc_url( $data_source ); |
| 107 |
} |
| 108 |
|
| 109 |
/** |
| 110 |
* Set JSON Source File URL For getting promotion data |
| 111 |
* @param string $URL Set Data Source URL. |
| 112 |
* |
| 113 |
* @return Promotions |
| 114 |
*/ |
| 115 |
public function set_source( $URL ) { |
| 116 |
$this->promotionSrc = esc_url( $URL ); |
| 117 |
return $this; |
| 118 |
} |
| 119 |
|
| 120 |
/** |
| 121 |
* Init Promotions |
| 122 |
* @return void |
| 123 |
*/ |
| 124 |
public function init() { |
| 125 |
if ( is_null( $this->promotionSrc ) ) { |
| 126 |
_doing_it_wrong( __METHOD__, esc_html__( 'Promotion Source URL Not Set. see Promotions::set_source( $URL )', 'disco' ), '1.0.0' ); |
| 127 |
} |
| 128 |
add_action( 'admin_init', [ $this, '__init_internal' ], 10 ); |
| 129 |
} |
| 130 |
|
| 131 |
/** |
| 132 |
* Set environment variables and init internal hooks |
| 133 |
* @return void |
| 134 |
*/ |
| 135 |
public function __init_internal() { |
| 136 |
$this->currentUser = get_current_user_id(); |
| 137 |
$this->hiddenPromotions = (array) get_user_option( $this->client->getSlug() . '_hidden_promos', $this->currentUser ); |
| 138 |
$this->promotions = $this->__get_promos(); |
| 139 |
// only run if there is active promotions. |
| 140 |
if ( count( $this->promotions ) ) { |
| 141 |
add_action( 'admin_notices', [ $this, '__show_promos' ], 10 ); |
| 142 |
add_action( 'wp_ajax_webappick_dismiss_promo', [ $this, '__webappick_dismiss_promo' ], 10 ); |
| 143 |
add_action( 'admin_print_styles', [ $this, '__get_promo_styles' ], 99 ); |
| 144 |
add_action( 'admin_enqueue_scripts', [ $this, '__enqueue_deps' ], 10 ); |
| 145 |
add_action( 'admin_print_footer_scripts', [ $this, '__get_promo_scripts' ], 10 ); |
| 146 |
} |
| 147 |
} |
| 148 |
|
| 149 |
/** |
| 150 |
* Render Promotions |
| 151 |
* @return void |
| 152 |
*/ |
| 153 |
public function __show_promos() { |
| 154 |
foreach ( $this->promotions as $promotion ) { |
| 155 |
$wrapperStyles = ''; |
| 156 |
$buttonStyles = ''; |
| 157 |
$is_dismissible = ! isset( $promotion->dismissible ) || isset( $promotion->dismissible ) && 0 == $promotion->dismissible ? false : true; |
| 158 |
|
| 159 |
$has_columns = isset( $promotion->button, $promotion->logo ); |
| 160 |
if ( isset( $promotion->color ) ) { |
| 161 |
$wrapperStyles .= 'color: ' . $promotion->color . ';'; |
| 162 |
} |
| 163 |
if ( isset( $promotion->wrapperPadding ) ) { |
| 164 |
$wrapperStyles .= 'padding: ' . $promotion->wrapperPadding . ';'; |
| 165 |
} |
| 166 |
if ( isset( $promotion->backgroundColor ) ) { |
| 167 |
$wrapperStyles .= 'background-color: ' . $promotion->backgroundColor . ';'; |
| 168 |
} |
| 169 |
if ( isset( $promotion->backgroundImage ) ) { |
| 170 |
$wrapperStyles .= 'background-image: url("' . esc_url( $promotion->backgroundImage ) . '");'; |
| 171 |
} |
| 172 |
if ( isset( $promotion->backgroundRepeat ) ) { |
| 173 |
$wrapperStyles .= 'background-repeat: ' . $promotion->backgroundRepeat . ';'; |
| 174 |
} |
| 175 |
if ( isset( $promotion->backgroundSize ) ) { |
| 176 |
$wrapperStyles .= 'background-size: ' . $promotion->backgroundSize . ';'; |
| 177 |
} |
| 178 |
if ( property_exists( $promotion, 'button' ) ) { |
| 179 |
if ( isset( $promotion->button->backgroundColor ) ) { |
| 180 |
$buttonStyles .= 'background-color: ' . $promotion->button->backgroundColor . ';border-color: ' . $promotion->button->backgroundColor . ';'; |
| 181 |
} |
| 182 |
if ( isset( $promotion->button->color ) ) { |
| 183 |
$buttonStyles .= 'color: ' . $promotion->button->color . ';'; |
| 184 |
} |
| 185 |
} |
| 186 |
$noticeClasses = 'notice notice-success wapk-promo'; |
| 187 |
if ( $is_dismissible ) { |
| 188 |
$noticeClasses .= ' is-dismissible'; |
| 189 |
} |
| 190 |
?> |
| 191 |
<div class="<?php echo esc_attr( $noticeClasses ); ?> " id="<?php echo esc_attr( $promotion->hash ); ?>" data-nonce="<?php echo esc_attr( wp_create_nonce( 'wapk-dismiss-promo' ) ); ?>" style="<?php echo esc_attr( $wrapperStyles ); ?>"> |
| 192 |
<div class="wapk-promo-wrap<?php if ( ! $has_columns ) echo ' no-column'; ?>"> |
| 193 |
<?php if ( isset( $promotion->logo ) && ! empty( $promotion->logo ) ) { ?> |
| 194 |
<div class="wapk-logo wapk-column"> |
| 195 |
<img src="<?php echo esc_url( $promotion->logo->src ); ?>" alt="<?php echo esc_attr( $promotion->logo->alt ); ?>"> |
| 196 |
</div> |
| 197 |
<?php } ?> |
| 198 |
<div class="wapk-details<?php if ( $has_columns ) echo ' wapk-column'; ?>"> |
| 199 |
<?php echo wp_kses_post( $promotion->content ); ?> |
| 200 |
</div> |
| 201 |
<?php if ( isset( $promotion->button ) && ! empty( $promotion->button ) ) { ?> |
| 202 |
<div class="wapk-btn-container wapk-column"> |
| 203 |
<a href="<?php echo esc_url( $promotion->button->url ); ?>" class="button wapk-promo-btn" style="<?php echo esc_attr( $buttonStyles ); ?>" target="_blank"><?php echo wp_kses_post( $promotion->button->label ); ?></a> |
| 204 |
<?php |
| 205 |
if ( isset( $promotion->button->after ) && ! empty( $promotion->button->after ) ) { |
| 206 |
echo wp_kses_post( $promotion->button->after ); |
| 207 |
} |
| 208 |
?> |
| 209 |
</div> |
| 210 |
<?php } ?> |
| 211 |
<?php if ( isset( $promotion->button ) && ! empty( $promotion->button ) ) { ?> |
| 212 |
<?php } ?> |
| 213 |
</div> |
| 214 |
</div> |
| 215 |
<?php |
| 216 |
} |
| 217 |
} |
| 218 |
|
| 219 |
/** |
| 220 |
* Get Promotion Data |
| 221 |
* Cache First then fetch source url for json data. |
| 222 |
* @return array |
| 223 |
*/ |
| 224 |
private function __get_promos() { |
| 225 |
$promos = get_transient( $this->client->getSlug() . '_cached_promos' ); |
| 226 |
if ( empty( $promos ) ) { |
| 227 |
// get promotions data from json source. |
| 228 |
$response = wp_safe_remote_get( $this->promotionSrc, array( 'timeout' => 15 ) ); // phpcs:ignore |
| 229 |
$promos = wp_remote_retrieve_body( $response ); |
| 230 |
if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { |
| 231 |
$promos = '[]'; |
| 232 |
} |
| 233 |
// cache data. |
| 234 |
set_transient( $this->client->getSlug() . '_cached_promos', $promos, 12 * HOUR_IN_SECONDS ); |
| 235 |
} |
| 236 |
// decode to array. |
| 237 |
$promos = json_decode( $promos ); |
| 238 |
|
| 239 |
// filter promotions by date. |
| 240 |
$promos = array_filter( $promos, [ $this, '__is_promo_active' ] ); |
| 241 |
if ( ! empty( $promos ) ) { |
| 242 |
// filter promotions by list of hidden promotions by the user. |
| 243 |
$promos = array_filter( $promos, [ $this, '__is_promo_hidden' ] ); |
| 244 |
} |
| 245 |
return $promos; |
| 246 |
} |
| 247 |
|
| 248 |
/** |
| 249 |
* Check if promotion is active by date. |
| 250 |
* must have start and end property |
| 251 |
* @param object $promo { the promo object. |
| 252 |
* Single Promo Object |
| 253 |
* @type string $content string. required |
| 254 |
* @type string $start valid timestamp. required |
| 255 |
* @type string $end valid timestamp. required |
| 256 |
* } |
| 257 |
* |
| 258 |
* @return bool |
| 259 |
*/ |
| 260 |
public function __is_promo_active( $promo ) { |
| 261 |
$ct = current_time( 'timestamp' ); // phpcs:ignore |
| 262 |
return ( ! empty( $promo->content ) && strtotime( $promo->start ) < $ct && $ct < strtotime( $promo->end ) ); |
| 263 |
} |
| 264 |
|
| 265 |
/** |
| 266 |
* Check if promo is hidden by current user |
| 267 |
* @param object $promo { the promo object. |
| 268 |
* Single Promo Object |
| 269 |
* @type string $hash valid unique hash for a promo |
| 270 |
* } |
| 271 |
* |
| 272 |
* @return bool true if promo is hidden by user |
| 273 |
*/ |
| 274 |
public function __is_promo_hidden( $promo ) { |
| 275 |
return ! in_array( $promo->hash, $this->hiddenPromotions ); |
| 276 |
} |
| 277 |
|
| 278 |
/** |
| 279 |
* Js Dependencies |
| 280 |
* @return void |
| 281 |
*/ |
| 282 |
public function __enqueue_deps(){ |
| 283 |
wp_enqueue_script( 'wp-util' ); |
| 284 |
wp_enqueue_script( 'jquery' ); |
| 285 |
} |
| 286 |
|
| 287 |
/** |
| 288 |
* Script for hiding promo on user click |
| 289 |
* @return void |
| 290 |
*/ |
| 291 |
public function __get_promo_scripts() { |
| 292 |
?> |
| 293 |
<!--suppress ES6ConvertVarToLetConst --> |
| 294 |
<script> |
| 295 |
(function($){ |
| 296 |
$('body').on('click', '.wapk-promo .notice-dismiss', function (e) { |
| 297 |
e.preventDefault(); |
| 298 |
var $parent = $(this).closest( '.wapk-promo' ); |
| 299 |
wp.ajax.post('webappick_dismiss_promo', { |
| 300 |
dismissed: true, |
| 301 |
hash: $parent.attr( 'id' ), |
| 302 |
_wpnonce: $parent.data( 'nonce' ), |
| 303 |
}); |
| 304 |
}); |
| 305 |
})(jQuery); |
| 306 |
</script> |
| 307 |
<?php |
| 308 |
} |
| 309 |
|
| 310 |
/** |
| 311 |
* Global Promo Styles |
| 312 |
* @return void |
| 313 |
*/ |
| 314 |
public function __get_promo_styles() { |
| 315 |
?> |
| 316 |
<!--suppress CssUnusedSymbol --> |
| 317 |
<style> |
| 318 |
.wapk-promo { border: none; padding: 15px 0; } |
| 319 |
.wapk-promo-wrap { display: flex; justify-content: center; align-items: center; text-align: center; color: inherit; max-width: 1820px; margin: 0 auto; } |
| 320 |
.wapk-promo-wrap.no-column{ display: block; } |
| 321 |
.wapk-column.wapk-logo { flex: 0 0 25%; } |
| 322 |
.wapk-column.wapk-logo img { height: 48px; width: auto; } |
| 323 |
.wapk-details {display: block;} |
| 324 |
.wapk-details h3 { color: inherit; font-size: 30px; margin: 12px 0; } |
| 325 |
.wapk-details p { color: inherit; font-size: 15px; } |
| 326 |
.wapk-column.wapk-details { flex: 0 0 50%; } |
| 327 |
.wapk-column.wapk-btn-container { flex: 0 0 25%; } |
| 328 |
.wapk-promo-wrap .wapk-promo-btn { position: relative; padding: 15px; border-radius: 30px; font-size: 15px; font-weight: 700; display: block; color: inherit; text-decoration: none; max-width: 200px; margin: 0 auto; line-height: normal; height: auto; box-shadow: 1px 2px 0 rgba(0, 0, 0, 0.1); } |
| 329 |
.wapk-promo-wrap .wapk-promo-btn:focus, |
| 330 |
.wapk-promo-wrap .wapk-promo-btn:hover, |
| 331 |
.wapk-promo-wrap .wapk-promo-btn:active { box-shadow: inset 3px 4px 6px 0 rgba(1, 9, 12, 0.25); } |
| 332 |
.wapk-promo-wrap .wapk-promo-btn:active { top: 1px; } |
| 333 |
@media screen and (max-width: 1200px) { |
| 334 |
.wapk-promo-wrap { display: block; overflow: hidden; } |
| 335 |
.wapk-column .wapk-logo { width: 100%; margin: 0 auto; } |
| 336 |
.wapk-column .wapk-details { width: 68%; float: left; margin-right: 4%; margin-top: 32px; } |
| 337 |
.wapk-column.wapk-btn-container { width: 28%; float: right; margin-top: 42px; } |
| 338 |
} |
| 339 |
@media screen and (max-width: 782px) { |
| 340 |
.wapk-promo-wrap .wapk-details { float: none; width: 100%; } |
| 341 |
.wapk-btn-container { float: none; width: 100%; margin-top: 32px; } |
| 342 |
.wapk-column.wapk-btn-container { width: 100%; float: right; margin-top: 42px; } |
| 343 |
} |
| 344 |
</style> |
| 345 |
<?php |
| 346 |
} |
| 347 |
|
| 348 |
/** |
| 349 |
* Ajax Callback handler for hiding promo |
| 350 |
* @return void |
| 351 |
*/ |
| 352 |
public function __webappick_dismiss_promo() { |
| 353 |
if ( |
| 354 |
isset( $_REQUEST['dismissed'], $_REQUEST['hash'], $_REQUEST['_wpnonce'] ) && |
| 355 |
'true' == $_REQUEST['dismissed'] && ! empty( $_REQUEST['hash'] ) && |
| 356 |
wp_verify_nonce( sanitize_text_field( $_REQUEST['_wpnonce'] ), 'wapk-dismiss-promo' ) |
| 357 |
) { |
| 358 |
$this->hiddenPromotions = array_merge( $this->hiddenPromotions, [ sanitize_text_field( $_REQUEST['hash'] ) ] ); |
| 359 |
update_user_option( $this->currentUser, $this->client->getSlug() . '_hidden_promos', $this->hiddenPromotions ); |
| 360 |
wp_send_json_success( esc_html__( 'Promo hidden', 'disco' ) ); |
| 361 |
} |
| 362 |
wp_send_json_error( esc_html__( 'Invalid Request', 'disco' ) ); |
| 363 |
die(); |
| 364 |
} |
| 365 |
|
| 366 |
/** |
| 367 |
* @noinspection PhpUnused |
| 368 |
* Clear Hidden Promotion preference for User |
| 369 |
* @return bool |
| 370 |
*/ |
| 371 |
public function clear_hidden_promos() { |
| 372 |
if ( ! did_action( 'admin_init' ) ) { |
| 373 |
_doing_it_wrong( __METHOD__, esc_html__( 'Method must be invoked inside admin_init action', 'disco' ), '1.0.0' ); |
| 374 |
} |
| 375 |
$this->currentUser = get_current_user_id(); |
| 376 |
return delete_user_option( $this->currentUser, $this->client->getSlug() . '_hidden_promos' ); |
| 377 |
} |
| 378 |
|
| 379 |
/** |
| 380 |
* Clear Cached Promotion data |
| 381 |
* @return bool |
| 382 |
*/ |
| 383 |
public function clear_cache() { |
| 384 |
return delete_transient( $this->client->getSlug() . '_cached_promos' ); |
| 385 |
} |
| 386 |
} |
| 387 |
// End of file Promotions.php. |
| 388 |
|