api_key = $api_key; } /** * Call a LACRM API function. * * @param string $function Function name (e.g. GetContacts, CreateContact). * @param array $params Parameters (will be JSON-encoded). * @return array{success: bool, data?: array, error_code?: string, error_description?: string} */ public function call( string $function, array $params = array() ): array { $body = array( 'Function' => $function, 'Parameters' => $params, ); $body_json = wp_json_encode( $body ); if ( $body_json === false ) { return array( 'success' => false, 'error_code' => 'json_encode', 'error_description' => __( 'Failed to encode request body.', 'aal-connector-for-lacrm' ), ); } $response = wp_remote_post( self::API_URL, array( 'timeout' => 30, 'headers' => array( 'Content-Type' => 'application/json', 'Authorization' => $this->api_key, ), 'body' => $body_json, ) ); if ( is_wp_error( $response ) ) { return array( 'success' => false, 'error_code' => 'wp_error', 'error_description' => $response->get_error_message(), ); } $code = wp_remote_retrieve_response_code( $response ); $raw = wp_remote_retrieve_body( $response ); $data = json_decode( $raw, true ); if ( $code === 400 && is_array( $data ) && isset( $data['ErrorCode'], $data['ErrorDescription'] ) ) { return array( 'success' => false, 'error_code' => (string) $data['ErrorCode'], 'error_description' => (string) $data['ErrorDescription'], ); } if ( $code >= 200 && $code < 300 ) { return array( 'success' => true, 'data' => is_array( $data ) ? $data : array(), ); } return array( 'success' => false, 'error_code' => 'http_' . $code, 'error_description' => $raw ?: __( 'Unknown API error.', 'aal-connector-for-lacrm' ), ); } /** * Find a contact by email (GetContacts with AdvancedFilters). * * @param string $email Email to search (exact match). * @return array{success: bool, contact_id?: string, contact?: array} */ public function find_contact_by_email( string $email ): array { $email = sanitize_email( $email ); if ( ! is_email( $email ) ) { return array( 'success' => false ); } $result = $this->call( 'GetContacts', array( 'AdvancedFilters' => array( array( 'Name' => 'Email', 'Operation' => 'IsExactly', 'Value' => $email, ), ), 'RecordTypeFilter' => 'Contacts', 'MaxNumberOfResults' => 1, 'Page' => 1, ) ); if ( ! $result['success'] || empty( $result['data']['Results'][0] ) ) { return array( 'success' => false ); } $contact = $result['data']['Results'][0]; $contact_id = $contact['ContactId'] ?? ''; if ( ! $contact_id ) { return array( 'success' => false ); } return array( 'success' => true, 'contact_id' => $contact_id, 'contact' => $contact, ); } /** * Create a contact (CreateContact). * * @param array $data Must include: assigned_to (User ID), name (full name). Optional: email, phone, company_name, background_info. * @return array{success: bool, contact_id?: string, error_code?: string, error_description?: string} */ public function create_contact( array $data ): array { $assigned_to = isset( $data['assigned_to'] ) ? (string) $data['assigned_to'] : ''; $name = isset( $data['name'] ) ? trim( (string) $data['name'] ) : ''; if ( $assigned_to === '' || $name === '' ) { return array( 'success' => false, 'error_code' => 'validation', 'error_description' => __( 'AssignedTo and Name are required for CreateContact.', 'aal-connector-for-lacrm' ), ); } $params = array( 'IsCompany' => false, 'AssignedTo' => $assigned_to, 'Name' => $name, ); if ( ! empty( $data['email'] ) ) { $params['Email'] = array( array( 'Text' => (string) $data['email'] ) ); } if ( ! empty( $data['phone'] ) ) { $params['Phone'] = array( array( 'Text' => (string) $data['phone'] ) ); } if ( isset( $data['company_name'] ) && (string) $data['company_name'] !== '' ) { $params['Company Name'] = (string) $data['company_name']; } if ( isset( $data['background_info'] ) && (string) $data['background_info'] !== '' ) { $params['Background Info'] = (string) $data['background_info']; } // Custom fields are passed as direct top-level named parameters (the field Name is the key). if ( ! empty( $data['custom_fields'] ) && is_array( $data['custom_fields'] ) ) { foreach ( $data['custom_fields'] as $cf_name => $cf_value ) { $cf_name = (string) $cf_name; $cf_value = trim( (string) $cf_value ); if ( $cf_name !== '' && $cf_value !== '' ) { $params[ $cf_name ] = $cf_value; } } } $result = $this->call( 'CreateContact', $params ); if ( ! $result['success'] ) { return array( 'success' => false, 'error_code' => $result['error_code'] ?? '', 'error_description' => $result['error_description'] ?? '', ); } $contact_id = $result['data']['ContactId'] ?? ''; return array( 'success' => true, 'contact_id' => $contact_id, ); } /** * Update a contact (EditContact). * * @param string $contact_id LACRM ContactId. * @param array $data Fields to update: name, email, phone, company_name, background_info, assigned_to. * @return array{success: bool, error_code?: string, error_description?: string} */ public function edit_contact( string $contact_id, array $data ): array { $contact_id = sanitize_text_field( $contact_id ); if ( $contact_id === '' ) { return array( 'success' => false, 'error_code' => 'validation', 'error_description' => __( 'ContactId is required.', 'aal-connector-for-lacrm' ), ); } $params = array( 'ContactId' => $contact_id ); if ( isset( $data['name'] ) && (string) $data['name'] !== '' ) { $params['Name'] = (string) $data['name']; } if ( ! empty( $data['email'] ) ) { $params['Email'] = array( array( 'Text' => (string) $data['email'] ) ); } if ( isset( $data['phone'] ) && (string) $data['phone'] !== '' ) { $params['Phone'] = array( array( 'Text' => (string) $data['phone'] ) ); } if ( array_key_exists( 'company_name', $data ) ) { $params['Company Name'] = (string) $data['company_name']; } if ( isset( $data['background_info'] ) && (string) $data['background_info'] !== '' ) { $params['Background Info'] = (string) $data['background_info']; } if ( isset( $data['assigned_to'] ) && (string) $data['assigned_to'] !== '' ) { $params['AssignedTo'] = (string) $data['assigned_to']; } // Custom fields are passed as direct top-level named parameters (the field Name is the key). if ( ! empty( $data['custom_fields'] ) && is_array( $data['custom_fields'] ) ) { foreach ( $data['custom_fields'] as $cf_name => $cf_value ) { $cf_name = (string) $cf_name; $cf_value = trim( (string) $cf_value ); if ( $cf_name !== '' && $cf_value !== '' ) { $params[ $cf_name ] = $cf_value; } } } $result = $this->call( 'EditContact', $params ); if ( ! $result['success'] ) { return array( 'success' => false, 'error_code' => $result['error_code'] ?? '', 'error_description' => $result['error_description'] ?? '', ); } return array( 'success' => true ); } /** * Create a note on a contact (CreateNote). * * @param string $contact_id LACRM ContactId. * @param string $note Plain text note. * @return array{success: bool, note_id?: string, error_code?: string, error_description?: string} */ public function create_note( string $contact_id, string $note ): array { $contact_id = sanitize_text_field( $contact_id ); $note = sanitize_textarea_field( $note ); if ( $contact_id === '' || $note === '' ) { return array( 'success' => false, 'error_code' => 'validation', 'error_description' => __( 'ContactId and Note are required.', 'aal-connector-for-lacrm' ), ); } $result = $this->call( 'CreateNote', array( 'ContactId' => $contact_id, 'Note' => $note, ) ); if ( ! $result['success'] ) { return array( 'success' => false, 'error_code' => $result['error_code'] ?? '', 'error_description' => $result['error_description'] ?? '', ); } return array( 'success' => true, 'note_id' => $result['data']['NoteId'] ?? '', ); } /** * List users (GetUsers) for owner dropdown. * * @return array{success: bool, users?: array, error_code?: string, error_description?: string} */ public function get_users(): array { $result = $this->call( 'GetUsers', array() ); if ( ! $result['success'] ) { return array( 'success' => false, 'error_code' => $result['error_code'] ?? '', 'error_description' => $result['error_description'] ?? '', ); } $users = is_array( $result['data'] ) ? $result['data'] : array(); return array( 'success' => true, 'users' => $users, ); } /** * Get all pipelines with their statuses (GetPipelines). * * @return array{success: bool, pipelines?: array, error_code?: string, error_description?: string} */ public function get_pipelines(): array { $result = $this->call( 'GetPipelines', array() ); if ( ! $result['success'] ) { return array( 'success' => false, 'error_code' => $result['error_code'] ?? '', 'error_description' => $result['error_description'] ?? '', ); } $data = $result['data'] ?? array(); // LACRM v2 may return pipelines nested under 'Pipelines' key or as a direct array. if ( isset( $data['Pipelines'] ) && is_array( $data['Pipelines'] ) ) { $pipelines = $data['Pipelines']; } elseif ( is_array( $data ) && ! empty( $data ) && isset( $data[0] ) ) { $pipelines = $data; } else { $pipelines = array(); } return array( 'success' => true, 'pipelines' => $pipelines, ); } /** * Create a pipeline item for a contact (CreatePipelineItem). * * @param string $contact_id LACRM ContactId. * @param array $data Must include: pipeline_id, status_id. Optional: note, assigned_to. * @return array{success: bool, pipeline_item_id?: string, error_code?: string, error_description?: string} */ public function create_pipeline_item( string $contact_id, array $data ): array { $contact_id = sanitize_text_field( $contact_id ); $pipeline_id = isset( $data['pipeline_id'] ) ? sanitize_text_field( (string) $data['pipeline_id'] ) : ''; $status_id = isset( $data['status_id'] ) ? sanitize_text_field( (string) $data['status_id'] ) : ''; if ( $contact_id === '' || $pipeline_id === '' || $status_id === '' ) { return array( 'success' => false, 'error_code' => 'validation', 'error_description' => __( 'ContactId, PipelineId, and StatusId are required for CreatePipelineItem.', 'aal-connector-for-lacrm' ), ); } $params = array( 'ContactId' => $contact_id, 'PipelineId' => $pipeline_id, 'StatusId' => $status_id, ); if ( ! empty( $data['note'] ) ) { $params['Note'] = sanitize_textarea_field( (string) $data['note'] ); } if ( ! empty( $data['assigned_to'] ) ) { $params['AssignedTo'] = sanitize_text_field( (string) $data['assigned_to'] ); } $result = $this->call( 'CreatePipelineItem', $params ); if ( ! $result['success'] ) { return array( 'success' => false, 'error_code' => $result['error_code'] ?? '', 'error_description' => $result['error_description'] ?? '', ); } return array( 'success' => true, 'pipeline_item_id' => $result['data']['PipelineItemId'] ?? '', ); } /** * Find a company by name (GetContacts with RecordTypeFilter: Companies). * * @param string $name Company name (exact match). * @return array{success: bool, company_id?: string, company?: array} */ public function find_company_by_name( string $name ): array { $name = sanitize_text_field( $name ); if ( $name === '' ) { return array( 'success' => false ); } $result = $this->call( 'GetContacts', array( 'AdvancedFilters' => array( array( 'Name' => 'Name', 'Operation' => 'IsExactly', 'Value' => $name, ), ), 'RecordTypeFilter' => 'Companies', 'MaxNumberOfResults' => 1, 'Page' => 1, ) ); if ( ! $result['success'] || empty( $result['data']['Results'][0] ) ) { return array( 'success' => false ); } $company = $result['data']['Results'][0]; $company_id = $company['ContactId'] ?? ''; if ( $company_id === '' ) { return array( 'success' => false ); } return array( 'success' => true, 'company_id' => $company_id, 'company' => $company, ); } /** * Create a company record in LACRM (CreateContact with IsCompany = true). * * @param array $data Must include: assigned_to, name. * @return array{success: bool, company_id?: string, error_code?: string, error_description?: string} */ public function create_company( array $data ): array { $assigned_to = isset( $data['assigned_to'] ) ? sanitize_text_field( (string) $data['assigned_to'] ) : ''; $name = isset( $data['name'] ) ? sanitize_text_field( trim( (string) $data['name'] ) ) : ''; if ( $assigned_to === '' || $name === '' ) { return array( 'success' => false, 'error_code' => 'validation', 'error_description' => __( 'AssignedTo and Name are required for creating a Company.', 'aal-connector-for-lacrm' ), ); } $result = $this->call( 'CreateContact', array( 'IsCompany' => true, 'AssignedTo' => $assigned_to, 'Name' => $name, ) ); if ( ! $result['success'] ) { return array( 'success' => false, 'error_code' => $result['error_code'] ?? '', 'error_description' => $result['error_description'] ?? '', ); } return array( 'success' => true, 'company_id' => $result['data']['ContactId'] ?? '', ); } /** * Check whether a contact already has an item in a given pipeline (GetPipelineReport). * Used to prevent creating duplicate pipeline items for the same contact. * * If the API call itself fails we fail open (return in_pipeline = false) so that * a temporary API error does not silently block pipeline creation. * * @param string $contact_id LACRM ContactId to look for. * @param string $pipeline_id Pipeline to search in. * @return array{success: bool, in_pipeline: bool} */ public function check_contact_in_pipeline( string $contact_id, string $pipeline_id ): array { $contact_id = sanitize_text_field( $contact_id ); $pipeline_id = sanitize_text_field( $pipeline_id ); if ( $contact_id === '' || $pipeline_id === '' ) { return array( 'success' => false, 'in_pipeline' => false ); } $result = $this->call( 'GetPipelineReport', array( 'PipelineId' => $pipeline_id, 'MaxNumberOfResults' => 500, 'Page' => 1, ) ); // If we cannot reach the API, fail open so pipeline creation is not silently blocked. if ( ! $result['success'] ) { return array( 'success' => false, 'in_pipeline' => false ); } $data = $result['data'] ?? array(); // LACRM v2 may return results under different keys depending on version. if ( isset( $data['Results'] ) && is_array( $data['Results'] ) ) { $items = $data['Results']; } elseif ( isset( $data['PipelineItems'] ) && is_array( $data['PipelineItems'] ) ) { $items = $data['PipelineItems']; } elseif ( is_array( $data ) && ! empty( $data ) && isset( $data[0] ) ) { $items = $data; } else { $items = array(); } foreach ( $items as $item ) { if ( ! is_array( $item ) ) { continue; } // ContactId may appear under different casing. $item_contact = (string) ( $item['ContactId'] ?? $item['contactId'] ?? '' ); if ( $item_contact !== '' && $item_contact === $contact_id ) { return array( 'success' => true, 'in_pipeline' => true ); } } return array( 'success' => true, 'in_pipeline' => false ); } /** * Get contact custom field definitions from the account (GetCustomFields). * * The API returns paginated results under the 'Results' key, matching the same * structure as GetContacts. Only Contact-type fields are returned (no pipeline fields). * * @return array{success: bool, fields?: array, error_code?: string, error_description?: string} */ public function get_custom_fields(): array { $result = $this->call( 'GetCustomFields', array( 'RecordType' => 'Contact', ) ); if ( ! $result['success'] ) { return array( 'success' => false, 'error_code' => $result['error_code'] ?? '', 'error_description' => $result['error_description'] ?? '', ); } $data = $result['data'] ?? array(); // GetCustomFields uses the same paginated envelope as GetContacts: {HasMoreResults, Results: [...]} if ( isset( $data['Results'] ) && is_array( $data['Results'] ) ) { $fields = $data['Results']; } elseif ( is_array( $data ) && ! empty( $data ) && isset( $data[0] ) ) { $fields = $data; } else { $fields = array(); } return array( 'success' => true, 'fields' => $fields, ); } /** * Create a task linked to a contact (CreateTask). * * @param string $contact_id LACRM ContactId. * @param array $data Must include: description. Optional: due_date (YYYY-MM-DD), assigned_to. * @return array{success: bool, task_id?: string, error_code?: string, error_description?: string} */ public function create_task( string $contact_id, array $data ): array { $contact_id = sanitize_text_field( $contact_id ); $description = isset( $data['description'] ) ? sanitize_textarea_field( (string) $data['description'] ) : ''; if ( $contact_id === '' || $description === '' ) { return array( 'success' => false, 'error_code' => 'validation', 'error_description' => __( 'ContactId and Description are required for CreateTask.', 'aal-connector-for-lacrm' ), ); } $params = array( 'ContactId' => $contact_id, 'Description' => $description, ); if ( ! empty( $data['due_date'] ) ) { $params['DueDate'] = sanitize_text_field( (string) $data['due_date'] ); } if ( ! empty( $data['assigned_to'] ) ) { $params['AssignedTo'] = sanitize_text_field( (string) $data['assigned_to'] ); } $result = $this->call( 'CreateTask', $params ); if ( ! $result['success'] ) { return array( 'success' => false, 'error_code' => $result['error_code'] ?? '', 'error_description' => $result['error_description'] ?? '', ); } return array( 'success' => true, 'task_id' => $result['data']['TaskId'] ?? '', ); } /** * Associate a contact (person) with a company contact (AssociateContacts). * * @param string $contact_id LACRM ContactId of the person. * @param string $company_id LACRM ContactId of the company record. * @return array{success: bool, error_code?: string, error_description?: string} */ public function associate_contacts( string $contact_id, string $company_id ): array { $contact_id = sanitize_text_field( $contact_id ); $company_id = sanitize_text_field( $company_id ); if ( $contact_id === '' || $company_id === '' ) { return array( 'success' => false, 'error_code' => 'validation', 'error_description' => __( 'Both ContactId and CompanyId are required for AssociateContacts.', 'aal-connector-for-lacrm' ), ); } $result = $this->call( 'AssociateContacts', array( 'ContactId' => $contact_id, 'TargetContactId' => $company_id, ) ); if ( ! $result['success'] ) { return array( 'success' => false, 'error_code' => $result['error_code'] ?? '', 'error_description' => $result['error_description'] ?? '', ); } return array( 'success' => true ); } }