PluginProbe
NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar / 3.3.3
NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar v3.3.3
3.3.3 3.3.2 3.3.1 3.3.0 3.2.14 3.2.13 3.2.12 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 trunk 0.2.5.5 0.2.5.6 0.2.5.7 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 All 158 releases
← All changes | includes/Extensions/WooCommerce/WooCommerce.php +626 -0 0.2.5.7 → 3.3.3 View file →
@@ -1,0 +1,626 @@
1 +<?php
2 +/**
3 + * WooCommerce Extension
4 + *
5 + * @package NotificationX\Extensions
6 + */
7 +
8 +namespace NotificationX\Extensions\WooCommerce;
9 +
10 +use NotificationX\Admin\Entries;
11 +use NotificationX\Admin\Settings;
12 +use NotificationX\Core\Helper;
13 +use NotificationX\Core\PostType;
14 +use NotificationX\Core\Rules;
15 +use NotificationX\GetInstance;
16 +use NotificationX\Extensions\Extension;
17 +use NotificationX\Extensions\GlobalFields;
18 +use NotificationX\Types\Conversions;
19 +
20 +/**
21 + * WooCommerce Extension Class
22 + * @method static WooCommerce get_instance($args = null)
23 + */
24 +class WooCommerce extends Extension {
25 + /**
26 + * Instance of WOOReviews
27 + *
28 + * @var WOOReviews
29 + */
30 + use GetInstance;
31 + use Woo;
32 +
33 + public $priority = 5;
34 + public $id = 'woocommerce';
35 + public $img = NOTIFICATIONX_ADMIN_URL . 'images/extensions/sources/woocommerce.png';
36 + public $doc_link = 'https://notificationx.com/docs/woocommerce-sales-notifications/';
37 + public $types = 'conversions';
38 + public $module = 'modules_woocommerce';
39 + public $module_priority = 3;
40 + public $class = '\WooCommerce';
41 + public $wpml_included = [
42 + 'sales_count', 'donation_count'
43 + ];
44 +
45 + /**
46 + * Initially Invoked when initialized.
47 + */
48 + public function __construct(){
49 + parent::__construct();
50 + }
51 +
52 + public function init_extension()
53 + {
54 + $this->title = __('WooCommerce', 'notificationx');
55 + $this->module_title = __('WooCommerce', 'notificationx');
56 + $this->templates = Conversions::get_instance()->templates;
57 + $this->templates['woo_template_new']['third_param']['product_title_raw'] = __('Product Title Raw', 'notificationx');
58 + $this->templates['woo_template_sales_count']['third_param']['product_title_raw'] = __('Product Title Raw', 'notificationx');
59 + }
60 +
61 + public function init(){
62 + parent::init();
63 + add_action('woocommerce_order_status_changed', array($this, 'status_transition'), 10, 4);
64 + add_action('woocommerce_process_shop_order_meta', array( $this, 'manual_order'), 10, 2 );
65 + add_action('woocommerce_new_order_item', array($this, 'save_new_orders'), 10, 3);
66 + add_filter("nx_entry_show_on_frontend_{$this->id}", [$this, 'entry_display'], 10, 3);
67 + }
68 +
69 + /**
70 + * This function is responsible for checking if the product is published or not.
71 + *
72 + * @param boolean $data
73 + * @param array $post
74 + * @param array $entry
75 + * @return boolean
76 + */
77 + public function entry_display($data, $post, $entry) {
78 + $product_id = $post['product_id'];
79 + if ( empty($product_id) ) {
80 + return true;
81 + }
82 +
83 + $product = wc_get_product($product_id);
84 + if ( ! $product ) {
85 + return true;
86 + }
87 +
88 + if ( $product->get_status() !== 'publish' ) {
89 + return true;
90 + }
91 +
92 + return false;
93 + }
94 +
95 +
96 + public function init_fields(){
97 + parent::init_fields();
98 + add_filter('nx_link_types', [$this, 'link_types']);
99 + add_filter( 'nx_woo_order_status', array( $this, 'order_status' ), 11 );
100 + $this->_init_fields();
101 + }
102 +
103 + /**
104 + * This functions is hooked
105 + *
106 + * @return void
107 + */
108 + public function admin_actions() {
109 + parent::admin_actions();
110 + add_filter("nx_can_entry_{$this->id}", array($this, 'check_order_status'), 10, 3);
111 + add_filter("nx_can_entry_{$this->id}", array($this->get_type(), 'nx_can_entry'), 10, 3);
112 + }
113 +
114 + public function public_actions(){
115 + parent::public_actions();
116 +
117 + add_filter("nx_filtered_data_{$this->id}", array($this, 'multiorder_combine'), 11, 3);
118 + }
119 +
120 + public function wpml_actions(){
121 + add_filter("nx_filtered_entry_{$this->id}", array($this, 'wpml_translate'), 11, 2);
122 + add_filter("nx_notification_link_{$this->id}", [$this, 'product_link'], 10, 3);
123 +
124 + }
125 +
126 + public function source_error_message($messages) {
127 + if (!$this->class_exists()) {
128 + $url = admin_url('plugin-install.php?s=woocommerce&tab=search&type=term');
129 + $messages[$this->id] = [
130 + 'message' => sprintf( '%s <a href="%s" target="_blank">%s</a> %s',
131 + __( 'You have to install', 'notificationx' ),
132 + $url,
133 + __( 'WooCommerce', 'notificationx' ),
134 + __( 'plugin first.', 'notificationx' )
135 + ),
136 + 'html' => true,
137 + 'type' => 'error',
138 + 'rules' => Rules::is('source', $this->id),
139 + ];
140 + }
141 + return $messages;
142 + }
143 +
144 +
145 + public function order_status($options){
146 + if(function_exists('wc_get_order_statuses')){
147 + $options = GlobalFields::get_instance()->normalize_fields(wc_get_order_statuses(), 'source', $this->id, $options);
148 + }
149 + return $options;
150 + }
151 +
152 +
153 + /**
154 + * Adds option to Link Type field in Content tab.
155 + *
156 + * @param array $options
157 + * @return array
158 + */
159 + public function link_types($options) {
160 + $options = GlobalFields::get_instance()->normalize_fields([
161 + 'product_page' => __('Product Page', 'notificationx'),
162 + ], 'source', $this->id, $options);
163 +
164 + return $options;
165 + }
166 +
167 + public function saved_post($post, $data, $nx_id) {
168 + $this->delete_notification(null, $nx_id);
169 + $this->get_notification_ready($data);
170 + }
171 +
172 + /**
173 + * This function is responsible for making the notification ready for first time we make the notification.
174 + *
175 + * @param string $type
176 + * @param array $data
177 + * @return void
178 + */
179 + public function get_notification_ready($post = array()) {
180 + if (!is_null($orders = $this->get_orders($post))) {
181 + // $orders = Helper::sortBy($orders, 'woocommerce');
182 + $entries = [];
183 + foreach ($orders as $key => $order) {
184 + $entries[] = [
185 + 'nx_id' => $post['nx_id'],
186 + 'source' => $this->id,
187 + 'entry_key' => $key,
188 + 'data' => $order,
189 + // 'updated_at' => Helper::mysql_time($order['timestamp']),
190 + ];
191 + }
192 + $this->update_notifications($entries);
193 + }
194 + return $orders;
195 + }
196 +
197 + /**
198 + * It will generate and save a notification
199 + * when orders are placed.
200 + *
201 + * @param int $item_id
202 + * @param WC_Order_Item_Product $item
203 + * @param int $order_id
204 + * @return void
205 + */
206 + public function save_new_orders($item_id, $item, $order_id) {
207 + $single_notification = $this->ordered_product($item_id, $item, $order_id);
208 + if (!empty($single_notification)) {
209 + $key = $order_id . '-' . $item_id;
210 + $this->save([
211 + 'source' => $this->id,
212 + 'entry_key' => $key,
213 + 'data' => $single_notification,
214 + ]);
215 + return true;
216 + }
217 + return false;
218 + }
219 +
220 + public function status_transition($id, $from, $to, $order) {
221 + $has_course = false;
222 + $from = "wc-$from";
223 + $to = "wc-$to";
224 + $items = $order->get_items();
225 + $posts = PostType::get_instance()->get_posts([
226 + 'source' => $this->id,
227 + 'enabled' => true,
228 + ]);
229 + if (!empty($posts)) {
230 + foreach ($posts as $post) {
231 + $done = !empty($post['order_status']) ? $post['order_status'] : ['wc-completed', 'wc-processing'];
232 + $status = array_keys(wc_get_order_statuses());
233 + $status = array_diff($status, $done);
234 + $status[] = 'wcf-main-order';
235 +
236 + foreach ($items as $item) {
237 + $key = $id . '-' . $item->get_id();
238 + if (function_exists('tutor_utils')) {
239 + $has_course = tutor_utils()->product_belongs_with_course($item->get_product_id());
240 + }
241 + if ($has_course) {
242 + continue;
243 + }
244 +
245 + if (in_array($from, $done) && in_array($to, $status)) {
246 + $this->delete_notification($key);
247 + }
248 +
249 + if (in_array($from, $status) && in_array($to, $done)) {
250 + $single_notification = $this->ordered_product($item->get_id(), $item, $order);
251 + if (!empty($single_notification)) {
252 + $this->update_notification([
253 + 'nx_id' => $post['nx_id'],
254 + 'source' => $this->id,
255 + 'entry_key' => $key,
256 + 'data' => $single_notification,
257 + ], false);
258 + }
259 + }
260 + }
261 +
262 + }
263 + }
264 +
265 + return true;
266 + }
267 +
268 + // define the manual_order callback
269 + public function manual_order( $order_id, $post ){
270 + $orders = [];
271 + $order = wc_get_order($order_id);
272 + $items = $order->get_items();
273 + foreach( $items as $item ) {
274 + $tutor_product = metadata_exists('post', $item->get_product_id(), "_tutor_product");
275 + if($tutor_product ) {
276 + continue;
277 + }
278 + $order_item = $this->ordered_product($item->get_id(), $item, $order);
279 + if($order_item){
280 + $this->save([
281 + 'source' => $this->id,
282 + 'entry_key' => $order->get_id() . '-' . $item->get_id(),
283 + 'data' => $order_item,
284 + ], false);
285 + }
286 + }
287 + }
288 +
289 + /**
290 + * This function is responsible for making ready the orders data.
291 + *
292 + * @param int $item_id
293 + * @param \WC_Order_Item_Product $item
294 + * @param int $order_id
295 + * @return void
296 + */
297 + public function ordered_product($item_id, $item, $order_id) {
298 + if (!$item instanceof \WC_Order_Item_Product) {
299 + return false;
300 + }
301 + $product = wc_get_product( $item->get_product_id() );
302 + if( empty( $product ) ) {
303 + return false;
304 + }
305 + $if_has_course = false;
306 + if (function_exists('tutor_utils')) {
307 + $if_has_course = tutor_utils()->product_belongs_with_course($item->get_product_id());
308 + }
309 + if ($if_has_course) {
310 + return false;
311 + }
312 + $new_order = [];
313 + if (is_int($order_id)) {
314 + $order = new \WC_Order($order_id);
315 + } else {
316 + $order = $order_id;
317 + }
318 +
319 + if (!$order instanceof \WC_Order) {
320 + return false;
321 + }
322 +
323 + $new_order['status'] = 'wc-' . $order->get_status();
324 +
325 + $date = $order->get_date_created();
326 + $countries = new \WC_Countries();
327 + $shipping_country = $order->get_billing_country();
328 + if (empty($shipping_country)) {
329 + $shipping_country = $order->get_shipping_country();
330 + }
331 + if (!empty($shipping_country)) {
332 + $new_order['country'] = isset($countries->countries[$shipping_country]) ? $countries->countries[$shipping_country] : '';
333 + $billing_state = $order->get_billing_state();
334 + if (!empty($billing_state)) {
335 + $new_order['state'] = isset($countries->states[$shipping_country], $countries->states[$shipping_country][$billing_state]) ? $countries->states[$shipping_country][$billing_state] : $billing_state;
336 + }else {
337 + $shipping_state = $order->get_shipping_state();
338 + if (!empty($shipping_state)) {
339 + $new_order['state'] = isset($countries->states[$shipping_country], $countries->states[$shipping_country][$shipping_state]) ? $countries->states[$shipping_country][$shipping_state] : $shipping_state;
340 + }
341 + }
342 + }
343 + $new_order['city'] = $order->get_billing_city();
344 + if (empty($new_order['city'])) {
345 + $new_order['city'] = $order->get_shipping_city();
346 + }
347 +
348 + $new_order['ip'] = $order->get_customer_ip_address();
349 + $product_data = $this->ready_product_data($item->get_data());
350 + if (!empty($product_data)) {
351 + $new_order['order_id'] = is_int($order_id) ? $order_id : $order_id->get_id();
352 + $product = $item->get_product();
353 + if ( isset($product) && $product->get_type() === 'variation' ) {
354 + $new_order['var_product_id'] = $item->get_variation_id();
355 + }
356 + $new_order['product_id'] = $item->get_product_id();
357 + $new_order['title'] = wp_strip_all_tags($product_data['title']);
358 + $new_order['link'] = $product_data['link'];
359 + }
360 + if($date && method_exists($date, 'getTimestamp')){
361 + $new_order['timestamp'] = $date->getTimestamp();
362 + }
363 + else{
364 + $new_order['timestamp'] = time();
365 + }
366 + return array_merge($new_order, $this->buyer($order));
367 + }
368 +
369 + /**
370 + * It will take an array to make data clean
371 + *
372 + * @param array $data
373 + * @return void
374 + */
375 + protected function ready_product_data($data) {
376 + if (empty($data)) {
377 + return;
378 + }
379 + $product = wc_get_product( $data['product_id'] );
380 + if( empty( $product ) ) {
381 + return;
382 + }
383 + return array(
384 + 'title' => $data['name'],
385 + 'product_name' => $product ? $product->get_name() : $data['name'],
386 + 'link' => get_permalink($data['product_id']),
387 + );
388 + }
389 +
390 + /**
391 + * This function is responsible for getting
392 + * the buyer name from order.
393 + *
394 + * @param WC_Order $order
395 + * @return void
396 + */
397 + protected function buyer(\WC_Order $order) {
398 + $first_name = $last_name = $email = '';
399 + $buyer_data = [];
400 +
401 + if (empty($first_name)) {
402 + $first_name = $order->get_billing_first_name();
403 + }
404 +
405 + if (empty($last_name)) {
406 + $last_name = $order->get_billing_last_name();
407 + }
408 +
409 + if (empty($email)) {
410 + $email = $order->get_billing_email();
411 + }
412 +
413 + $buyer_data['first_name'] = $first_name;
414 + $buyer_data['last_name'] = $last_name;
415 + $buyer_data['name'] = $first_name . ' ' . mb_substr($last_name, 0, 1);
416 + $buyer_data['email'] = $email;
417 +
418 + return $buyer_data;
419 + }
420 +
421 + /**
422 + * Get all the orders from database using a date query
423 + * for limitation.
424 + *
425 + * @param array $data
426 + * @return array
427 + */
428 + public function get_orders($data = array()) {
429 + if (empty($data) || !function_exists('wc_get_orders')) return null;
430 + $orders = [];
431 + $time = Helper::generate_time_string($data);
432 + $from = strtotime(gmdate('Y-m-d h:i', $time));
433 + $status = !empty($data['order_status']) ? $data['order_status'] : ['wc-completed', 'wc-processing'];
434 + $wc_orders = \wc_get_orders([
435 + 'status' => $status,
436 + 'date_created' => '>' . $from,
437 + 'numberposts' => isset($data['display_last']) ? intval($data['display_last']) : 10,
438 + ]);
439 + foreach ($wc_orders as $order) {
440 + $items = $order->get_items();
441 + foreach ($items as $item) {
442 + $tutor_product = metadata_exists('post', $item->get_product_id(), "_tutor_product");
443 + if ($tutor_product) {
444 + continue;
445 + }
446 + $orders[$order->get_id() . '-' . $item->get_id()] = $this->ordered_product($item->get_id(), $item, $order);
447 + }
448 + }
449 + return $orders;
450 + }
451 + /**
452 + * Limit entry by selected status;
453 + *
454 + * @param bool $return
455 + * @param array $entry
456 + * @param array $settings
457 + * @return boolean
458 + */
459 + public function check_order_status($return, $entry, $settings){
460 + $done = !empty($settings['order_status']) ? $settings['order_status'] : ['wc-completed', 'wc-processing'];
461 + if(!in_array($entry['data']['status'], $done)){
462 + return false;
463 + }
464 +
465 + return $return;
466 + }
467 +
468 +
469 + public function wpml_translate($entry, $settings) {
470 + if(!empty($entry['product_id'])){
471 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
472 + $product_id = apply_filters( 'wpml_object_id', $entry['product_id'], 'product', false);
473 + $product = wc_get_product($product_id);
474 + if($product){
475 + // $current_lang = apply_filters( 'wpml_current_language', NULL );
476 +
477 + $entry['product_id'] = $product_id;
478 + $entry['title'] = $product->get_name();
479 + }
480 + }
481 +
482 + return $entry;
483 + }
484 +
485 +
486 + public function product_link($link, $post, $entry) {
487 + if(!empty($entry['product_id']) && !empty( $post['link_type'] ) && $post['link_type'] === 'product_page' ){
488 + $link = get_permalink($entry['product_id']);
489 + }
490 + return $link;
491 + }
492 +
493 + public function multiorder_combine($data, $settings) {
494 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
495 + $should_combine = apply_filters('nx_should_combine', true, $data, $settings);
496 + if (!$should_combine || empty($settings['combine_multiorder']) || intval($settings['combine_multiorder']) != 1 ) {
497 + return $data;
498 + }
499 + $items = [];
500 + $item_counts = [];
501 + $item_titles = [];
502 + foreach ($data as $key => $item) {
503 + $order_id = isset( $item['order_id'] ) ? $item['order_id'] : ( isset( $item['id'] ) ? $item['id'] : null );
504 + if( $order_id === null ) {
505 + continue;
506 + }
507 + if (!isset($items[$order_id])) {
508 + $items[$order_id] = $item;
509 + } else {
510 + $item_counts[$order_id] = isset($item_counts[$order_id]) ? ++$item_counts[$order_id] : 1;
511 + if ( isset( $item['title'] ) ) {
512 + $item_titles[$order_id][] = $item['title'];
513 + }
514 + }
515 + }
516 +
517 + $display = !empty($settings['combine_multiorder_display'])
518 + ? $settings['combine_multiorder_display']
519 + : 'count';
520 +
521 + foreach ($item_counts as $key => $item) {
522 +
523 + if ( $display === 'list' && ! empty( $item_titles[$key] ) ) {
524 + // List the actual product names, e.g. "Product A & Product B".
525 + $products_more_title = implode(
526 + __(' & ', 'notificationx'),
527 + $item_titles[$key]
528 + );
529 + } else {
530 + $singular = !empty($settings['combine_multiorder_text'])
531 + ? $settings['combine_multiorder_text']
532 + : __('more product', 'notificationx');
533 +
534 + $plural = !empty($settings['combine_multiorder_text_plural'])
535 + ? $settings['combine_multiorder_text_plural']
536 + : __('more products', 'notificationx');
537 +
538 + // Both forms are already resolved here - either a user-entered
539 + // override or an already-translated default - so they are never
540 + // catalogue msgids. _n() would find no entry and fall back to
541 + // exactly this choice, so make it explicit.
542 + $more_product_text = sprintf(
543 + 1 == $item ? $singular : $plural,
544 + $item
545 + );
546 + $products_more_title = sprintf('%d %s', $item, $more_product_text);
547 + }
548 +
549 + $items[$key]['title'] = sprintf(
550 + /* translators: %1$s: product title, %2$s: combined "and N more products" text */
551 + __('%1$s & %2$s', 'notificationx'),
552 + $items[$key]['title'],
553 + $products_more_title
554 + );
555 + }
556 +
557 + // @todo maybe sort
558 + return $items;
559 + }
560 +
561 + /**
562 + * Image action callback
563 + * @param array $image_data
564 + * @param array $data
565 + * @param stdClass $settings
566 + * @return array
567 + */
568 + public function notification_image($image_data, $data, $settings) {
569 + if (!$settings['show_default_image'] && $settings['show_notification_image'] === 'featured_image') {
570 + $id = $this->product_img_id( $data );
571 + if ( ! empty( $id ) ) {
572 + $image_size = (string) Settings::get_instance()->get('settings.notification_image_size', '100_100');
573 + $image_size = explode('_', $image_size);
574 + $width = 100;
575 + $height = 100;
576 + if( !empty( $image_size[0] ) && !empty( $image_size[1] ) ) {
577 + $width = !empty($image_size[0]) ? $image_size[0] : 100;
578 + $height = !empty( $image_size[1] ) ? $image_size[1] : 100;
579 + }
580 + $product_image = wp_get_attachment_image_src(
581 + get_post_thumbnail_id($id),
582 + [$width, $height],
583 + false
584 + );
585 + $image_data['url'] = is_array($product_image) ? $product_image[0] : '';
586 + }
587 + }
588 + return $image_data;
589 + }
590 +
591 + private function product_img_id( $data ) {
592 + if (!empty($data['var_product_id']) && has_post_thumbnail($data['var_product_id'])) {
593 + return $data['var_product_id'];
594 + } elseif (!empty($data['product_id']) && has_post_thumbnail($data['product_id'])) {
595 + return $data['product_id'];
596 + }
597 + }
598 +
599 + public function fallback_data($data, $entry) {
600 + $data['name'] = __('Someone', 'notificationx');
601 + $data['first_name'] = __('Someone', 'notificationx');
602 + $data['last_name'] = __('Someone', 'notificationx');
603 + $data['anonymous_title'] = __('Anonymous Product', 'notificationx');
604 + if(empty($entry['product_title']) && !empty($entry['title'])){
605 + $data['product_title'] = $entry['title'];
606 + }
607 + return $data;
608 + }
609 +
610 + public function doc(){
611 + /* translators: %1$s: WooCommerce installed & activated link URL, %2$s: documentation link URL, %3$s: Watch video tutorial link URL, %4$s: Best FOMO and Social Proof Plugin link URL, %5$s: boost WooCommerce Sales link URL */
612 + return sprintf(__('<p>Make sure that you have <a target="_blank" href="%1$s">WooCommerce installed & activated</a> to use this campaign. For further assistance, check out our step by step <a target="_blank" href="%2$s">documentation</a>.</p>
613 + <p>🎦 <a href="%3$s" target="_blank">Watch video tutorial</a> to learn quickly</p>
614 + <p>⭐ NotificationX Integration with WooCommerce</p>
615 + <p><strong>Recommended Blog:</strong></p>
616 + <p>🔥 Why NotificationX is The <a target="_blank" href="%4$s">Best FOMO and Social Proof Plugin</a> for WooCommerce?</p>
617 + <p>🚀 How to <a target="_blank" href="%5$s">boost WooCommerce Sales</a> Using NotificationX</p>', 'notificationx'),
618 + 'https://wordpress.org/plugins/woocommerce/',
619 + 'https://notificationx.com/docs/woocommerce-sales-notifications/',
620 + 'https://www.youtube.com/watch?v=dVthd36hJ-E&t=1s',
621 + 'https://notificationx.com/integrations/woocommerce/',
622 + 'https://notificationx.com/blog/best-fomo-and-social-proof-plugin-for-woocommerce/'
623 + );
624 + }
625 +
626 +}