getFileName() ), '/\\' ) . DIRECTORY_SEPARATOR; } catch ( Exception $e ) { $cf7_dir = ''; } } // 3. Standard WordPress plugins directory (plugin folder is usually "contact-form-7"). if ( ( $cf7_dir === '' || ! file_exists( $cf7_dir . $contact_form_file ) ) && defined( 'WP_PLUGIN_DIR' ) ) { $default = rtrim( WP_PLUGIN_DIR, '/\\' ) . DIRECTORY_SEPARATOR . 'contact-form-7' . DIRECTORY_SEPARATOR; if ( file_exists( $default . $contact_form_file ) ) { $cf7_dir = $default; } } // 4. Scan active plugins for contact-form-7 (handles custom plugin dirs / symlinks). if ( ( $cf7_dir === '' || ! file_exists( $cf7_dir . $contact_form_file ) ) && function_exists( 'wp_get_active_and_valid_plugins' ) ) { $active = wp_get_active_and_valid_plugins(); foreach ( $active as $plugin_file ) { $dir = dirname( $plugin_file ); if ( strpos( wp_basename( $dir ), 'contact-form-7' ) !== false && file_exists( $dir . DIRECTORY_SEPARATOR . $contact_form_file ) ) { $cf7_dir = rtrim( $dir, '/\\' ) . DIRECTORY_SEPARATOR; break; } } } if ( $cf7_dir !== '' && file_exists( $cf7_dir . $contact_form_file ) ) { require_once $cf7_dir . $contact_form_file; } // Some CF7 versions use WPCF7_ContactForm (no underscore); alias so our code works. if ( class_exists( 'WPCF7_ContactForm' ) && ! class_exists( 'WPCF7_Contact_Form' ) ) { class_alias( 'WPCF7_ContactForm', 'WPCF7_Contact_Form' ); } } /** * Enqueue JS/CSS assets on the CF7 form editor page. * * @param string $hook Current admin page hook. */ public function enqueue_editor_assets( string $hook ): void { // CF7 editor screens are under admin.php?page=wpcf7 (hook can vary by CF7/WP version). // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only check. $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; if ( $page !== 'wpcf7' ) { return; } // Extra safety: ignore unrelated admin pages even if query params are unexpected. if ( strpos( $hook, 'wpcf7' ) === false && $hook !== 'toplevel_page_wpcf7' ) { return; } $api_key = lacrm_forms_get_option( 'api_key', '' ); $pipelines = array(); if ( $api_key !== '' ) { $api = new LACRM_Forms_API( $api_key ); $result = $api->get_pipelines(); if ( $result['success'] ) { $pipelines = $this->normalize_pipelines( $result['pipelines'] ); } } wp_enqueue_style( 'lacrm-connector-admin-settings', plugin_dir_url( LACRM_FORMS_PLUGIN_FILE ) . 'assets/css/admin-settings.css', array(), LACRM_FORMS_VERSION ); wp_enqueue_script( 'lacrm-editor', plugin_dir_url( LACRM_FORMS_PLUGIN_FILE ) . 'assets/js/lacrm-editor.js', array( 'jquery' ), LACRM_FORMS_VERSION, true ); wp_localize_script( 'lacrm-editor', 'lacrmEditorData', array( 'pipelines' => $pipelines, 'i18n' => array( 'select_pipeline' => __( '— Select Pipeline —', 'aal-connector-for-lacrm' ), 'select_status' => __( '— Select Status —', 'aal-connector-for-lacrm' ), ), ) ); } /** * Normalize raw custom field data from LACRM API into a consistent structure. * * @param array $raw Raw custom fields array from API. * @return array Normalized: [{id, name, type}] */ /** * Types that are display-only layout elements with no storable value. */ private const CF_DISPLAY_ONLY_TYPES = array( 'Section', 'SectionHeader', 'TextBlock', 'Signature', 'FileLink' ); private function normalize_custom_fields( array $raw ): array { $fields = array(); foreach ( $raw as $cf ) { if ( ! is_array( $cf ) ) { continue; } // Skip LACRM built-in fields (Birthday, Job Title, Website, etc.). $default_flag = $cf['IsDefaultField'] ?? $cf['isDefaultField'] ?? $cf['DefaultField'] ?? $cf['defaultField'] ?? false; if ( filter_var( $default_flag, FILTER_VALIDATE_BOOLEAN ) ) { continue; } $id = (string) ( $cf['CustomFieldId'] ?? $cf['customFieldId'] ?? '' ); $name = (string) ( $cf['Name'] ?? $cf['name'] ?? '' ); $type = (string) ( $cf['Type'] ?? $cf['type'] ?? 'Text' ); if ( $id === '' || $name === '' ) { continue; } // Skip display-only layout types that hold no user-entered value. if ( in_array( $type, self::CF_DISPLAY_ONLY_TYPES, true ) ) { continue; } // Capture selectable options for Dropdown/RadioList/Checkbox types (used as UI hints). $options = array(); if ( in_array( $type, array( 'Dropdown', 'RadioList', 'Checkbox' ), true ) ) { $raw_opts = $cf['Options'] ?? $cf['options'] ?? array(); if ( is_array( $raw_opts ) ) { foreach ( $raw_opts as $opt ) { $opt_label = is_array( $opt ) ? (string) ( $opt['Option'] ?? $opt['label'] ?? '' ) : (string) $opt; if ( $opt_label !== '' ) { $options[] = $opt_label; } } } } $fields[] = array( 'id' => $id, 'name' => $name, 'type' => $type, 'options' => $options ); } return $fields; } /** * Normalize raw pipeline data from LACRM API into a consistent structure. * * @param array $raw Raw pipeline array from API. * @return array Normalized: [{id, name, statuses: [{id, name}]}] */ private function normalize_pipelines( array $raw ): array { $pipelines = array(); foreach ( $raw as $p ) { if ( ! is_array( $p ) ) { continue; } $id = (string) ( $p['PipelineId'] ?? $p['pipelineId'] ?? $p['PipelineID'] ?? $p['pipelineID'] ?? $p['Id'] ?? $p['id'] ?? '' ); $name = (string) ( $p['Name'] ?? $p['name'] ?? $p['PipelineName'] ?? $p['pipelineName'] ?? '' ); if ( $id === '' ) { continue; } $statuses_raw = $p['Statuses'] ?? $p['StatusList'] ?? $p['statuses'] ?? $p['PipelineStatuses'] ?? $p['pipelineStatuses'] ?? array(); $statuses = array(); if ( is_array( $statuses_raw ) ) { foreach ( $statuses_raw as $s ) { if ( ! is_array( $s ) ) { continue; } $sid = (string) ( $s['StatusId'] ?? $s['statusId'] ?? $s['PipelineStatusId'] ?? $s['pipelineStatusId'] ?? $s['Id'] ?? $s['id'] ?? '' ); $sname = (string) ( $s['Name'] ?? $s['name'] ?? $s['StatusName'] ?? $s['statusName'] ?? '' ); if ( $sid !== '' ) { $statuses[] = array( 'id' => $sid, 'name' => $sname ); } } } $pipelines[] = array( 'id' => $id, 'name' => $name, 'statuses' => $statuses ); } return $pipelines; } /** * Add LACRM panel to CF7 form editor (tabs). * Only show the tab when LACRM is connected (API key is set in Settings → AAL Connector For LACRM). * * @param array $panels Existing panels. * @return array */ public function add_editor_panel( array $panels ): array { $panels['lacrm_forms'] = array( 'title' => __( 'Less Annoying CRM', 'aal-connector-for-lacrm' ), 'callback' => array( $this, 'render_editor_panel' ), ); return $panels; } /** * Format a form tag name (slug) as human-readable label for dropdown display. * * @param string $tag_name CF7 tag name (e.g. "email-address", "your-name"). * @return string Readable label (e.g. "Email Address", "Your Name"). */ private function format_tag_label( string $tag_name ): string { if ( $tag_name === '' ) { return ''; } return ucwords( str_replace( array( '-', '_' ), ' ', $tag_name ) ); } /** * Get CF7 form tags (field names) for a form. * * @param int $form_id WPCF7_Contact_Form post ID. * @return array Tag names (slug => slug for value, we use format_tag_label for display). */ private function get_form_tags( int $form_id ): array { $this->ensure_cf7_contact_form_loaded(); if ( ! class_exists( 'WPCF7_Contact_Form' ) ) { return array(); } $form = WPCF7_Contact_Form::get_instance( $form_id ); if ( ! $form ) { return array(); } $tags = $form->scan_form_tags(); $names = array(); foreach ( $tags as $tag ) { if ( ! empty( $tag->name ) ) { $names[ $tag->name ] = $tag->name; } } return $names; } /** * Get form ID from contact form object or current screen (CF7 passes object; fallback for edge cases). * * @param mixed $contact_form Form object from CF7 or null. * @return int Form post ID, or 0. */ private function get_form_id_from_context( $contact_form ): int { if ( is_object( $contact_form ) ) { if ( method_exists( $contact_form, 'id' ) ) { $id = (int) $contact_form->id(); if ( $id > 0 ) { return $id; } } if ( isset( $contact_form->id ) && (int) $contact_form->id > 0 ) { return (int) $contact_form->id; } if ( isset( $contact_form->ID ) && (int) $contact_form->ID > 0 ) { return (int) $contact_form->ID; } } // Fallback: current post when editing a CF7 form (e.g. admin screen). // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only; used to identify which form is being edited. if ( is_admin() && isset( $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Same read-only context as above. return absint( $_GET['post'] ); } return 0; } /** * Render a '; echo ''; foreach ( $choices as $tag_name ) { echo ''; } echo ''; } /** * Render LACRM editor panel: enable sync + field mapping + company + pipeline settings. * * @param mixed $contact_form Form object from CF7 (WPCF7_Contact_Form) or null. */ public function render_editor_panel( $contact_form ): void { $this->ensure_cf7_contact_form_loaded(); if ( ! class_exists( 'WPCF7_Contact_Form' ) ) { echo '

' . esc_html__( 'Less Annoying CRM integration could not load. Please ensure Contact Form 7 is up to date.', 'aal-connector-for-lacrm' ) . '

'; return; } $form_id = $this->get_form_id_from_context( $contact_form ); if ( $form_id <= 0 ) { echo '

' . esc_html__( 'Save the form first to configure LACRM mapping.', 'aal-connector-for-lacrm' ) . '

'; return; } $settings = get_post_meta( $form_id, self::POST_META_KEY, true ); if ( ! is_array( $settings ) ) { $settings = array(); } $enabled = ! empty( $settings['enabled'] ); $owner = $settings['owner'] ?? ''; $map = $settings['field_map'] ?? array(); $company_enabled = ! empty( $settings['company_enabled'] ); $pipeline_enabled = ! empty( $settings['pipeline_enabled'] ); $pipeline_id = $settings['pipeline_id'] ?? ''; $pipeline_status_id = $settings['pipeline_status_id'] ?? ''; $pipeline_trigger = $settings['pipeline_trigger'] ?? 'new_only'; $pipeline_note_field = $settings['pipeline_note_field'] ?? ''; $custom_field_map = is_array( $settings['custom_field_map'] ?? null ) ? $settings['custom_field_map'] : array(); $task_enabled = ! empty( $settings['task_enabled'] ); $task_description = $settings['task_description'] ?? ''; $task_description_field = $settings['task_description_field'] ?? ''; $task_due_days = isset( $settings['task_due_days'] ) ? (int) $settings['task_due_days'] : 1; $task_assigned_to = $settings['task_assigned_to'] ?? ''; $task_trigger = $settings['task_trigger'] ?? 'new_only'; $conditional_enabled = ! empty( $settings['conditional_enabled'] ); $conditional_field = $settings['conditional_field'] ?? ''; $conditional_operator = $settings['conditional_operator'] ?? 'not_empty'; $conditional_value = $settings['conditional_value'] ?? ''; $tag_choices = $this->get_form_tags( $form_id ); $api_key = lacrm_forms_get_option( 'api_key', '' ); $owners = array(); $pipelines = array(); $custom_fields = array(); if ( $api_key !== '' ) { $api = new LACRM_Forms_API( $api_key ); $users = $api->get_users(); if ( $users['success'] && ! empty( $users['users'] ) ) { foreach ( $users['users'] as $user ) { $uid = $user['UserId'] ?? ''; $name = trim( ( $user['FirstName'] ?? '' ) . ' ' . ( $user['LastName'] ?? '' ) ); if ( $name === '' ) { $name = $uid; } $owners[ $uid ] = $name; } } $pl_result = $api->get_pipelines(); if ( $pl_result['success'] ) { $pipelines = $this->normalize_pipelines( $pl_result['pipelines'] ); } $cf_result = $api->get_custom_fields(); if ( $cf_result['success'] ) { $custom_fields = $this->normalize_custom_fields( $cf_result['fields'] ); } } wp_nonce_field( 'lacrm_forms_cf7_save', 'lacrm_forms_cf7_nonce' ); ?>

plugin settings. You can still configure mappings now.', 'aal-connector-for-lacrm' ), esc_url( admin_url( 'options-general.php?page=aal-connector-for-lacrm' ) ) ) ); ?>


No contact assignee set. LACRM requires an owner for every contact. Set a Default Assignee in plugin settings or choose an owner above.', 'aal-connector-for-lacrm' ), esc_url( admin_url( 'options-general.php?page=aal-connector-for-lacrm' ) ) ) ); ?>

$label ) : $selected_tag = $map[ $key ] ?? ''; ?>

>

>

>

>

>
ensure_cf7_contact_form_loaded(); if ( ! class_exists( 'WPCF7_Contact_Form' ) ) { return; } if ( ! is_object( $contact_form ) || ! method_exists( $contact_form, 'id' ) ) { return; } if ( ! isset( $_POST['lacrm_forms_cf7_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['lacrm_forms_cf7_nonce'] ) ), 'lacrm_forms_cf7_save' ) ) { return; } $post_id = (int) $contact_form->id(); if ( $post_id <= 0 || ! current_user_can( 'wpcf7_edit_contact_form', $post_id ) ) { return; } // ── Contact sync settings ── $enabled = ! empty( $_POST['lacrm_forms_cf7_enabled'] ); $owner = isset( $_POST['lacrm_forms_cf7_owner'] ) ? sanitize_text_field( wp_unslash( $_POST['lacrm_forms_cf7_owner'] ) ) : ''; $map_raw = array(); if ( isset( $_POST['lacrm_forms_cf7_field_map'] ) && is_array( $_POST['lacrm_forms_cf7_field_map'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitized via array_map below. $map_raw = array_map( 'sanitize_text_field', wp_unslash( $_POST['lacrm_forms_cf7_field_map'] ) ); } $map = array(); foreach ( array_keys( LACRM_FIELD_KEYS ) as $key ) { $map[ $key ] = $map_raw[ $key ] ?? ''; } // ── Company settings ── $company_enabled = ! empty( $_POST['lacrm_forms_cf7_company_enabled'] ); // ── Pipeline settings ── $pipeline_enabled = ! empty( $_POST['lacrm_forms_cf7_pipeline_enabled'] ); $pipeline_id = isset( $_POST['lacrm_forms_cf7_pipeline_id'] ) ? sanitize_text_field( wp_unslash( $_POST['lacrm_forms_cf7_pipeline_id'] ) ) : ''; $pipeline_status_id = isset( $_POST['lacrm_forms_cf7_pipeline_status_id'] ) ? sanitize_text_field( wp_unslash( $_POST['lacrm_forms_cf7_pipeline_status_id'] ) ) : ''; $pipeline_trigger = isset( $_POST['lacrm_forms_cf7_pipeline_trigger'] ) && $_POST['lacrm_forms_cf7_pipeline_trigger'] === 'always' ? 'always' : 'new_only'; $pipeline_note_field = isset( $_POST['lacrm_forms_cf7_pipeline_note_field'] ) ? sanitize_text_field( wp_unslash( $_POST['lacrm_forms_cf7_pipeline_note_field'] ) ) : ''; // ── Custom field mapping ── // Keys are the LACRM field Name (the API parameter name); values are the CF7 tag name. $custom_field_map = array(); if ( isset( $_POST['lacrm_forms_cf7_custom_fields'] ) && is_array( $_POST['lacrm_forms_cf7_custom_fields'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitized below. foreach ( wp_unslash( $_POST['lacrm_forms_cf7_custom_fields'] ) as $cf_name => $tag_name ) { $cf_name = sanitize_text_field( (string) $cf_name ); if ( $cf_name !== '' ) { $custom_field_map[ $cf_name ] = sanitize_text_field( (string) $tag_name ); } } } // ── Task settings ── $task_enabled = ! empty( $_POST['lacrm_forms_cf7_task_enabled'] ); $task_description = isset( $_POST['lacrm_forms_cf7_task_description'] ) ? sanitize_text_field( wp_unslash( $_POST['lacrm_forms_cf7_task_description'] ) ) : ''; $task_description_field = isset( $_POST['lacrm_forms_cf7_task_description_field'] ) ? sanitize_text_field( wp_unslash( $_POST['lacrm_forms_cf7_task_description_field'] ) ) : ''; $task_due_days = isset( $_POST['lacrm_forms_cf7_task_due_days'] ) ? max( 0, min( 365, (int) $_POST['lacrm_forms_cf7_task_due_days'] ) ) : 1; $task_assigned_to = isset( $_POST['lacrm_forms_cf7_task_assigned_to'] ) ? sanitize_text_field( wp_unslash( $_POST['lacrm_forms_cf7_task_assigned_to'] ) ) : ''; $task_trigger = isset( $_POST['lacrm_forms_cf7_task_trigger'] ) && $_POST['lacrm_forms_cf7_task_trigger'] === 'always' ? 'always' : 'new_only'; // ── Conditional sync settings ── $conditional_enabled = ! empty( $_POST['lacrm_forms_cf7_conditional_enabled'] ); $conditional_field = isset( $_POST['lacrm_forms_cf7_conditional_field'] ) ? sanitize_text_field( wp_unslash( $_POST['lacrm_forms_cf7_conditional_field'] ) ) : ''; $allowed_operators = array( 'not_empty', 'is_empty', 'is', 'is_not', 'contains' ); $conditional_operator = ( isset( $_POST['lacrm_forms_cf7_conditional_operator'] ) && in_array( $_POST['lacrm_forms_cf7_conditional_operator'], $allowed_operators, true ) ) ? sanitize_key( $_POST['lacrm_forms_cf7_conditional_operator'] ) : 'not_empty'; $conditional_value = isset( $_POST['lacrm_forms_cf7_conditional_value'] ) ? sanitize_text_field( wp_unslash( $_POST['lacrm_forms_cf7_conditional_value'] ) ) : ''; update_post_meta( $post_id, self::POST_META_KEY, array( 'enabled' => $enabled, 'owner' => $owner, 'field_map' => $map, 'company_enabled' => $company_enabled, 'pipeline_enabled' => $pipeline_enabled, 'pipeline_id' => $pipeline_id, 'pipeline_status_id' => $pipeline_status_id, 'pipeline_trigger' => $pipeline_trigger, 'pipeline_note_field' => $pipeline_note_field, 'custom_field_map' => $custom_field_map, 'task_enabled' => $task_enabled, 'task_description' => $task_description, 'task_description_field' => $task_description_field, 'task_due_days' => $task_due_days, 'task_assigned_to' => $task_assigned_to, 'task_trigger' => $task_trigger, 'conditional_enabled' => $conditional_enabled, 'conditional_field' => $conditional_field, 'conditional_operator' => $conditional_operator, 'conditional_value' => $conditional_value, ) ); } /** * Evaluate a conditional sync rule against submitted form data. * * @param array $settings Saved form settings. * @param array $posted Posted form data from CF7 submission. * @return bool True if sync should proceed, false to skip. */ private function evaluate_condition( array $settings, array $posted ): bool { $field = (string) ( $settings['conditional_field'] ?? '' ); $operator = (string) ( $settings['conditional_operator'] ?? 'not_empty' ); $value = (string) ( $settings['conditional_value'] ?? '' ); if ( $field === '' ) { return true; // No field configured — don't block sync. } $field_value = isset( $posted[ $field ] ) ? trim( (string) $posted[ $field ] ) : ''; switch ( $operator ) { case 'is': return strtolower( $field_value ) === strtolower( $value ); case 'is_not': return strtolower( $field_value ) !== strtolower( $value ); case 'contains': return $value !== '' && false !== stripos( $field_value, $value ); case 'is_empty': return $field_value === ''; case 'not_empty': default: return $field_value !== ''; } } /** * Hook: wpcf7_mail_sent – sync to LACRM after mail is sent. * * @param WPCF7_Contact_Form $contact_form Form. */ public function on_mail_sent( $contact_form ): void { $this->ensure_cf7_contact_form_loaded(); if ( ! class_exists( 'WPCF7_Contact_Form' ) || ! class_exists( 'WPCF7_Submission' ) ) { return; } if ( ! is_object( $contact_form ) || ! method_exists( $contact_form, 'id' ) ) { return; } $form_id = (int) $contact_form->id(); if ( $form_id <= 0 ) { return; } $settings = get_post_meta( $form_id, self::POST_META_KEY, true ); if ( ! is_array( $settings ) || empty( $settings['enabled'] ) ) { return; } $submission = WPCF7_Submission::get_instance(); if ( ! $submission ) { return; } $posted = $submission->get_posted_data(); if ( ! is_array( $posted ) || empty( $posted ) ) { return; } // ── Conditional sync check ── if ( ! empty( $settings['conditional_enabled'] ) ) { if ( ! $this->evaluate_condition( $settings, $posted ) ) { LACRM_Forms_Logger::instance()->log( __( 'CF7: sync skipped — conditional field check not met.', 'aal-connector-for-lacrm' ), 'info', array( 'form_id' => $form_id ) ); return; } } $map = $settings['field_map'] ?? array(); $get = function ( string $key ) use ( $posted, $map ) { $tag = $map[ $key ] ?? ''; return $tag === '' ? '' : ( isset( $posted[ $tag ] ) ? trim( (string) $posted[ $tag ] ) : '' ); }; $first_name = $get( 'first_name' ); $last_name = $get( 'last_name' ); $email = $get( 'email' ); $phone = $get( 'phone' ); $company = $get( 'company' ); $notes = $get( 'notes' ); // Build custom field values: keys are LACRM field Names (direct API params). $custom_fields = array(); $custom_field_map = is_array( $settings['custom_field_map'] ?? null ) ? $settings['custom_field_map'] : array(); foreach ( $custom_field_map as $cf_name => $tag_name ) { $cf_name = (string) $cf_name; $tag_name = (string) $tag_name; if ( $cf_name === '' || $tag_name === '' ) { continue; } $cf_value = isset( $posted[ $tag_name ] ) ? trim( (string) $posted[ $tag_name ] ) : ''; if ( $cf_value !== '' ) { $custom_fields[ $cf_name ] = $cf_value; // cf_name IS the LACRM API parameter name. } } if ( ! is_email( $email ) ) { LACRM_Forms_Logger::instance()->log( __( 'CF7: No valid email in mapped field; LACRM sync skipped.', 'aal-connector-for-lacrm' ), 'error', array( 'form_id' => $form_id ) ); return; } $name = trim( $first_name . ' ' . $last_name ); if ( $name === '' ) { $name = $email; } $api_key = lacrm_forms_get_option( 'api_key', '' ); if ( $api_key === '' ) { LACRM_Forms_Logger::instance()->log( __( 'LACRM API key not set; skipping CF7 sync.', 'aal-connector-for-lacrm' ), 'error', array( 'form_id' => $form_id ) ); return; } $assigned_to = ! empty( $settings['owner'] ) ? (string) $settings['owner'] : (string) lacrm_forms_get_option( 'default_owner', '' ); if ( $assigned_to === '' ) { LACRM_Forms_Logger::instance()->log( __( 'No contact owner; LACRM CreateContact requires AssignedTo.', 'aal-connector-for-lacrm' ), 'error', array( 'form_id' => $form_id ) ); return; } $api = new LACRM_Forms_API( $api_key ); $find = $api->find_contact_by_email( $email ); $contact_id = null; $is_new = false; if ( $find['success'] && ! empty( $find['contact_id'] ) ) { // ── Update existing contact ── $contact_id = $find['contact_id']; $edit = $api->edit_contact( $contact_id, array( 'name' => $name, 'email' => $email, 'phone' => $phone, 'company_name' => $company, 'background_info' => $notes, 'assigned_to' => $assigned_to, 'custom_fields' => $custom_fields, ) ); if ( ! $edit['success'] ) { LACRM_Forms_Logger::instance()->log( $edit['error_description'] ?? 'EditContact failed', 'error', array( 'contact_id' => $contact_id ) ); } else { LACRM_Forms_Logger::instance()->log( __( 'LACRM contact updated (CF7).', 'aal-connector-for-lacrm' ), 'info', array( 'contact_id' => $contact_id, 'form_id' => $form_id ) ); } // Always add a note for resubmissions so the contact's profile shows submission history. $form_title = is_object( $contact_form ) && method_exists( $contact_form, 'title' ) ? $contact_form->title() : __( 'Contact Form', 'aal-connector-for-lacrm' ); $resubmit_note = sprintf( /* translators: 1: form title, 2: date/time */ __( 'Form resubmitted: %1$s on %2$s.', 'aal-connector-for-lacrm' ), $form_title, current_time( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) ) ); if ( $notes !== '' ) { $resubmit_note .= "\n" . __( 'Message:', 'aal-connector-for-lacrm' ) . ' ' . $notes; } $api->create_note( $contact_id, $resubmit_note ); } else { // ── Create new contact ── $create = $api->create_contact( array( 'assigned_to' => $assigned_to, 'name' => $name, 'email' => $email, 'phone' => $phone, 'company_name' => $company, 'background_info' => $notes, 'custom_fields' => $custom_fields, ) ); if ( ! $create['success'] ) { LACRM_Forms_Logger::instance()->log( $create['error_description'] ?? 'CreateContact failed', 'error', array( 'form_id' => $form_id ) ); return; } $contact_id = $create['contact_id'] ?? null; $is_new = true; LACRM_Forms_Logger::instance()->log( __( 'LACRM contact created (CF7).', 'aal-connector-for-lacrm' ), 'info', array( 'contact_id' => $contact_id, 'form_id' => $form_id ) ); // For new contacts, add a note only if they provided a message. if ( $contact_id && $notes !== '' ) { $api->create_note( $contact_id, $notes ); } } if ( ! $contact_id ) { return; } // ── Company sync ── if ( ! empty( $settings['company_enabled'] ) && $company !== '' ) { $this->sync_company( $api, $contact_id, $company, $assigned_to, $form_id ); } // ── Pipeline sync ── if ( ! empty( $settings['pipeline_enabled'] ) ) { $pipeline_id = $settings['pipeline_id'] ?? ''; $pipeline_status_id = $settings['pipeline_status_id'] ?? ''; $pipeline_trigger = $settings['pipeline_trigger'] ?? 'new_only'; $note_field_tag = $settings['pipeline_note_field'] ?? ''; $pipeline_note = $note_field_tag !== '' ? ( isset( $posted[ $note_field_tag ] ) ? trim( (string) $posted[ $note_field_tag ] ) : '' ) : ''; $should_add = $pipeline_trigger === 'always' || $is_new; if ( $should_add && $pipeline_id !== '' && $pipeline_status_id !== '' ) { $this->sync_pipeline( $api, $contact_id, $pipeline_id, $pipeline_status_id, $pipeline_note, $assigned_to, $form_id ); } } // ── Task sync ── if ( ! empty( $settings['task_enabled'] ) ) { $task_trig = $settings['task_trigger'] ?? 'new_only'; if ( $task_trig === 'always' || $is_new ) { $this->sync_task( $api, $contact_id, $settings, $posted, $assigned_to, $form_id ); } } } /** * Create a task in LACRM linked to the contact. * * @param LACRM_Forms_API $api API instance. * @param string $contact_id LACRM ContactId. * @param array $settings Form LACRM settings. * @param array $posted CF7 posted data. * @param string $assigned_to Fallback user ID. * @param int $form_id CF7 form ID (for logging context). */ private function sync_task( LACRM_Forms_API $api, string $contact_id, array $settings, array $posted, string $assigned_to, int $form_id ): void { $description_field = (string) ( $settings['task_description_field'] ?? '' ); $description = ''; if ( $description_field !== '' && isset( $posted[ $description_field ] ) ) { $description = trim( (string) $posted[ $description_field ] ); } if ( $description === '' ) { $description = (string) ( $settings['task_description'] ?? '' ); } if ( $description === '' ) { LACRM_Forms_Logger::instance()->log( __( 'LACRM: task skipped — no description configured.', 'aal-connector-for-lacrm' ), 'info', array( 'contact_id' => $contact_id, 'form_id' => $form_id ) ); return; } $due_days = isset( $settings['task_due_days'] ) ? max( 0, (int) $settings['task_due_days'] ) : 1; $due_date = gmdate( 'Y-m-d', strtotime( '+' . $due_days . ' days', (int) current_time( 'timestamp', true ) ) ); $task_owner = ! empty( $settings['task_assigned_to'] ) ? (string) $settings['task_assigned_to'] : $assigned_to; $result = $api->create_task( $contact_id, array( 'description' => $description, 'due_date' => $due_date, 'assigned_to' => $task_owner, ) ); if ( ! $result['success'] ) { LACRM_Forms_Logger::instance()->log( $result['error_description'] ?? 'CreateTask failed', 'error', array( 'contact_id' => $contact_id, 'form_id' => $form_id ) ); } else { LACRM_Forms_Logger::instance()->log( __( 'LACRM: task created (CF7).', 'aal-connector-for-lacrm' ), 'info', array( 'contact_id' => $contact_id, 'task_id' => $result['task_id'] ?? '', 'form_id' => $form_id ) ); } } /** * Find or create a Company in LACRM and associate it with the contact. * * @param LACRM_Forms_API $api API instance. * @param string $contact_id LACRM ContactId of the person. * @param string $company Company name from the form. * @param string $assigned_to User ID to assign newly created company to. * @param int $form_id CF7 form ID (for logging context). */ private function sync_company( LACRM_Forms_API $api, string $contact_id, string $company, string $assigned_to, int $form_id ): void { // Search for existing company by name. $find = $api->find_company_by_name( $company ); if ( $find['success'] && ! empty( $find['company_id'] ) ) { $company_id = $find['company_id']; LACRM_Forms_Logger::instance()->log( __( 'LACRM: existing company found, linking contact.', 'aal-connector-for-lacrm' ), 'info', array( 'contact_id' => $contact_id, 'company_id' => $company_id, 'form_id' => $form_id ) ); } else { // Create a new company record. $create = $api->create_company( array( 'assigned_to' => $assigned_to, 'name' => $company, ) ); if ( ! $create['success'] ) { LACRM_Forms_Logger::instance()->log( $create['error_description'] ?? 'CreateCompany failed', 'error', array( 'form_id' => $form_id ) ); return; } $company_id = $create['company_id'] ?? ''; LACRM_Forms_Logger::instance()->log( __( 'LACRM: company created.', 'aal-connector-for-lacrm' ), 'info', array( 'contact_id' => $contact_id, 'company_id' => $company_id, 'form_id' => $form_id ) ); } if ( $company_id === '' ) { return; } // Associate person contact with company contact. $assoc = $api->associate_contacts( $contact_id, $company_id ); if ( ! $assoc['success'] ) { LACRM_Forms_Logger::instance()->log( $assoc['error_description'] ?? 'AssociateContacts failed', 'error', array( 'contact_id' => $contact_id, 'company_id' => $company_id ) ); } else { LACRM_Forms_Logger::instance()->log( __( 'LACRM: contact linked to company.', 'aal-connector-for-lacrm' ), 'info', array( 'contact_id' => $contact_id, 'company_id' => $company_id ) ); } } /** * Attach a contact to an existing LACRM pipeline — skips if already attached. * * Flow: * 1. GetPipelineReport → check whether contact already has an item in this pipeline. * 2. Already in pipeline → log and return (no duplicate created). * 3. Not in pipeline → CreatePipelineItem (attach contact to the pipeline). * * @param LACRM_Forms_API $api API instance. * @param string $contact_id LACRM ContactId. * @param string $pipeline_id ID of the existing pipeline. * @param string $pipeline_status_id Status ID within the pipeline. * @param string $note Optional note text for the pipeline item. * @param string $assigned_to User ID to assign the pipeline item to. * @param int $form_id CF7 form ID (for logging context). */ private function sync_pipeline( LACRM_Forms_API $api, string $contact_id, string $pipeline_id, string $pipeline_status_id, string $note, string $assigned_to, int $form_id ): void { // ── Step 1: check whether contact is already in this pipeline ── $check = $api->check_contact_in_pipeline( $contact_id, $pipeline_id ); if ( $check['in_pipeline'] ) { LACRM_Forms_Logger::instance()->log( __( 'LACRM: contact already in pipeline — skipped to avoid duplicate.', 'aal-connector-for-lacrm' ), 'info', array( 'contact_id' => $contact_id, 'pipeline_id' => $pipeline_id, 'form_id' => $form_id ) ); return; } // ── Step 2: attach contact to the pipeline ── $result = $api->create_pipeline_item( $contact_id, array( 'pipeline_id' => $pipeline_id, 'status_id' => $pipeline_status_id, 'note' => $note, 'assigned_to' => $assigned_to, ) ); if ( ! $result['success'] ) { LACRM_Forms_Logger::instance()->log( $result['error_description'] ?? 'CreatePipelineItem failed', 'error', array( 'contact_id' => $contact_id, 'pipeline_id' => $pipeline_id, 'form_id' => $form_id ) ); } else { LACRM_Forms_Logger::instance()->log( __( 'LACRM: contact attached to pipeline.', 'aal-connector-for-lacrm' ), 'info', array( 'contact_id' => $contact_id, 'pipeline_item_id' => $result['pipeline_item_id'] ?? '', 'form_id' => $form_id ) ); } } }