like any standard field.
*
* Company (only when B2B mode is on) isn't a contact field: after the contact
* is saved, a company with that name is found or created via
* zeroBS_integrations_addOrUpdateCompany() and linked to the contact,
* keeping any companies it's already linked to.
*
* Tags are not part of that helper's field set, so they're applied
* separately via the DAL contact object ($zbs->DAL->contacts->addUpdateContact(),
* includes/ZeroBSCRM.DAL3.Obj.Contacts.php), whose $args['data'] accepts
* 'tags' (array of tag IDs or tag name strings) + 'tag_mode' ('append' here
* so existing tags on an updated contact are preserved). The tag list itself
* comes from $zbs->DAL->getAllTags() (includes/ZeroBSCRM.DAL3.php), which
* returns every tag regardless of object type (Jetpack CRM tags aren't
* scoped per-object-type at the definition level).
*
* @link https://plugins.trac.wordpress.org/browser/zero-bs-crm/trunk/includes/ZeroBSCRM.IntegrationFuncs.php
* @link https://plugins.trac.wordpress.org/browser/zero-bs-crm/trunk/includes/ZeroBSCRM.DAL3.Obj.Contacts.php
*/
add_filter( 'adfoin_action_providers', 'adfoin_jetpackcrmac_actions', 10, 1 );
function adfoin_jetpackcrmac_actions( $actions ) {
$actions['jetpackcrmac'] = array(
'title' => __( 'Jetpack CRM', 'advanced-form-integration' ),
'tasks' => array(
'add_contact' => __( 'Add Contact', 'advanced-form-integration' ),
),
);
return $actions;
}
add_action( 'adfoin_action_fields', 'adfoin_jetpackcrmac_action_fields' );
function adfoin_jetpackcrmac_action_fields() {
?>
DAL ) || ! method_exists( $zbs->DAL, 'getAllTags' ) ) {
wp_send_json_error( __( 'Jetpack CRM is not active.', 'advanced-form-integration' ) );
}
$raw_tags = $zbs->DAL->getAllTags( array( 'perPage' => 999 ) );
$tags = array();
if ( is_array( $raw_tags ) ) {
foreach ( $raw_tags as $tag ) {
$tag_id = isset( $tag['ID'] ) ? $tag['ID'] : ( isset( $tag['id'] ) ? $tag['id'] : '' );
$tag_name = isset( $tag['name'] ) ? $tag['name'] : ( isset( $tag['zbstag_name'] ) ? $tag['zbstag_name'] : '' );
if ( '' === $tag_id ) {
continue;
}
$tags[ $tag_id ] = $tag_name;
}
}
wp_send_json_success( $tags );
}
add_action( 'wp_ajax_adfoin_get_jetpackcrmac_contact_fields', 'adfoin_get_jetpackcrmac_contact_fields', 10, 0 );
/*
* Get Jetpack CRM contact fields
*/
function adfoin_get_jetpackcrmac_contact_fields() {
adfoin_verify_nonce();
if ( ! function_exists( 'zeroBS_integrations_addOrUpdateCustomer' ) ) {
wp_send_json_error( __( 'Jetpack CRM is not active.', 'advanced-form-integration' ) );
}
$fields = array(
array( 'key' => 'zbsc_email', 'value' => __( 'Email', 'advanced-form-integration' ), 'description' => __( 'Required. Used to find or create the contact.', 'advanced-form-integration' ) ),
);
// Standard + custom contact fields, straight from Jetpack CRM's own field
// model ($zbsCustomerFields). Custom fields are merged into that global by
// zeroBSCRM_unpackCustomFields() keyed by their slug and flagged with
// 'custom-field' => true, and zeroBS_buildObjArr() saves any zbsc_
// key found in it, so the same zbsc_ prefix works for both.
foreach ( adfoin_jetpackcrmac_get_field_model() as $field_key => $field ) {
if ( 'email' === $field_key ) {
continue;
}
$type = isset( $field[0] ) ? $field[0] : 'text';
// Autonumber values are generated by Jetpack CRM itself.
if ( 'autonumber' === $type ) {
continue;
}
$is_custom = ! empty( $field['custom-field'] );
$label = isset( $field[1] ) && '' !== $field[1] ? $field[1] : $field_key;
$description = adfoin_jetpackcrmac_field_hint( $type, $field );
if ( $is_custom ) {
$description = trim( __( 'Custom field.', 'advanced-form-integration' ) . ' ' . $description );
}
$fields[] = array(
'key' => 'zbsc_' . $field_key,
'value' => $label,
'description' => $description,
);
}
if ( adfoin_jetpackcrmac_companies_enabled() ) {
$fields[] = array(
'key' => 'company_name',
'value' => jpcrm_label_company(),
'description' => __( 'Links the contact to an existing company with this name, or creates one.', 'advanced-form-integration' ),
);
}
$fields[] = array( 'key' => 'zbsc_tw', 'value' => __( 'Twitter / X Handle', 'advanced-form-integration' ), 'description' => '' );
$fields[] = array( 'key' => 'zbsc_fb', 'value' => __( 'Facebook Page', 'advanced-form-integration' ), 'description' => '' );
$fields[] = array( 'key' => 'zbsc_li', 'value' => __( 'LinkedIn Profile', 'advanced-form-integration' ), 'description' => '' );
wp_send_json_success( $fields );
}
/*
* Jetpack CRM contact field model (standard + custom fields), minus fields
* whose setting is switched off (e.g. second address).
*/
function adfoin_jetpackcrmac_get_field_model() {
global $zbs;
$var_name = 'zbsCustomerFields';
if ( isset( $zbs ) && isset( $zbs->DAL ) && method_exists( $zbs->DAL, 'objFieldVarName' ) && defined( 'ZBS_TYPE_CONTACT' ) ) {
$var_name = $zbs->DAL->objFieldVarName( ZBS_TYPE_CONTACT );
}
if ( empty( $var_name ) || ! isset( $GLOBALS[ $var_name ] ) || ! is_array( $GLOBALS[ $var_name ] ) ) {
return array();
}
$model = array();
foreach ( $GLOBALS[ $var_name ] as $field_key => $field ) {
if ( ! is_array( $field ) ) {
continue;
}
if ( ! empty( $field['opt'] ) && isset( $zbs->settings ) && ! $zbs->settings->get( $field['opt'] ) ) {
continue;
}
$model[ $field_key ] = $field;
}
return $model;
}
/*
* Whether Jetpack CRM's B2B mode (Companies module) is on
*/
function adfoin_jetpackcrmac_companies_enabled() {
global $zbs;
return function_exists( 'zeroBSCRM_getSetting' )
&& function_exists( 'zeroBS_integrations_addOrUpdateCompany' )
&& isset( $zbs->DAL->companies )
&& 1 == zeroBSCRM_getSetting( 'companylevelcustomers' );
}
/*
* Finds a company by name (case-insensitive exact match) or creates it, and
* links it to the contact without removing any companies already linked.
* Returns the company ID, or false on failure.
*/
function adfoin_jetpackcrmac_link_company( $contact_id, $company_name ) {
global $zbs, $wpdb;
// get_company_id_by_name() matches with LIKE, so escape wildcards for an exact match.
$company_id = $zbs->DAL->companies->get_company_id_by_name( $wpdb->esc_like( $company_name ) );
if ( ! $company_id ) {
$company_id = zeroBS_integrations_addOrUpdateCompany(
'adfoin',
$company_name,
array( 'zbsco_name' => $company_name ),
'',
'auto',
false,
false,
'update',
'zbsco_'
);
}
if ( ! $company_id ) {
return false;
}
// Not addUpdateObjLinks( 'mode' => 'append' ): it checks the new ID against
// the existing link-row IDs rather than the linked company IDs, so it skips
// the link whenever those numbers happen to collide.
$linked_ids = wp_list_pluck( (array) $zbs->DAL->contacts->getContactCompanies( $contact_id ), 'id' );
if ( ! in_array( (int) $company_id, array_map( 'intval', $linked_ids ), true ) ) {
$zbs->DAL->addUpdateObjLink(
array(
'data' => array(
'objtypefrom' => ZBS_TYPE_CONTACT,
'objtypeto' => ZBS_TYPE_COMPANY,
'objfromid' => $contact_id,
'objtoid' => $company_id,
),
)
);
}
return (int) $company_id;
}
/*
* Mapping hint shown under a field, based on its Jetpack CRM field type
*/
function adfoin_jetpackcrmac_field_hint( $type, $field ) {
$options = array();
if ( isset( $field[3] ) && is_array( $field[3] ) ) {
$options = array_filter( array_map( 'trim', $field[3] ) );
}
switch ( $type ) {
case 'select':
case 'radio':
return $options ? sprintf( __( 'Options: %s', 'advanced-form-integration' ), implode( ', ', $options ) ) : '';
case 'checkbox':
return $options
? sprintf( __( 'Comma-separated for multiple. Options: %s', 'advanced-form-integration' ), implode( ', ', $options ) )
: __( 'Comma-separated for multiple.', 'advanced-form-integration' );
case 'date':
return __( 'Date, e.g. 2026-01-31.', 'advanced-form-integration' );
case 'numberint':
return __( 'Whole number.', 'advanced-form-integration' );
case 'numberfloat':
case 'price':
return __( 'Number.', 'advanced-form-integration' );
case 'selectcountry':
return __( 'Country name.', 'advanced-form-integration' );
}
return '';
}
/*
* Turns a submitted multi-checkbox value into Jetpack CRM's comma-separated
* form. Form plugins join selections with ', ', but a selected value can
* itself contain commas ("Seven Churches of Revelation, Istanbul, and
* Cappadocia") while Jetpack CRM options never can. So the submission is split
* on separators and runs of consecutive pieces are matched against the field's
* options as whole values, longest run first. Comparison ignores case,
* accents and punctuation. Falls back to the split pieces when nothing matches.
*/
function adfoin_jetpackcrmac_normalize_checkbox_value( $value, $options ) {
$clean = function ( $text ) {
$text = html_entity_decode( wp_strip_all_tags( (string) $text ), ENT_QUOTES, 'UTF-8' );
return trim( preg_replace( '/\s+/u', ' ', $text ) );
};
$loose = function ( $text ) {
$text = strtolower( remove_accents( $text ) );
return trim( preg_replace( '/[^\p{L}\p{N}]+/u', ' ', $text ) );
};
$pieces = array_values( array_filter( array_map( 'trim', preg_split( '/[,|\n]+/', $clean( $value ) ) ), 'strlen' ) );
$lookup = array();
foreach ( (array) $options as $option ) {
$option = $clean( $option );
$key = $loose( $option );
if ( '' !== $key && ! isset( $lookup[ $key ] ) ) {
$lookup[ $key ] = $option;
}
}
$found = array();
$count = count( $pieces );
$i = 0;
while ( $i < $count ) {
$next = $i + 1;
for ( $j = $count; $j > $i; $j-- ) {
$key = $loose( implode( ' ', array_slice( $pieces, $i, $j - $i ) ) );
if ( isset( $lookup[ $key ] ) ) {
$found[] = $lookup[ $key ];
$next = $j;
break;
}
}
$i = $next;
}
if ( empty( $found ) ) {
return implode( ',', $pieces );
}
return implode( ',', array_unique( $found ) );
}
/*
* Normalizes mapped values to the format Jetpack CRM expects for each field type
*/
function adfoin_jetpackcrmac_format_value( $key, $value, $model ) {
$field_key = 0 === strpos( $key, 'zbsc_' ) ? substr( $key, 5 ) : $key;
if ( ! isset( $model[ $field_key ][0] ) ) {
return $value;
}
switch ( $model[ $field_key ][0] ) {
case 'date':
// zeroBS_buildObjArr() only parses '!Y-m-d' for date fields.
if ( ! preg_match( '/^\d{4}-\d{2}-\d{2}$/', $value ) ) {
$timestamp = is_numeric( $value ) ? (int) $value : strtotime( $value );
if ( $timestamp ) {
$value = gmdate( 'Y-m-d', $timestamp );
}
}
break;
case 'checkbox':
$options = isset( $model[ $field_key ][3] ) && is_array( $model[ $field_key ][3] ) ? $model[ $field_key ][3] : array();
$value = adfoin_jetpackcrmac_normalize_checkbox_value( $value, $options );
break;
}
return $value;
}
add_action( 'adfoin_jetpackcrmac_job_queue', 'adfoin_jetpackcrmac_job_queue', 10, 1 );
function adfoin_jetpackcrmac_job_queue( $data ) {
adfoin_jetpackcrmac_send_data( $data['record'], $data['posted_data'] );
}
/*
* Handles creating/updating a Jetpack CRM contact
*/
function adfoin_jetpackcrmac_send_data( $record, $posted_data ) {
if ( ! function_exists( 'zeroBS_integrations_addOrUpdateCustomer' ) ) {
return;
}
$record_data = json_decode( $record['data'], true );
if ( isset( $record_data['action_data']['cl'] ) && adfoin_check_conditional_logic( $record_data['action_data']['cl'], $posted_data ) ) {
return;
}
$field_data = isset( $record_data['field_data'] ) ? $record_data['field_data'] : array();
$task = isset( $record['task'] ) ? $record['task'] : '';
if ( 'add_contact' !== $task ) {
return;
}
$tag_id = isset( $field_data['tagId'] ) ? $field_data['tagId'] : '';
unset( $field_data['tagId'] );
$prepared_data = array();
$field_model = adfoin_jetpackcrmac_get_field_model();
foreach ( $field_data as $key => $value ) {
if ( '' === $key ) {
continue;
}
$parsed_value = adfoin_get_parsed_values( $value, $posted_data );
if ( '' === $parsed_value || null === $parsed_value ) {
continue;
}
$prepared_data[ $key ] = adfoin_jetpackcrmac_format_value( $key, $parsed_value, $field_model );
}
$request_payload = $prepared_data;
// Company isn't a contact field; it's linked separately once the contact exists.
$company_name = isset( $prepared_data['company_name'] ) ? trim( $prepared_data['company_name'] ) : '';
unset( $prepared_data['company_name'] );
$response_body = array( 'success' => false );
$status_code = 400;
if ( empty( $prepared_data['zbsc_email'] ) || ! is_email( $prepared_data['zbsc_email'] ) ) {
$response_body['message'] = __( 'A valid email address is required to create a Jetpack CRM contact.', 'advanced-form-integration' );
} else {
// $do_not_update_blanks = true so updating an existing contact only
// touches the fields this submission mapped, instead of blanking the rest.
$customer_id = zeroBS_integrations_addOrUpdateCustomer(
'adfoin',
$prepared_data['zbsc_email'],
$prepared_data,
'',
'auto',
false,
false,
'update',
'zbsc_',
array(),
true
);
if ( $customer_id ) {
global $zbs;
if ( $tag_id && isset( $zbs ) && isset( $zbs->DAL ) && isset( $zbs->DAL->contacts ) ) {
// Use the dedicated tag-only method, not addUpdateContact() — passing
// 'tags'/'tag_mode' through addUpdateContact()'s generic $args['data']
// merges in that method's OTHER default (blank) fields and overwrites
// the email/name/etc. that zeroBS_integrations_addOrUpdateCustomer()
// just set above. 'tagIDs' (not 'tags') because the dropdown supplies a
// tag ID — 'tags' expects tag name strings and looks them up by name.
$zbs->DAL->contacts->addUpdateContactTags(
array(
'id' => $customer_id,
'tagIDs' => array( $tag_id ),
'mode' => 'append',
)
);
}
$status_code = 200;
$response_body['success'] = true;
$response_body['message'] = __( 'Contact created/updated successfully.', 'advanced-form-integration' );
$response_body['id'] = $customer_id;
if ( '' !== $company_name ) {
if ( ! adfoin_jetpackcrmac_companies_enabled() ) {
$response_body['company_error'] = __( 'Company not linked: B2B mode is disabled in Jetpack CRM.', 'advanced-form-integration' );
} else {
$company_id = adfoin_jetpackcrmac_link_company( $customer_id, $company_name );
if ( $company_id ) {
$response_body['company_id'] = $company_id;
} else {
$response_body['company_error'] = __( 'Company could not be found or created.', 'advanced-form-integration' );
}
}
}
} else {
$response_body['message'] = __( 'Failed to create/update Jetpack CRM contact. Please verify the supplied data.', 'advanced-form-integration' );
}
}
$log_args = array(
'method' => 'LOCAL',
'body' => $request_payload,
);
$log_response = array(
'response' => array(
'code' => $status_code,
'message' => $response_body['message'],
),
'body' => $response_body,
);
adfoin_add_to_log( $log_response, 'jetpackcrmac', $log_args, $record );
}