Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 188
0.00% covered (danger)
0.00%
0 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
Woo_Sync_Contact_Tabs
0.00% covered (danger)
0.00%
0 / 187
0.00% covered (danger)
0.00%
0 / 10
1122
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 instance
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 init_hooks
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 append_info_tabs
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
30
 generate_bookings_tab_html
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 1
30
 generate_subscriptions_tab_html
0.00% covered (danger)
0.00%
0 / 37
0.00% covered (danger)
0.00%
0 / 1
20
 generate_memberships_tab_html
0.00% covered (danger)
0.00%
0 / 37
0.00% covered (danger)
0.00%
0 / 1
30
 display_membership_status
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
6
 get_contact_memberships
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 get_contact_subscriptions
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
42
1<?php
2/*
3 * Jetpack CRM
4 * https://jetpackcrm.com
5 *
6 * WooSync: Contact Tabs
7 *  Adds extra tabs to contact single view: vitals tab set
8 */
9namespace Automattic\JetpackCRM;
10
11// block direct access
12defined( 'ZEROBSCRM_PATH' ) || exit( 0 );
13
14/**
15 * WooSync Contact Tabs class
16 */
17class Woo_Sync_Contact_Tabs {
18
19    /**
20     * The single instance of the class.
21     */
22    protected static $_instance = null;
23
24    /**
25     * Setup WooSync Contact Tabs
26     * Note: This will effectively fire after core settings and modules loaded
27     * ... effectively on tail end of `init`
28     */
29    public function __construct() {
30
31        // Initialise Hooks
32        $this->init_hooks();
33    }
34
35    /**
36     * Main Class Instance.
37     *
38     * Ensures only one instance of Woo_Sync_Contact_Tabs is loaded or can be loaded.
39     *
40     * @since 2.0
41     * @static
42     * @see
43     * @return Woo_Sync_Contact_Tabs main instance
44     */
45    public static function instance() {
46        if ( self::$_instance === null ) {
47            self::$_instance = new self();
48        }
49        return self::$_instance;
50    }
51
52    /**
53     * Initialise Hooks
54     */
55    private function init_hooks() {
56
57        // add in tabs
58        add_filter( 'jetpack-crm-contact-vital-tabs', array( $this, 'append_info_tabs' ), 10, 2 );
59    }
60
61    /**
62     * Wire in ant applicable tabs (subs, memberships, bookings)
63     */
64    public function append_info_tabs( $array, $id ) {
65
66        if ( ! is_array( $array ) ) {
67            $array = array();
68        }
69
70        // Woo Subscriptions
71        if ( function_exists( 'wcs_get_users_subscriptions' ) ) {
72            $array[] = array(
73                'id'      => 'woocommerce-subscriptions-tab',
74                'name'    => __( 'Subscriptions', 'zero-bs-crm' ),
75                'content' => $this->generate_subscriptions_tab_html( $id ),
76            );
77        }
78
79        // Woo Memberships
80        if ( function_exists( 'wc_memberships_get_user_memberships' ) ) {
81            $array[] = array(
82                'id'      => 'woocommerce-memberships-tab',
83                'name'    => __( 'Memberships', 'zero-bs-crm' ),
84                'content' => $this->generate_memberships_tab_html( $id ),
85            );
86        }
87
88        // Woo Bookings
89        if ( class_exists( 'WC_Bookings_Controller' ) ) {
90            $array[] = array(
91                'id'      => 'woocommerce-bookings-tab',
92                'name'    => __( 'Upcoming Bookings', 'zero-bs-crm' ),
93                'content' => $this->generate_bookings_tab_html( $id ),
94            );
95        }
96
97        return $array;
98    }
99
100    /**
101     * Draw the Woo Bookings Contact Vitals tab
102     */
103    private function generate_bookings_tab_html( $object_id = -1 ) {
104
105        global $zbs;
106
107        // return html
108        $html = '';
109
110        // retrieve bookings
111        $bookings = $zbs->modules->woosync->get_future_woo_bookings_for_object( $object_id );
112
113        if ( count( $bookings ) > 0 ) {
114
115            $html             .= '<div class="table-wrap woo-sync-book">';
116                $html         .= '<table class="ui single line table">';
117                    $html     .= '<thead>';
118                    $html     .= '<tr>';
119                        $html .= '<th scope="col" class="booking-id">' . __( 'ID', 'woocommerce-bookings' ) . '</th>';
120                        $html .= '<th scope="col" class="booked-product">' . __( 'Booked', 'woocommerce-bookings' ) . '</th>';
121                        $html .= '<th scope="col" class="booking-start-date">' . __( 'Start Date', 'woocommerce-bookings' ) . '</th>';
122                        $html .= '<th scope="col" class="booking-end-date">' . __( 'End Date', 'woocommerce-bookings' ) . '</th>';
123                        $html .= '<th scope="col" class="booking-status">' . __( 'Status', 'woocommerce-bookings' ) . '</th>';
124                    $html     .= '</tr>';
125                $html         .= '</thead>';
126                $html         .= '<tbody>';
127            foreach ( $bookings as $booking ) {
128
129                    $html     .= '<tr>';
130                        $html .= '<td class="booking-id">' . esc_html( $booking->get_id() ) . '</td>';
131                        $html .= '<td class="booked-product">';
132                if ( $booking->get_product() && $booking->get_product()->is_type( 'booking' ) ) :
133                    $html .= '<a href="' . esc_url( get_permalink( $booking->get_product()->get_id() ) ) . '">';
134                    $html .= esc_html( $booking->get_product()->get_title() );
135                    $html .= '</a>';
136                            endif;
137                        $html .= '</td>';
138
139                        $status = esc_html( wc_bookings_get_status_label( $booking->get_status() ) );
140
141                        $html .= '<td class="booking-start-date">' . esc_html( $booking->get_start_date() ) . '</td>';
142                        $html .= '<td class="booking-end-date">' . esc_html( $booking->get_end_date() ) . '</td>';
143                        $html .= '<td><span class="ui label ' . strtolower( $status ) . '">' . $status . '</span></td>';
144
145                    $html .= '</tr>';
146            }
147
148                    $html .= '</tbody>';
149                $html     .= '</table>';
150            $html         .= '</div>';
151
152        } else {
153
154            $html .= '<div class="ui message info blue"><i class="ui icon info circle"></i>' . __( 'This contact does not have any upcoming WooCommerce Bookings.', 'zero-bs-crm' ) . '</div>';
155
156        }
157
158        return $html;
159    }
160
161    /**
162     * Returns HTML that can be used to render the Subscriptions Table.
163     * When no WordPress user is found a <div> is returned with an info message.
164     * When no subscriptions are found a <div> is returned with an info message.
165     *
166     * @param integer $object_id Contact ID.
167     * @return string HTML that can be used to render the Subscriptions Table.
168     */
169    private function generate_subscriptions_tab_html( $object_id = -1 ) {
170
171        $subscriptions = array();
172        if ( zeroBSCRM_getClientPortalUserID( $object_id ) > 0 ) {
173            // Retrieve any subs against the main email or aliases.
174            $subscriptions = $this->get_contact_subscriptions( $object_id );
175        }
176        if ( count( $subscriptions ) === 0 ) {
177            return '<div class="ui message info blue"><i class="ui icon info circle"></i>' . __( 'This contact does not have any WooCommerce Subscriptions yet.', 'zero-bs-crm' ) . '</div>';
178        }
179
180        $html  = '';
181        $html .= '<div class="table-wrap woo-sync-subs">';
182        $html .= '<table class="ui single line table">';
183        $html .= '<thead><tr>';
184        $html .= '<th>' . __( 'Subscription', 'zero-bs-crm' ) . '</th>';
185        $html .= '<th>' . __( 'Status', 'zero-bs-crm' ) . '</th>';
186        $html .= '<th>' . __( 'Amount', 'zero-bs-crm' ) . '</th>';
187        $html .= '<th>' . __( 'Start', 'zero-bs-crm' ) . '</th>';
188        $html .= '<th>' . __( 'Renews', 'zero-bs-crm' ) . '</th>';
189        $html .= '</tr></thead>';
190        $html .= '<tbody>';
191
192        foreach ( $subscriptions as $order_id ) {
193            $order        = wc_get_order( $order_id );
194            $status       = $order->get_status();
195            $date_created = $order->get_date_created();
196            $date_renew   = $order->get_date( 'next_payment_date' );
197            $price        = $order->get_formatted_order_total();
198            $name         = '';
199            $sub_link     = admin_url( "post.php?post={$order_id}&action=edit" );
200            $created      = jpcrm_uts_to_date_str( strtotime( $date_created ) );
201            $next         = jpcrm_uts_to_date_str( strtotime( $date_renew ) );
202
203            $html .= '<tr>';
204            $html .= '<td><a href="' . esc_url( $sub_link ) . '">' . $name . __( ' Subscription #', 'zero-bs-crm' ) . $order_id . '</a></td>';
205            $html .= '<td><span class="ui label ' . $status . '">' . $status . '</span></td>';
206            $html .= '<td>' . $price . '</td>';
207            $html .= '<td>' . $created . '</td>';
208            $html .= '<td>' . $next . '</td>';
209            $html .= '</tr>';
210
211        }
212
213        $html .= '</tbody>';
214        $html .= '</table>';
215        $html .= '</div>';
216
217        return $html;
218    }
219
220    /**
221     * Generate HTML for memberships contact vitals tab
222     */
223    private function generate_memberships_tab_html( $object_id = -1 ) {
224
225        $data = $this->get_contact_memberships( $object_id );
226
227        if ( $data['message'] === 'notfound' || count( $data['memberships'] ) <= 0 ) {
228
229            return '<div class="ui message info blue"><i class="ui icon info circle"></i>' . __( 'This contact does not have any WooCommerce Memberships yet.', 'zero-bs-crm' ) . '</div>';
230
231        }
232
233        $memberships = $data['memberships'];
234
235        $html  = '';
236        $html .= '<div class="table-wrap woo-sync-mem">';
237        $html .= '<table class="ui single line table">';
238        $html .= '<thead><tr>';
239        $html .= '<th>' . __( 'Membership', 'zero-bs-crm' ) . '</th>';
240        $html .= '<th>' . __( 'Status', 'zero-bs-crm' ) . '</th>';
241        $html .= '<th>' . __( 'Name', 'zero-bs-crm' ) . '</th>';
242        $html .= '<th>' . __( 'Start', 'zero-bs-crm' ) . '</th>';
243        $html .= '<th>' . __( 'Expires', 'zero-bs-crm' ) . '</th>';
244        $html .= '</tr></thead>';
245        $html .= '<tbody>';
246
247        foreach ( $memberships as $membership ) {
248
249            // populate fields
250            $member_id    = $membership->id;
251            $status       = $this->display_membership_status( $membership->get_status() );
252            $name         = $membership->plan->name;
253            $date_expires = $membership->get_end_date( 'Y-m-d H:i:s' );
254            $date_created = $membership->get_start_date();
255            $created      = jpcrm_uts_to_date_str( strtotime( $date_created ) );
256
257            if ( empty( $date_expires ) ) {
258
259                $expires = __( 'Never', 'zero-bs-crm' );
260
261            } else {
262
263                $expires = jpcrm_uts_to_date_str( strtotime( $date_expires ) );
264
265            }
266
267            $membership_link = admin_url( 'post.php?post=' . $membership->id . '&action=edit' );
268
269            $html .= '<tr>';
270            $html .= '<td><a href="' . esc_url( $membership_link ) . '">' . sprintf( __( ' Membership #%s', 'zero-bs-crm' ), $member_id ) . '</a></td>';
271            $html .= '<td><span class="ui label ' . $status . '">' . $status . '</span></td>';
272            $html .= '<td>' . $name . '</td>';
273            $html .= '<td>' . $created . '</td>';
274            $html .= '<td>' . $expires . '</td>';
275            $html .= '</tr>';
276
277        }
278
279        $html .= '</tbody>';
280        $html .= '</table>';
281        $html .= '</div>';
282
283        return $html;
284    }
285
286    /**
287     * Helper to display membership statuses
288     */
289    private function display_membership_status( $status = '' ) {
290
291        $woocommerce_statuses = array(
292            'wcm-active'               => __( 'active', 'zero-bs-crm' ),
293            'wcm-complimentary'        => __( 'complimentary', 'zero-bs-crm' ),
294            'wcm-pending'              => __( 'pending', 'zero-bs-crm' ),
295            'wcm-delayed'              => __( 'delayed', 'zero-bs-crm' ),
296            'wcm-pending-cancelletion' => __( 'pending cancellation', 'zero-bs-crm' ),
297            'wcm-paused'               => __( 'paused', 'zero-bs-crm' ),
298            'wcm-expired'              => __( 'expired', 'zero-bs-crm' ),
299            'wcm-cancelled'            => __( 'cancelled', 'zero-bs-crm' ),
300        );
301
302        $display_status = $status;
303
304        if ( array_key_exists( $status, $woocommerce_statuses ) ) {
305
306            $display_status = $woocommerce_statuses[ $status ];
307
308        }
309
310        return $display_status;
311    }
312
313    /**
314     * Retrieves memberships for a contact
315     */
316    private function get_contact_memberships( $object_id = -1 ) {
317
318        $wp_id = zeroBS_getCustomerWPID( $object_id );
319
320        if ( $wp_id > 0 ) {
321
322            return array(
323                'message'     => 'success',
324                'memberships' => wc_memberships_get_user_memberships( $wp_id ),
325            );
326
327        } else {
328
329            return array(
330                'message'     => 'notfound',
331                'memberships' => array(),
332            );
333
334        }
335    }
336
337    /**
338     * Retrieves any Woo Subscriptions against a contact
339     *
340     * @param int $object_id Contact ID.
341     */
342    private function get_contact_subscriptions( $object_id = -1 ) {
343        $all_sub_statuses = array_keys( wcs_get_subscription_statuses() );
344
345        $subscription_ids = array();
346
347        if ( $object_id > 0 ) {
348
349            // 1 - get the subscription IDs for the attached wp user
350            $user_id = zeroBS_getCustomerWPID( $object_id );
351            if ( $user_id > 0 ) {
352                $args = array(
353                    'customer_id' => $user_id,
354                    'status'      => $all_sub_statuses,
355                    'type'        => 'shop_subscription',
356                    'return'      => 'ids',
357                );
358
359                $subscription_ids = wc_get_orders( $args );
360            }
361
362            // 2 - find subs for all emails (inc aliases)
363            global $zbs;
364            $emails = $zbs->DAL->contacts->getContactEmails( $object_id ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
365            if ( is_array( $emails ) ) {
366
367                foreach ( $emails as $email ) {
368                    if ( empty( $email ) ) {
369                        continue;
370                    }
371
372                    $args = array(
373                        'billing_email' => $email,
374                        'status'        => $all_sub_statuses,
375                        'type'          => 'shop_subscription',
376                        'return'        => 'ids',
377                    );
378
379                    $subscription_ids_by_email = wc_get_orders( $args );
380                    $subscription_ids          = array_merge( $subscription_ids, $subscription_ids_by_email );
381                }
382            }
383
384            // 3 - remove any duplicate IDs between array_1 and array_2
385            $subscription_ids = array_unique( $subscription_ids, SORT_REGULAR );
386        }
387
388        return $subscription_ids;
389    }
390}