PluginProbe
NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar / trunk
NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar vtrunk
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 1.2.0 1.2.1 All 156 releases
notificationx / includes / Extensions / WooCommerce / WooCommerce.php

WooCommerce.php in NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar trunk, at includes/Extensions/WooCommerce/WooCommerce.php

627 lines 23.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 }
627