<?php
/*!
* Jetpack CRM
* https://jetpackcrm.com
*
* WooSync: Woo Admin class
* Collects CRM additions to the WooCommerce backend UI
*/
namespace Automattic\JetpackCRM;
// block direct access
defined( 'ZEROBSCRM_PATH' ) || exit( 0 );
/**
* WooSync Woo Admin class
*/
class Woo_Sync_Woo_Admin_Integration {
/**
* The single instance of the class.
*/
protected static $_instance = null;
/**
* Setup WooSync
* Note: This will effectively fire after core settings and modules loaded
* ... effectively on tail end of `init`
*/
public function __construct() {
// Initialise Hooks
$this->init_hooks();
}
/**
* Main Class Instance.
*
* Ensures only one instance of Woo_Sync_Woo_Admin_Integration is loaded or can be loaded.
*
* @since 2.0
* @static
* @return Woo_Sync_Woo_Admin_Integration main instance
*/
public static function instance() {
if ( is_null( self::$_instance ) ) {
self::$_instance = new self();
}
return self::$_instance;
}
/**
* Initialise Hooks
*/
private function init_hooks() {
global $zbs;
// Abort if Woo isn't active.
if ( ! $zbs->woocommerce_is_active() ) {
return;
}
// Hook into Woo orders listview.
if ( jpcrm_woosync_is_hpos_enabled() ) {
// These hooks are available as of Woo 7.3.0 and are required for HPOS.
add_filter( 'woocommerce_shop_order_list_table_columns', array( $this, 'append_orders_column' ) );
add_action( 'woocommerce_shop_order_list_table_custom_column', array( $this, 'render_orders_column_content' ), 20, 2 );
} else {
add_filter( 'manage_edit-shop_order_columns', array( $this, 'append_orders_column' ) );
add_action( 'manage_shop_order_posts_custom_column', array( $this, 'render_orders_column_content' ), 20, 2 );
}
// Add CRM contact to Woo order page.
add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ) );
}
/**
* Add CRM contact column to Woo orders listview.
*
* @param array $columns Listview columns.
*/
public function append_orders_column( $columns ) {
$rebuilt_columns = array();
// Inserting columns to a specific location
foreach ( $columns as $key => $column ) {
$rebuilt_columns[ $key ] = $column;
if ( $key === 'order_status' ) {
// Inserting after "Status" column
$rebuilt_columns['jpcrm_contact'] = __( 'CRM Contact', 'zero-bs-crm' );
}
}
return $rebuilt_columns;
}
/**
* HTML rendering of our custom orders view column content
*
* @param string $column Column slug.
* @param int $order_or_post_id Order post ID.
*/
public function render_orders_column_content( $column, $order_or_post_id ) {
global $zbs;
switch ( $column ) {
case 'jpcrm_contact':
if ( jpcrm_woosync_is_hpos_enabled() ) {
$order = $order_or_post_id;
} else {
$order = wc_get_order( $order_or_post_id );
}
// Use the helper method to find contact by email or WordPress user ID.
$contact_id = $this->get_contact_id_from_order( $order );
if ( $contact_id > 0 ) {
$url = jpcrm_esc_link( 'view', $contact_id, 'zerobs_customer' );
$label = __( 'View Contact', 'zero-bs-crm' );
$class = 'primary';
} else {
$url = admin_url( 'admin.php?page=' . $zbs->modules->woosync->slugs['hub'] );
$label = __( 'Add Contact', 'zero-bs-crm' );
$class = 'secondary';
}
?>
<div class="zbs-actions">
<a class="button button-<?php echo esc_attr( $class ); ?>" href="<?php echo esc_url( $url ); ?>"><?php echo esc_html( $label ); ?></a>
</div>
<?php
break;
}
}
/**
* Add CRM meta boxes to Woo pages
*/
public function add_meta_boxes() {
// Gather Woo screens where we'll want to add metaboxes.
$screens_to_use = array();
$woo_screens = array( 'shop_order', 'shop_subscription' );
foreach ( $woo_screens as $woo_screen ) {
$potential_screen = wc_get_page_screen_id( $woo_screen );
if ( ! empty( $potential_screen ) ) {
$screens_to_use[] = $potential_screen;
}
}
// Currently if Woo is active we should at least have the orders page, but that could change.
if ( ! empty( $screens_to_use ) ) {
add_meta_box(
'zbs_crm_contact',
__( 'CRM Contact', 'zero-bs-crm' ),
array( $this, 'render_woo_order_page_contact_box' ),
$screens_to_use,
'side',
'core'
);
}
}
/**
* Renders HTML for contact metabox on Woo pages
*
* @param WC_Order|\WP_POST $order_or_post Order or post.
*/
public function render_woo_order_page_contact_box( $order_or_post ) {
// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
global $zbs;
if ( jpcrm_woosync_is_hpos_enabled() ) {
$order = $order_or_post;
} else {
$order = wc_get_order( $order_or_post->ID );
}
$this->render_metabox_styles();
// the customer information pane
$billing_email = $order->get_billing_email();
// No billing email found, so render message and return.
if ( empty( $billing_email ) ) {
?>
<div class="jpcrm-contact-metabox">
<div class="no-crm-contact">
<p style="margin-top:20px;">
<?php esc_html_e( 'Once you save your order to a customer with a billing email, the CRM contact card will display here.', 'zero-bs-crm' ); ?>
</p>
</div>
</div>
<?php
return;
}
// Use the helper method to find contact by email or WordPress user ID.
$contact_id = $this->get_contact_id_from_order( $order );
// Can't find a contact, show "Add Contact" button.
if ( $contact_id <= 0 ) {
?>
<div class="jpcrm-contact-metabox">
<div class="no-crm-contact">
<p style="margin-top:20px;">
<?php esc_html_e( 'No CRM contact found for this order.', 'zero-bs-crm' ); ?>
</p>
<div class="panel-edit-contact">
<a class="view-contact-link button button-secondary" href="<?php echo esc_url( admin_url( 'admin.php?page=' . $zbs->modules->woosync->slugs['hub'] ) ); ?>"><?php echo esc_html__( 'Add Contact', 'zero-bs-crm' ); ?></a>
</div>
</div>
</div>
<?php
return;
}
// retrieve contact
$crm_contact = $zbs->DAL->contacts->getContact( $contact_id );
$contact_name = $zbs->DAL->contacts->getContactFullNameEtc( $contact_id, $crm_contact, array( false, false ) );
$contact_transaction_count = $zbs->DAL->specific_obj_type_count_for_assignee( $contact_id, ZBS_TYPE_TRANSACTION, ZBS_TYPE_CONTACT ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
// Use contact email from CRM if available, otherwise fallback to billing email.
$display_email = ! empty( $crm_contact['email'] ) ? $crm_contact['email'] : $billing_email;
// check avatar mode
$avatar = '';
$avatar_mode = zeroBSCRM_getSetting( 'avatarmode' );
if ( $avatar_mode !== 3 ) {
$avatar = zeroBS_customerAvatarHTML( $contact_id, $crm_contact, 100, 'ui small image centered' );
}
?>
<div class="jpcrm-contact-metabox">
<?php echo $avatar; /* phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped */ ?>
<div id="panel-name"><span class="jpcrm-name"><?php echo esc_html( $contact_name ); ?></span></div>
<div id="panel-status" class="ui label status <?php echo esc_attr( strtolower( $crm_contact['status'] ) ); ?>">
<?php echo esc_html( $crm_contact['status'] ); ?>
</div>
<div>
<?php
echo esc_html( zeroBSCRM_prettifyLongInts( $contact_transaction_count ) . ' ' . ( $contact_transaction_count > 1 ? __( 'Transactions', 'zero-bs-crm' ) : __( 'Transaction', 'zero-bs-crm' ) ) );
?>
</div>
<div class="panel-left-info cust-email">
<i class="ui icon envelope outline"></i> <span class="panel-customer-email"><?php echo esc_html( $display_email ); ?></span>
</div>
<div class="panel-edit-contact">
<a class="view-contact-link button button-primary" href="<?php echo esc_url( jpcrm_esc_link( 'view', $contact_id, 'zerobs_customer' ) ); ?>"><?php echo esc_html__( 'View Contact', 'zero-bs-crm' ); ?></a>
</div>
</div>
<?php
// phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
}
/**
* Get CRM contact ID from WooCommerce order.
*
* This method attempts to identify the CRM contact associated with a WooCommerce order.
*
* Priority order:
* 1. If order has a customer (WordPress user), use that to find CRM contact
* 2. If order is from a guest (no WordPress user), use billing email
*
* This ensures that logged-in customers are always identified by their account,
* even if they use a different billing email on the order.
*
* @param \WC_Order $order WooCommerce order object.
* @return int CRM contact ID, or 0 if not found.
*/
public function get_contact_id_from_order( $order ) {
// First, check if the order has a customer (WordPress user).
// If so, use their account to find the CRM contact.
$customer_id = $order->get_customer_id();
if ( $customer_id > 0 ) {
// Try to find CRM contact linked to this WordPress user.
$contact_id = zeroBS_getCustomerIDFromWPID( $customer_id );
if ( $contact_id > 0 ) {
return $contact_id;
}
// If no CRM contact is linked to WP user, try the WP user's email.
$wp_user = get_user_by( 'id', $customer_id );
if ( $wp_user && ! empty( $wp_user->user_email ) ) {
$contact_id = zeroBS_getCustomerIDWithEmail( $wp_user->user_email );
if ( $contact_id > 0 ) {
return $contact_id;
}
}
}
// Fallback for guest orders: use billing email.
$billing_email = $order->get_billing_email();
if ( ! empty( $billing_email ) ) {
$contact_id = zeroBS_getCustomerIDWithEmail( $billing_email );
if ( $contact_id > 0 ) {
return $contact_id;
}
}
return 0;
}
/**
* Renders metabox styles.
*
* It'd be better to move this to its own file, but putting it here for now.
*/
public function render_metabox_styles() {
?>
<style>
.jpcrm-contact-metabox{
margin: 20px 0;
text-align:center;
}
.zbs-custom-avatar{
border-radius: 50% !important;
max-width:80px;
text-align:center;
padding:10px;
}
.view-contact-link{
margin-top:10px;
}
.cust-email{
padding-bottom:10px;
padding-top:10px;
color: black;
font-weight:700;
}
.jpcrm-name{
font-weight:900;
}
.status{
margin-left: 0;
padding: 0.3em 0.78571429em;
display: inline-block;
border-radius: 5px;
margin-top: 3px;
margin-bottom: 3px;
font-size: 12px !important;
font-weight: 500;
background-color: #ccc;
}
.customer{
background-color: #21BA45 !important;
border-color: #21BA45 !important;
color: #FFFFFF !important;
}
</style>
<?php
}
}