diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 984602a3..a0dda661 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,11 +30,9 @@ jobs: uses: actions/cache@v4 with: path: node_modules - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('package-lock.json') }} + key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }} restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}- - ${{ runner.os }}-build- - ${{ runner.os }}- + ${{ runner.os }}-node- - name: Install dependencies run: npm ci diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 855f6ad7..6745c2f2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,6 +15,14 @@ jobs: steps: - uses: actions/checkout@v5 + - name: Cache composer dependencies + uses: actions/cache@v4 + with: + path: vendor + key: ${{ runner.os }}-composer-${{ hashFiles('composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer- + - name: Composer install run: composer -q install @@ -35,17 +43,21 @@ jobs: with: submodules: true - - name: Cache composer + - name: Cache composer dependencies uses: actions/cache@v4 with: path: vendor - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('composer.lock') }} + key: ${{ runner.os }}-composer-${{ hashFiles('composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer- - - name: Cache plugins + - name: Cache test plugins uses: actions/cache@v4 with: path: /tmp/*.zip - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('bin/download-test-deps.sh') }} + key: ${{ runner.os }}-test-plugins-${{ hashFiles('bin/download-test-deps.sh') }} + restore-keys: | + ${{ runner.os }}-test-plugins- - name: Composer install run: composer -q install diff --git a/README.md b/README.md index f83c189e..152ee362 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ Forms Bridge has the following addons: - [Rocket.Chat](https://formsbridge.codeccoop.org/documentation/rocket-chat/) - [Slack](https://formsbridge.codeccoop.org/documentation/slack/) - [SuiteCRM](https://formsbridge.codeccoop.org/documentation/suitecrm/) +- [Vtiger](https://formsbridge.codeccoop.org/documentation/vtiger/) - [Zoho CRM](https://formsbridge.codeccoop.org/documentation/zoho-crm/) - [Zulip](https://formsbridge.codeccoop.org/documentation/zulip/) diff --git a/forms-bridge/addons/suitecrm/class-suitecrm-addon.php b/forms-bridge/addons/suitecrm/class-suitecrm-addon.php index ea5d0c5f..f261def8 100644 --- a/forms-bridge/addons/suitecrm/class-suitecrm-addon.php +++ b/forms-bridge/addons/suitecrm/class-suitecrm-addon.php @@ -88,12 +88,7 @@ public function fetch( $endpoint, $backend ) { ) ); - return $bridge->submit( - array( - 'select_fields' => array( 'id', 'name' ), - 'max_results' => 100, - ) - ); + return $bridge->submit( array( 'max_results' => 100 ) ); } /** diff --git a/forms-bridge/addons/suitecrm/class-suitecrm-form-bridge.php b/forms-bridge/addons/suitecrm/class-suitecrm-form-bridge.php index 73f5c36e..7aab83f8 100644 --- a/forms-bridge/addons/suitecrm/class-suitecrm-form-bridge.php +++ b/forms-bridge/addons/suitecrm/class-suitecrm-form-bridge.php @@ -198,6 +198,7 @@ public function submit( $payload = array(), $attachments = array() ) { add_filter( 'http_bridge_request', static function ( $request ) { + unset( $request['args']['headers']['Authorization'] ); self::$request = $request; return $request; }, diff --git a/forms-bridge/addons/suitecrm/jobs/contact.php b/forms-bridge/addons/suitecrm/jobs/contact.php index b7a4f4ba..d628500c 100644 --- a/forms-bridge/addons/suitecrm/jobs/contact.php +++ b/forms-bridge/addons/suitecrm/jobs/contact.php @@ -203,8 +203,8 @@ /** * Creates a new contact and add its ID to the payload. * - * @param array $payload Bridge payload. - * @param Form_Bridge $bridge Bridge object. + * @param array $payload Bridge payload. + * @param SuiteCRM_Form_Bridge $bridge Bridge object. * * @return array */ diff --git a/forms-bridge/addons/suitecrm/templates/accounts.php b/forms-bridge/addons/suitecrm/templates/accounts.php index ab6746f3..826accce 100644 --- a/forms-bridge/addons/suitecrm/templates/accounts.php +++ b/forms-bridge/addons/suitecrm/templates/accounts.php @@ -12,7 +12,7 @@ return array( 'title' => __( 'Accounts', 'forms-bridge' ), 'description' => __( - 'Account bridge template. The resulting bridge will convert form submissions into SuiteCRM accounts (companies/organizations).', + 'Account form bridge template. The resulting bridge will convert form submissions into SuiteCRM accounts (companies/organizations).', 'forms-bridge' ), 'fields' => array( @@ -27,15 +27,11 @@ 'value' => 'Contacts', ), array( - 'ref' => '#bridge/custom_fields[]', - 'name' => 'assigned_user_id', - 'label' => __( 'Assigned User', 'forms-bridge' ), - 'description' => __( - 'User to assign the account to', - 'forms-bridge' - ), - 'type' => 'select', - 'options' => array( + 'ref' => '#bridge/custom_fields[]', + 'name' => 'assigned_user_id', + 'label' => __( 'Assigned User', 'forms-bridge' ), + 'type' => 'select', + 'options' => array( 'endpoint' => 'Users', 'finger' => array( 'value' => 'entry_list[].id', @@ -146,6 +142,10 @@ 'value' => 'Entertainment', 'label' => __( 'Entertainment', 'forms-bridge' ), ), + array( + 'value' => 'Environmental', + 'label' => __( 'Environmental', 'forms-bridge' ), + ), array( 'value' => 'Finance', 'label' => __( 'Finance', 'forms-bridge' ), @@ -166,6 +166,10 @@ 'value' => 'Insurance', 'label' => __( 'Insurance', 'forms-bridge' ), ), + array( + 'value' => 'Machinery', + 'label' => __( 'Machinery', 'forms-bridge' ), + ), array( 'value' => 'Manufacturing', 'label' => __( 'Manufacturing', 'forms-bridge' ), @@ -174,18 +178,38 @@ 'value' => 'Media', 'label' => __( 'Media', 'forms-bridge' ), ), + array( + 'value' => 'Not For Profit', + 'label' => __( 'Not For Profit', 'forms-bridge' ), + ), + array( + 'value' => 'Recreation', + 'label' => __( 'Recreation', 'forms-bridge' ), + ), array( 'value' => 'Retail', 'label' => __( 'Retail', 'forms-bridge' ), ), + array( + 'value' => 'Shipping', + 'label' => __( 'Shipping', 'forms-bridge' ), + ), array( 'value' => 'Technology', 'label' => __( 'Technology', 'forms-bridge' ), ), + array( + 'value' => 'Telecomunications', + 'label' => __( 'Telecomunications', 'forms-bridge' ), + ), array( 'value' => 'Transportation', 'label' => __( 'Transportation', 'forms-bridge' ), ), + array( + 'value' => 'Utilities', + 'label' => __( 'Utilities', 'forms-bridge' ), + ), array( 'value' => 'Other', 'label' => __( 'Other', 'forms-bridge' ), @@ -206,10 +230,30 @@ 'value' => 'Cold Call', 'label' => __( 'Cold Call', 'forms-bridge' ), ), + array( + 'value' => 'Existing Customer', + 'label' => __( 'Existing Customer', 'forms-bridge' ), + ), + array( + 'value' => 'Employee', + 'label' => __( 'Employee', 'forms-bridge' ), + ), + array( + 'value' => 'Partner', + 'label' => __( 'Partner', 'forms-bridge' ), + ), + array( + 'value' => 'Public Relations', + 'label' => __( 'Public Relations', 'forms-bridge' ), + ), array( 'value' => 'Email', 'label' => __( 'Email', 'forms-bridge' ), ), + array( + 'value' => 'Direct Mail', + 'label' => __( 'Direct Mail', 'forms-bridge' ), + ), array( 'value' => 'Word of mouth', 'label' => __( 'Word of Mouth', 'forms-bridge' ), @@ -218,6 +262,14 @@ 'value' => 'Campaign', 'label' => __( 'Campaign', 'forms-bridge' ), ), + array( + 'value' => 'Conference', + 'label' => __( 'Conference', 'forms-bridge' ), + ), + array( + 'value' => 'Trade Show', + 'label' => __( 'Trade Show', 'forms-bridge' ), + ), array( 'value' => 'Other', 'label' => __( 'Other', 'forms-bridge' ), @@ -227,9 +279,25 @@ ), ), 'bridge' => array( - 'endpoint' => 'Contacts', - 'method' => 'set_entry', - 'workflow' => array( 'account', 'skip-contact' ), + 'endpoint' => 'Contacts', + 'method' => 'set_entry', + 'workflow' => array( 'account', 'skip-contact' ), + 'mutations' => array( + array( + array( + 'from' => 'email1', + 'to' => 'user_email', + 'cast' => 'copy', + ), + ), + array( + array( + 'from' => 'user_email', + 'to' => 'email1', + 'cast' => 'string', + ), + ), + ), ), 'form' => array( 'fields' => array( @@ -252,9 +320,15 @@ 'required' => true, ), array( - 'label' => __( 'Email', 'forms-bridge' ), - 'name' => 'email1', - 'type' => 'email', + 'label' => __( 'Email', 'forms-bridge' ), + 'name' => 'email1', + 'type' => 'email', + 'required' => true, + ), + array( + 'label' => __( 'Title', 'forms-bridge' ), + 'name' => 'title', + 'type' => 'text', ), array( 'label' => __( 'Phone', 'forms-bridge' ), diff --git a/forms-bridge/addons/suitecrm/templates/contacts.php b/forms-bridge/addons/suitecrm/templates/contacts.php index 415e51ee..bf735035 100644 --- a/forms-bridge/addons/suitecrm/templates/contacts.php +++ b/forms-bridge/addons/suitecrm/templates/contacts.php @@ -12,7 +12,7 @@ return array( 'title' => __( 'Contacts', 'forms-bridge' ), 'description' => __( - 'Contact form template. The resulting bridge will convert form submissions into SuiteCRM contacts.', + 'Contact form bridge template. The resulting bridge will convert form submissions into SuiteCRM contacts.', 'forms-bridge' ), 'fields' => array( @@ -53,10 +53,30 @@ 'value' => 'Cold Call', 'label' => __( 'Cold Call', 'forms-bridge' ), ), + array( + 'value' => 'Existing Customer', + 'label' => __( 'Existing Customer', 'forms-bridge' ), + ), + array( + 'value' => 'Employee', + 'label' => __( 'Employee', 'forms-bridge' ), + ), + array( + 'value' => 'Partner', + 'label' => __( 'Partner', 'forms-bridge' ), + ), + array( + 'value' => 'Public Relations', + 'label' => __( 'Public Relations', 'forms-bridge' ), + ), array( 'value' => 'Email', 'label' => __( 'Email', 'forms-bridge' ), ), + array( + 'value' => 'Direct Mail', + 'label' => __( 'Direct Mail', 'forms-bridge' ), + ), array( 'value' => 'Word of mouth', 'label' => __( 'Word of Mouth', 'forms-bridge' ), @@ -65,6 +85,14 @@ 'value' => 'Campaign', 'label' => __( 'Campaign', 'forms-bridge' ), ), + array( + 'value' => 'Conference', + 'label' => __( 'Conference', 'forms-bridge' ), + ), + array( + 'value' => 'Trade Show', + 'label' => __( 'Trade Show', 'forms-bridge' ), + ), array( 'value' => 'Other', 'label' => __( 'Other', 'forms-bridge' ), @@ -103,11 +131,6 @@ 'name' => 'phone_work', 'type' => 'tel', ), - array( - 'label' => __( 'Mobile', 'forms-bridge' ), - 'name' => 'phone_mobile', - 'type' => 'tel', - ), array( 'label' => __( 'Address', 'forms-bridge' ), 'name' => 'primary_address_street', diff --git a/forms-bridge/addons/suitecrm/templates/leads.php b/forms-bridge/addons/suitecrm/templates/leads.php index fde77969..d2ba7164 100644 --- a/forms-bridge/addons/suitecrm/templates/leads.php +++ b/forms-bridge/addons/suitecrm/templates/leads.php @@ -86,10 +86,30 @@ 'value' => 'Cold Call', 'label' => __( 'Cold Call', 'forms-bridge' ), ), + array( + 'value' => 'Existing Customer', + 'label' => __( 'Existing Customer', 'forms-bridge' ), + ), + array( + 'value' => 'Employee', + 'label' => __( 'Employee', 'forms-bridge' ), + ), + array( + 'value' => 'Partner', + 'label' => __( 'Partner', 'forms-bridge' ), + ), + array( + 'value' => 'Public Relations', + 'label' => __( 'Public Relations', 'forms-bridge' ), + ), array( 'value' => 'Email', 'label' => __( 'Email', 'forms-bridge' ), ), + array( + 'value' => 'Direct Mail', + 'label' => __( 'Direct Mail', 'forms-bridge' ), + ), array( 'value' => 'Word of mouth', 'label' => __( 'Word of Mouth', 'forms-bridge' ), @@ -106,10 +126,6 @@ 'value' => 'Trade Show', 'label' => __( 'Trade Show', 'forms-bridge' ), ), - array( - 'value' => 'Partner', - 'label' => __( 'Partner', 'forms-bridge' ), - ), array( 'value' => 'Other', 'label' => __( 'Other', 'forms-bridge' ), @@ -147,11 +163,6 @@ 'name' => 'phone_work', 'type' => 'tel', ), - array( - 'label' => __( 'Mobile', 'forms-bridge' ), - 'name' => 'phone_mobile', - 'type' => 'tel', - ), array( 'label' => __( 'Company', 'forms-bridge' ), 'name' => 'account_name', @@ -167,31 +178,6 @@ 'name' => 'website', 'type' => 'url', ), - array( - 'label' => __( 'Address', 'forms-bridge' ), - 'name' => 'primary_address_street', - 'type' => 'text', - ), - array( - 'label' => __( 'City', 'forms-bridge' ), - 'name' => 'primary_address_city', - 'type' => 'text', - ), - array( - 'label' => __( 'Postal Code', 'forms-bridge' ), - 'name' => 'primary_address_postalcode', - 'type' => 'text', - ), - array( - 'label' => __( 'State', 'forms-bridge' ), - 'name' => 'primary_address_state', - 'type' => 'text', - ), - array( - 'label' => __( 'Country', 'forms-bridge' ), - 'name' => 'primary_address_country', - 'type' => 'text', - ), array( 'label' => __( 'Message', 'forms-bridge' ), 'name' => 'description', diff --git a/forms-bridge/addons/suitecrm/templates/meetings.php b/forms-bridge/addons/suitecrm/templates/meetings.php index 26bdf6db..2239ba55 100644 --- a/forms-bridge/addons/suitecrm/templates/meetings.php +++ b/forms-bridge/addons/suitecrm/templates/meetings.php @@ -12,7 +12,7 @@ return array( 'title' => __( 'Meetings', 'forms-bridge' ), 'description' => __( - 'Meetings bridge template. The resulting bridge will convert form submissions into SuiteCRM meetings.', + 'Meetings form bridge template. The resulting bridge will convert form submissions into SuiteCRM meetings.', 'forms-bridge', ), 'fields' => array( @@ -61,22 +61,18 @@ 'required' => true, ), array( - 'ref' => '#bridge/custom_fields[]', - 'name' => 'meeting_assigned_user_id', - 'label' => __( 'Assigned User', 'forms-bridge' ), - 'description' => __( - 'User to assign the account to', - 'forms-bridge' - ), - 'type' => 'select', - 'options' => array( + 'ref' => '#bridge/custom_fields[]', + 'name' => 'assigned_user_id', + 'label' => __( 'Assigned User', 'forms-bridge' ), + 'type' => 'select', + 'options' => array( 'endpoint' => 'Users', 'finger' => array( 'value' => 'entry_list[].id', 'label' => 'entry_list[].name_value_list.name.value', ), ), - 'required' => true, + 'required' => true, ), array( 'ref' => '#bridge/custom_fields[]', @@ -92,10 +88,30 @@ 'value' => 'Cold Call', 'label' => __( 'Cold Call', 'forms-bridge' ), ), + array( + 'value' => 'Existing Customer', + 'label' => __( 'Existing Customer', 'forms-bridge' ), + ), + array( + 'value' => 'Employee', + 'label' => __( 'Employee', 'forms-bridge' ), + ), + array( + 'value' => 'Partner', + 'label' => __( 'Partner', 'forms-bridge' ), + ), + array( + 'value' => 'Public Relations', + 'label' => __( 'Public Relations', 'forms-bridge' ), + ), array( 'value' => 'Email', 'label' => __( 'Email', 'forms-bridge' ), ), + array( + 'value' => 'Direct Mail', + 'label' => __( 'Direct Mail', 'forms-bridge' ), + ), array( 'value' => 'Word of mouth', 'label' => __( 'Word of Mouth', 'forms-bridge' ), @@ -104,6 +120,14 @@ 'value' => 'Campaign', 'label' => __( 'Campaign', 'forms-bridge' ), ), + array( + 'value' => 'Conference', + 'label' => __( 'Conference', 'forms-bridge' ), + ), + array( + 'value' => 'Trade Show', + 'label' => __( 'Trade Show', 'forms-bridge' ), + ), array( 'value' => 'Other', 'label' => __( 'Other', 'forms-bridge' ), @@ -120,10 +144,6 @@ 'name' => 'meeting_status', 'value' => 'Planned', ), - array( - 'name' => 'meeting_type', - 'value' => 'Sugar', - ), array( 'name' => 'parent_type', 'value' => 'Contacts', @@ -131,7 +151,13 @@ ), 'workflow' => array( 'date-fields-to-date', 'contact', 'meeting-invitees' ), 'mutations' => array( - array(), + array( + array( + 'from' => 'assigned_user_id', + 'to' => 'meeting_assigned_user_id', + 'cast' => 'copy', + ), + ), array( array( 'from' => 'first_name', @@ -224,11 +250,6 @@ 'name' => 'phone_work', 'type' => 'tel', ), - array( - 'label' => __( 'Mobile', 'forms-bridge' ), - 'name' => 'phone_mobile', - 'type' => 'tel', - ), array( 'name' => 'date', 'label' => __( 'Date', 'forms-bridge' ), diff --git a/forms-bridge/addons/vtiger/assets/logo.png b/forms-bridge/addons/vtiger/assets/logo.png new file mode 100644 index 00000000..dd2d81ea Binary files /dev/null and b/forms-bridge/addons/vtiger/assets/logo.png differ diff --git a/forms-bridge/addons/vtiger/class-vtiger-addon.php b/forms-bridge/addons/vtiger/class-vtiger-addon.php new file mode 100644 index 00000000..a0f213f2 --- /dev/null +++ b/forms-bridge/addons/vtiger/class-vtiger-addon.php @@ -0,0 +1,193 @@ + '__vtiger-' . time(), + 'method' => 'listtypes', + 'endpoint' => '', + 'backend' => $backend, + ) + ); + + $response = $bridge->submit(); + + if ( is_wp_error( $response ) ) { + Logger::log( 'Vtiger backend ping error response', Logger::ERROR ); + Logger::log( $response, Logger::ERROR ); + return false; + } + + return true; + } + + /** + * Performs a query request against the backend module and retrieve the response data. + * + * @param string $endpoint Target module name. + * @param string $backend Target backend name. + * + * @return array|WP_Error + */ + public function fetch( $endpoint, $backend ) { + $bridge = new Vtiger_Form_Bridge( + array( + 'name' => '__vtiger-' . time(), + 'method' => 'query', + 'endpoint' => $endpoint, + 'backend' => $backend, + ) + ); + + return $bridge->submit( + array( + 'query' => "SELECT * FROM {$endpoint};", + ) + ); + } + + /** + * Fetch available modules from the backend. + * + * @param Backend $backend HTTP backend object. + * + * @return array + */ + public function get_endpoints( $backend ) { + $bridge = new Vtiger_Form_Bridge( + array( + 'name' => '__vtiger-' . time(), + 'method' => 'listtypes', + 'endpoint' => '', + 'backend' => $backend, + ) + ); + + $response = $bridge->submit(); + + if ( is_wp_error( $response ) ) { + return array(); + } + + if ( ! isset( $response['data']['result']['types'] ) ) { + return array(); + } + + return $response['data']['result']['types']; + } + + /** + * Performs an introspection of the backend module and returns API fields + * and accepted content type. + * + * @param string $module Target module name. + * @param string $backend Target backend name. + * @param string|null $method API method. + * + * @return array List of fields and content type of the module. + */ + public function get_endpoint_schema( $module, $backend, $method = null ) { + if ( 'create' !== $method ) { + return array(); + } + + $bridge = new Vtiger_Form_Bridge( + array( + 'name' => '__vtiger-' . time(), + 'method' => 'describe', + 'endpoint' => $module, + 'backend' => $backend, + ) + ); + + $response = $bridge->submit(); + + if ( is_wp_error( $response ) ) { + return array(); + } + + if ( ! isset( $response['data']['result']['fields'] ) ) { + return array(); + } + + $fields = array(); + foreach ( $response['data']['result']['fields'] as $spec ) { + if ( ! $spec['editable'] ) { + continue; + } + + $type = 'string'; + + if ( in_array( $spec['type']['name'], array( 'integer', 'autogenerated' ), true ) ) { + $type = 'integer'; + } elseif ( in_array( $spec['type']['name'], array( 'double', 'currency' ), true ) ) { + $type = 'number'; + } elseif ( 'boolean' === $spec['type']['name'] ) { + $type = 'boolean'; + } + + $schema = array( + 'type' => $type, + 'required' => $spec['mandatory'], + ); + + $fields[] = array( + 'name' => $spec['name'], + 'schema' => $schema, + ); + } + + return $fields; + } +} + +Vtiger_Addon::setup(); diff --git a/forms-bridge/addons/vtiger/class-vtiger-form-bridge.php b/forms-bridge/addons/vtiger/class-vtiger-form-bridge.php new file mode 100644 index 00000000..026a2823 --- /dev/null +++ b/forms-bridge/addons/vtiger/class-vtiger-form-bridge.php @@ -0,0 +1,386 @@ + $res ); + if ( self::$request ) { + $error_data['request'] = self::$request; + } + + $error->add_data( $error_data ); + return $error; + } + + return $data; + } + + /** + * Get challenge token for authentication. + * + * @param string $username Username for challenge. + * @param Backend $backend Bridge backend object. + * + * @return string|WP_Error Challenge token on success. + */ + private static function get_challenge( $username, $backend ) { + $url = self::ENDPOINT . '?' . http_build_query( + array( + 'operation' => 'getchallenge', + 'username' => $username, + ) + ); + + $response = $backend->get( $url ); + $result = self::rest_response( $response ); + + if ( is_wp_error( $result ) ) { + return $result; + } + + if ( empty( $result['result']['token'] ) ) { + return new WP_Error( + 'challenge_failed', + __( 'Vtiger challenge failed: No token returned', 'forms-bridge' ), + $result + ); + } + + return $result['result']['token']; + } + + /** + * Login to Vtiger and get session name. + * + * @param Credential $credential Bridge credential object. + * @param Backend $backend Bridge backend object. + * + * @return string|WP_Error Session name on success. + */ + private static function rest_login( $credential, $backend ) { + if ( self::$session_name ) { + return self::$session_name; + } + + $username = $credential->client_id ?? ''; + $access_key = $credential->client_secret ?? ''; + + // Step 1: Get challenge token. + $token = self::get_challenge( $username, $backend ); + + if ( is_wp_error( $token ) ) { + return $token; + } + + // Step 2: Login with MD5(token + accessKey). + $access_key_hash = md5( $token . $access_key ); + + $payload = array( + 'operation' => 'login', + 'username' => $username, + 'accessKey' => $access_key_hash, + ); + + $response = $backend->post( self::ENDPOINT, $payload ); + $result = self::rest_response( $response ); + + if ( is_wp_error( $result ) ) { + return $result; + } + + if ( empty( $result['result']['sessionName'] ) ) { + return new WP_Error( + 'login_failed', + __( 'Vtiger login failed: No session returned', 'forms-bridge' ), + $result + ); + } + + self::$session_name = $result['result']['sessionName']; + self::$user_id = $result['result']['userId'] ?? ''; + + return self::$session_name; + } + + /** + * Bridge constructor with addon name provisioning. + * + * @param array $data Bridge data. + */ + public function __construct( $data ) { + parent::__construct( $data, 'vtiger' ); + } + + /** + * Submits submission to the backend. + * + * @param array $payload Submission data. + * @param array $more_args Additional arguments. + * + * @return array|WP_Error HTTP response. + */ + public function submit( $payload = array(), $more_args = array() ) { + if ( ! $this->is_valid ) { + return new WP_Error( + 'invalid_bridge', + 'Bridge data is invalid', + (array) $this->data + ); + } + + $backend = $this->backend(); + + if ( ! $backend ) { + return new WP_Error( + 'invalid_backend', + 'The bridge does not have a valid backend', + $this->data, + ); + } + + $credential = $backend->credential; + if ( ! $credential ) { + return new WP_Error( + 'invalid_credential', + 'The bridge does not have a valid credential', + $backend->data(), + ); + } + + add_filter( + 'http_bridge_request', + static function ( $request ) { + unset( $request['args']['headers']['Authorization'] ); + self::$request = $request; + return $request; + }, + 10, + 1 + ); + + // Login to get session. + $session_name = self::rest_login( $credential, $backend ); + + if ( is_wp_error( $session_name ) ) { + return $session_name; + } + + // Build and execute the API request. + return $this->execute_operation( $session_name, $payload, $more_args, $backend ); + } + + /** + * Execute a Vtiger webservice operation. + * + * @param string $session_name Session name. + * @param array $payload Form submission payload. + * @param array $more_args Additional arguments. + * @param Backend $backend Backend object. + * + * @return array|WP_Error HTTP response. + */ + private function execute_operation( $session_name, $payload, $more_args, $backend ) { + $module = $this->endpoint; + + switch ( $this->method ) { + case 'listtypes': + $url = self::ENDPOINT . '?' . http_build_query( + array( + 'operation' => 'listtypes', + 'sessionName' => $session_name, + ) + ); + $response = $backend->get( $url ); + break; + + case 'describe': + $url = self::ENDPOINT . '?' . http_build_query( + array( + 'operation' => 'describe', + 'sessionName' => $session_name, + 'elementType' => $module, + ) + ); + $response = $backend->get( $url ); + break; + + case 'query': + $query = $more_args['query'] ?? $payload['query'] ?? "SELECT * FROM {$module};"; + $url = self::ENDPOINT . '?' . http_build_query( + array( + 'operation' => 'query', + 'sessionName' => $session_name, + 'query' => $query, + ) + ); + $response = $backend->get( $url ); + break; + + case 'retrieve': + $url = self::ENDPOINT . '?' . http_build_query( + array( + 'operation' => 'retrieve', + 'sessionName' => $session_name, + 'id' => $payload['id'] ?? '', + ) + ); + $response = $backend->get( $url ); + break; + + case 'create': + // Add module type to element data. + $element = array_merge( + $payload, + array( 'assigned_user_id' => $payload['assigned_user_id'] ?? self::$user_id ) + ); + + $post_data = array( + 'operation' => 'create', + 'sessionName' => $session_name, + 'elementType' => $module, + 'element' => wp_json_encode( $element ), + ); + + $response = $backend->post( self::ENDPOINT, $post_data ); + break; + + case 'update': + $post_data = array( + 'operation' => 'update', + 'sessionName' => $session_name, + 'element' => wp_json_encode( $payload ), + ); + + $response = $backend->post( self::ENDPOINT, $post_data ); + break; + + case 'delete': + $post_data = array( + 'operation' => 'delete', + 'sessionName' => $session_name, + 'id' => $payload['id'] ?? '', + ); + + $response = $backend->post( self::ENDPOINT, $post_data ); + break; + + case 'sync': + $url = self::ENDPOINT . '?' . http_build_query( + array( + 'operation' => 'sync', + 'sessionName' => $session_name, + 'elementType' => $module, + 'modifiedTime' => $more_args['modifiedTime'] ?? 0, + ) + ); + $response = $backend->get( $url ); + break; + + default: + // For custom operations, try as GET first. + $params = array_merge( + array( + 'operation' => $this->method, + 'sessionName' => $session_name, + ), + $payload + ); + + if ( ! empty( $module ) ) { + $params['elementType'] = $module; + } + + $url = self::ENDPOINT . '?' . http_build_query( $params ); + $response = $backend->get( $url ); + break; + } + + $result = self::rest_response( $response ); + + if ( is_wp_error( $result ) ) { + return $result; + } + + // Normalize response. + $response['data'] = $result; + + return $response; + } +} diff --git a/forms-bridge/addons/vtiger/hooks.php b/forms-bridge/addons/vtiger/hooks.php new file mode 100644 index 00000000..a11aee77 --- /dev/null +++ b/forms-bridge/addons/vtiger/hooks.php @@ -0,0 +1,172 @@ + array( + array( + 'ref' => '#credential', + 'name' => 'name', + 'label' => __( 'Name', 'forms-bridge' ), + 'type' => 'text', + 'required' => true, + ), + array( + 'ref' => '#credential', + 'name' => 'schema', + 'type' => 'text', + 'value' => 'Basic', + ), + array( + 'ref' => '#credential', + 'name' => 'client_id', + 'label' => __( 'Username', 'forms-bridge' ), + 'description' => __( + 'Vtiger user name', + 'forms-bridge' + ), + 'type' => 'text', + 'required' => true, + ), + array( + 'ref' => '#credential', + 'name' => 'client_secret', + 'description' => __( + 'Access Key from My Preferences in Vtiger', + 'forms-bridge' + ), + 'label' => __( 'Access Key', 'forms-bridge' ), + 'type' => 'text', + 'required' => true, + ), + array( + 'ref' => '#backend', + 'name' => 'base_url', + 'label' => __( 'Vtiger URL', 'forms-bridge' ), + 'description' => __( + 'Base URL of your Vtiger installation (e.g., https://crm.example.com)', + 'forms-bridge' + ), + 'type' => 'url', + 'required' => true, + ), + array( + 'ref' => '#backend', + 'name' => 'name', + 'default' => 'Vtiger', + ), + array( + 'ref' => '#backend/headers[]', + 'name' => 'Content-Type', + 'value' => 'application/x-www-form-urlencoded', + ), + array( + 'ref' => '#bridge', + 'name' => 'endpoint', + 'label' => __( 'Module', 'forms-bridge' ), + 'type' => 'text', + 'required' => true, + ), + array( + 'ref' => '#bridge', + 'name' => 'method', + 'label' => __( 'Operation', 'forms-bridge' ), + 'type' => 'text', + 'value' => 'create', + 'required' => true, + ), + ), + 'bridge' => array( + 'name' => '', + 'form_id' => '', + 'backend' => '', + 'endpoint' => '', + 'method' => 'create', + ), + 'backend' => array( + 'name' => 'Vtiger', + 'headers' => array( + array( + 'name' => 'Content-Type', + 'value' => 'application/x-www-form-urlencoded', + ), + array( + 'name' => 'Accept', + 'value' => 'application/json', + ), + ), + ), + 'credential' => array( + 'name' => '', + 'schema' => 'Basic', + 'client_id' => '', + 'client_secret' => '', + ), + ), + $defaults, + $schema + ); + }, + 10, + 3 +); diff --git a/forms-bridge/addons/vtiger/jobs/account.php b/forms-bridge/addons/vtiger/jobs/account.php new file mode 100644 index 00000000..dbb68b82 --- /dev/null +++ b/forms-bridge/addons/vtiger/jobs/account.php @@ -0,0 +1,261 @@ + __( 'Account', 'forms-bridge' ), + 'description' => __( 'Creates an account (organization) in Vtiger', 'forms-bridge' ), + 'method' => 'forms_bridge_vtiger_create_account', + 'input' => array( + array( + 'name' => 'accountname', + 'schema' => array( 'type' => 'string' ), + 'required' => true, + ), + array( + 'name' => 'phone', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'otherphone', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'website', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'fax', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'tickersymbol', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'email1', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'email2', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'ownership', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'employees', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'account_id', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'rating', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'industry', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'siccode', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'accounttype', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'annual_revenue', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'emailoptout', + 'schema' => array( 'type' => 'boolean' ), + ), + array( + 'name' => 'notify_owner', + 'schema' => array( 'type' => 'boolean' ), + ), + array( + 'name' => 'assigned_user_id', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'bill_street', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'ship_street', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'bill_city', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'ship_city', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'bill_state', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'ship_state', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'bill_code', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'ship_code', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'bill_country', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'ship_country', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'bill_pobox', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'ship_pobox', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'description', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'starred', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'tags', + 'schema' => array( 'type' => 'string' ), + ), + ), + 'output' => array( + array( + 'name' => 'account_id', + 'schema' => array( 'type' => 'string' ), + ), + ), +); + +/** + * Creates a new account and add its ID to the payload. + * + * @param array $payload Bridge payload. + * @param Form_Bridge $bridge Bridge object. + * + * @return array + */ +function forms_bridge_vtiger_create_account( $payload, $bridge ) { + $account = array( + 'accountname' => $payload['accountname'], + ); + + $account_fields = array( + 'phone', + 'website', + 'fax', + 'tickersymbol', + 'otherphone', + 'account_id', + 'email1', + 'email2', + 'employees', + 'ownership', + 'rating', + 'industry', + 'siccode', + 'accounttype', + 'annual_revenue', + 'emailoptout', + 'notify_owner', + 'assigned_user_id', + 'bill_street', + 'ship_street', + 'bill_city', + 'ship_city', + 'bill_code', + 'ship_code', + 'bill_state', + 'ship_state', + 'bill_country', + 'ship_country', + 'bill_pobox', + 'ship_pobox', + 'description', + 'starred', + 'tags', + ); + + foreach ( $account_fields as $field ) { + if ( isset( $payload[ $field ] ) ) { + $account[ $field ] = $payload[ $field ]; + } + } + + $query = "SELECT id FROM Accounts WHERE accountname = '{$account['accountname']}';"; + + $response = $bridge->patch( + array( + 'method' => 'query', + 'endpoint' => 'Accounts', + ) + )->submit( array( 'query' => $query ) ); + + if ( is_wp_error( $response ) ) { + return $response; + } + + $account_id = $response['data']['result'][0]['id']; + if ( ! empty( $account_id ) ) { + $response = $bridge->patch( + array( + 'method' => 'update', + 'endpoint' => 'Accounts', + ) + )->submit( array_merge( array( 'id' => $account_id ), $account ) ); + + if ( is_wp_error( $response ) ) { + return $response; + } + + $payload['account_id'] = $account_id; + return $payload; + } + + $response = $bridge->patch( + array( + 'method' => 'create', + 'endpoint' => 'Accounts', + ) + )->submit( $account ); + + if ( is_wp_error( $response ) ) { + return $response; + } + + $payload['account_id'] = $response['data']['result']['id']; + return $payload; +} diff --git a/forms-bridge/addons/vtiger/jobs/contact.php b/forms-bridge/addons/vtiger/jobs/contact.php new file mode 100644 index 00000000..1f0f0632 --- /dev/null +++ b/forms-bridge/addons/vtiger/jobs/contact.php @@ -0,0 +1,305 @@ + __( 'Contact', 'forms-bridge' ), + 'description' => __( 'Creates a contact in Vtiger', 'forms-bridge' ), + 'method' => 'forms_bridge_vtiger_create_contact', + 'input' => array( + array( + 'name' => 'lastname', + 'schema' => array( 'type' => 'string' ), + 'required' => true, + ), + array( + 'name' => 'firstname', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'salutationtype', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'leadsource', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'birthday', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'assigned_user_id', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'phone', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'mobile', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'homephone', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'otherphone', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'fax', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'email', + 'schema' => array( 'type' => 'boolean' ), + ), + array( + 'name' => 'secondaryemail', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'contact_id', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'account_id', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'title', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'department', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'assistant', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'assistantphone', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'donotcall', + 'schema' => array( 'type' => 'boolean' ), + ), + array( + 'name' => 'emailoptout', + 'schema' => array( 'type' => 'boolean' ), + ), + array( + 'name' => 'reference', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'notify_owner', + 'schema' => array( 'type' => 'boolean' ), + ), + array( + 'name' => 'portal', + 'schema' => array( 'type' => 'boolean' ), + ), + array( + 'name' => 'mailingstreet', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'otherstreet', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'mailingcity', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'othercity', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'mailingstate', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'otherstate', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'mailingzip', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'otherzip', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'mailingcountry', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'othercountry', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'mailingpobox', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'otherpobox', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'imagename', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'description', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'starred', + 'schema' => array( 'type' => 'boolean' ), + ), + array( + 'name' => 'tags', + 'schema' => array( 'type' => 'string' ), + ), + ), + 'output' => array( + array( + 'name' => 'contact_id', + 'schema' => array( 'type' => 'string' ), + ), + ), +); + +/** + * Creates a new contact and add its ID to the payload. + * + * @param array $payload Bridge payload. + * @param Vtiger_Form_Bridge $bridge Bridge object. + * + * @return array + */ +function forms_bridge_vtiger_create_contact( $payload, $bridge ) { + $contact = array( + 'lastname' => $payload['lastname'], + ); + + $contact_fields = array( + 'firstname', + 'salutationtype', + 'assigned_user_id', + 'phone', + 'mobile', + 'account_id', + 'homephone', + 'leadsource', + 'otherphone', + 'title', + 'fax', + 'department', + 'birthday', + 'email', + 'contact_id', + 'assistant', + 'secondaryemail', + 'assistantphone', + 'donotcall', + 'emailoptout', + 'reference', + 'notify_owner', + 'portal', + 'mailingstreet', + 'otherstreet', + 'mailingcity', + 'othercity', + 'mailingzip', + 'otherzip', + 'mailingstate', + 'otherstate', + 'mailingcountry', + 'othercountry', + 'mailingpobox', + 'otherpobox', + 'imagename', + 'description', + 'starred', + 'tags', + ); + + foreach ( $contact_fields as $field ) { + if ( isset( $payload[ $field ] ) ) { + $contact[ $field ] = $payload[ $field ]; + } + } + + if ( isset( $contact['firstname'] ) ) { + $query = "SELECT id FROM Contacts WHERE firstname = '{$contact['firstname']}' AND lastname = '{$contact['lastname']}'"; + + if ( isset( $contact['email'] ) ) { + $query .= " OR email = '{$contact['email']}';"; + } else { + $query .= ';'; + } + } elseif ( isset( $contact['email'] ) ) { + $query = "SELECT id FROM Contacts WHERE email = '{$contact['email']}';"; + } + + if ( isset( $query ) ) { + $response = $bridge->patch( + array( + 'method' => 'query', + 'endpoint' => 'Contacts', + ) + )->submit( array( 'query' => $query ) ); + + if ( is_wp_error( $response ) ) { + return $response; + } + + $contact_id = $response['data']['result'][0]['id'] ?? null; + if ( ! empty( $contact_id ) ) { + $response = $bridge->patch( + array( + 'name' => '__vtiger-' . time(), + 'method' => 'update', + 'endpoint' => 'Contacts', + ) + )->submit( array_merge( array( 'id' => $contact_id ), $contact ) ); + + if ( is_wp_error( $response ) ) { + return $response; + } + + $payload['contact_id'] = $contact_id; + return $payload; + } + } + + $response = $bridge->patch( + array( + 'name' => '__vtiger-' . time(), + 'method' => 'create', + 'endpoint' => 'Contacts', + ) + )->submit( $contact ); + + if ( is_wp_error( $response ) ) { + return $response; + } + + $payload['contact_id'] = $response['data']['result']['id']; + return $payload; +} diff --git a/forms-bridge/addons/vtiger/jobs/event-date-time.php b/forms-bridge/addons/vtiger/jobs/event-date-time.php new file mode 100644 index 00000000..375846b0 --- /dev/null +++ b/forms-bridge/addons/vtiger/jobs/event-date-time.php @@ -0,0 +1,79 @@ + __( 'Event date and time', 'forms-bridge' ), + 'description' => __( 'Given a datetime and a duration, sets up the vtiger event dates', 'forms-bridge' ), + 'method' => 'forms_bridge_vtiger_event_date_and_time', + 'input' => array( + array( + 'name' => 'datetime', + 'schema' => array( 'type' => 'string' ), + 'required' => true, + ), + array( + 'name' => 'duration_hours', + 'schema' => array( 'type' => 'integer' ), + 'required' => true, + ), + array( + 'name' => 'duration_minutes', + 'schema' => array( 'type' => 'string' ), + 'required' => true, + ), + ), + 'output' => array( + array( + 'name' => 'date_start', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'time_start', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'due_date', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'time_end', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'duration_hours', + 'schema' => array( 'type' => 'integer' ), + ), + array( + 'name' => 'duration_minutes', + 'schema' => array( 'type' => 'string' ), + ), + ), +); + +/** + * Given a payload with a datetime and duration, sets up the vtiger event dates. + * + * @param array $payload Bridge payload. + * + * @return array + */ +function forms_bridge_vtiger_event_date_and_time( $payload ) { + $datetime = $payload['datetime']; + $time = strtotime( $datetime ); + $endtime = $time + $payload['duration_hours'] * 3600 + $payload['duration_minutes'] * 60; + + $payload['date_start'] = date( 'Y-m-d', $time ); + $payload['time_start'] = date( 'H:i:s', $time ); + $payload['due_date'] = date( 'Y-m-d', $endtime ); + $payload['time_end'] = date( 'H:i:s', $endtime ); + + return $payload; +} diff --git a/forms-bridge/addons/vtiger/jobs/skip-contact.php b/forms-bridge/addons/vtiger/jobs/skip-contact.php new file mode 100644 index 00000000..9a8b5324 --- /dev/null +++ b/forms-bridge/addons/vtiger/jobs/skip-contact.php @@ -0,0 +1,75 @@ + __( 'Skip contact', 'forms-bridge' ), + 'description' => __( 'Searches for existing contacts by name and skip duplications', 'forms-bridge' ), + 'method' => 'forms_bridge_vtiger_skip_contact', + 'input' => array( + array( + 'name' => 'lastname', + 'schema' => array( 'type' => 'string' ), + 'required' => true, + ), + array( + 'name' => 'firstname', + 'schema' => array( 'type' => 'string' ), + ), + ), + 'output' => array( + array( + 'name' => 'lastname', + 'schema' => array( 'type' => 'string' ), + ), + array( + 'name' => 'firstname', + 'schema' => array( 'type' => 'string' ), + ), + ), +); + +/** + * Look up existing contacts by name and skips bridge submission if found. + * + * @param array $payload Bridge payload. + * @param Vtiger_Form_Bridge $bridge Bridge object. + */ +function forms_bridge_vtiger_skip_contact( $payload, $bridge ) { + $query = "SELECT id FROM Contacts WHERE email = '{$payload['email']}'"; + + if ( isset( $payload['firstname'], $payload['lastname'] ) ) { + $query .= " OR firstname = '{$payload['firstname']}' AND lastname = '{$payload['lastname']}';"; + } + + $response = $bridge->patch( + array( + 'method' => 'query', + 'endpoint' => 'Contacts', + ) + )->submit( array( 'query' => $query ) ); + + if ( is_wp_error( $response ) ) { + return $response; + } + + $contact_id = $response['data']['result'][0]['id'] ?? null; + if ( ! empty( $contact_id ) ) { + $result = forms_bridge_vtiger_create_contact( $payload, $bridge ); + + if ( is_wp_error( $result ) ) { + return $result; + } + + return; + } + + return $payload; +} diff --git a/forms-bridge/addons/vtiger/templates/accounts.php b/forms-bridge/addons/vtiger/templates/accounts.php new file mode 100644 index 00000000..af8dc969 --- /dev/null +++ b/forms-bridge/addons/vtiger/templates/accounts.php @@ -0,0 +1,322 @@ + __( 'Accounts', 'forms-bridge' ), + 'description' => __( + 'Account form bridge template. The resulting bridge will convert form submissions into Vtiger accounts (organizations).', + 'forms-bridge' + ), + 'fields' => array( + array( + 'ref' => '#form', + 'name' => 'title', + 'default' => __( 'Accounts', 'forms-bridge' ), + ), + array( + 'ref' => '#bridge', + 'name' => 'endpoint', + 'value' => 'Contacts', + ), + array( + 'ref' => '#bridge/custom_fields[]', + 'name' => 'assigned_user_id', + 'label' => __( 'Assigned User', 'forms-bridge' ), + 'type' => 'select', + 'options' => array( + 'endpoint' => 'Users', + 'finger' => array( + 'value' => 'result[].id', + 'label' => 'result[].user_name', + ), + ), + ), + array( + 'ref' => '#bridge/custom_fields[]', + 'name' => 'accounttype', + 'label' => __( 'Account Type', 'forms-bridge' ), + 'type' => 'select', + 'options' => array( + array( + 'value' => 'Analyst', + 'label' => __( 'Analyst', 'forms-bridge' ), + ), + array( + 'value' => 'Competitor', + 'label' => __( 'Competitor', 'forms-bridge' ), + ), + array( + 'value' => 'Customer', + 'label' => __( 'Customer', 'forms-bridge' ), + ), + array( + 'value' => 'Integrator', + 'label' => __( 'Integrator', 'forms-bridge' ), + ), + array( + 'value' => 'Investor', + 'label' => __( 'Investor', 'forms-bridge' ), + ), + array( + 'value' => 'Partner', + 'label' => __( 'Partner', 'forms-bridge' ), + ), + array( + 'value' => 'Press', + 'label' => __( 'Press', 'forms-bridge' ), + ), + array( + 'value' => 'Prospect', + 'label' => __( 'Prospect', 'forms-bridge' ), + ), + array( + 'value' => 'Reseller', + 'label' => __( 'Reseller', 'forms-bridge' ), + ), + array( + 'value' => 'Other', + 'label' => __( 'Other', 'forms-bridge' ), + ), + ), + 'default' => 'Prospect', + ), + array( + 'ref' => '#bridge/custom_fields[]', + 'name' => 'industry', + 'label' => __( 'Industry', 'forms-bridge' ), + 'description' => __( + 'Industry sector', + 'forms-bridge' + ), + 'type' => 'select', + 'options' => array( + array( + 'value' => 'Apparel', + 'label' => __( 'Apparel', 'forms-bridge' ), + ), + array( + 'value' => 'Banking', + 'label' => __( 'Banking', 'forms-bridge' ), + ), + array( + 'value' => 'Biotechnology', + 'label' => __( 'Biotechnology', 'forms-bridge' ), + ), + array( + 'value' => 'Chemicals', + 'label' => __( 'Chemicals', 'forms-bridge' ), + ), + array( + 'value' => 'Communications', + 'label' => __( 'Communications', 'forms-bridge' ), + ), + array( + 'value' => 'Construction', + 'label' => __( 'Construction', 'forms-bridge' ), + ), + array( + 'value' => 'Consulting', + 'label' => __( 'Consulting', 'forms-bridge' ), + ), + array( + 'value' => 'Education', + 'label' => __( 'Education', 'forms-bridge' ), + ), + array( + 'value' => 'Electronics', + 'label' => __( 'Electronics', 'forms-bridge' ), + ), + array( + 'value' => 'Energy', + 'label' => __( 'Energy', 'forms-bridge' ), + ), + array( + 'value' => 'Engineering', + 'label' => __( 'Engineering', 'forms-bridge' ), + ), + array( + 'value' => 'Entertainment', + 'label' => __( 'Entertainment', 'forms-bridge' ), + ), + array( + 'value' => 'Environmental', + 'label' => __( 'Environmental', 'forms-bridge' ), + ), + array( + 'value' => 'Finance', + 'label' => __( 'Finance', 'forms-bridge' ), + ), + array( + 'value' => 'Food & Beverage', + 'label' => __( 'Food & Beverage', 'forms-bridge' ), + ), + array( + 'value' => 'Government', + 'label' => __( 'Government', 'forms-bridge' ), + ), + array( + 'value' => 'Healthcare', + 'label' => __( 'Healthcare', 'forms-bridge' ), + ), + array( + 'value' => 'Hospitality', + 'label' => __( 'Hospitality', 'forms-bridge' ), + ), + array( + 'value' => 'Insurance', + 'label' => __( 'Insurance', 'forms-bridge' ), + ), + array( + 'value' => 'Machinery', + 'label' => __( 'Machinery', 'forms-bridge' ), + ), + array( + 'value' => 'Manufacturing', + 'label' => __( 'Manufacturing', 'forms-bridge' ), + ), + array( + 'value' => 'Media', + 'label' => __( 'Media', 'forms-bridge' ), + ), + array( + 'value' => 'Not For Profit', + 'label' => __( 'Not For Profit', 'forms-bridge' ), + ), + array( + 'value' => 'Recreation', + 'label' => __( 'Recreation', 'forms-bridge' ), + ), + array( + 'value' => 'Retail', + 'label' => __( 'Retail', 'forms-bridge' ), + ), + array( + 'value' => 'Shipping', + 'label' => __( 'Shipping', 'forms-bridge' ), + ), + array( + 'value' => 'Technology', + 'label' => __( 'Technology', 'forms-bridge' ), + ), + array( + 'value' => 'Telecomunications', + 'label' => __( 'Telecomunications', 'forms-bridge' ), + ), + array( + 'value' => 'Transportation', + 'label' => __( 'Transportation', 'forms-bridge' ), + ), + array( + 'value' => 'Utilities', + 'label' => __( 'Utilities', 'forms-bridge' ), + ), + array( + 'value' => 'Other', + 'label' => __( 'Other', 'forms-bridge' ), + ), + ), + ), + ), + 'bridge' => array( + 'endpoint' => 'Contacts', + 'method' => 'create', + 'workflow' => array( 'account', 'skip-contact' ), + 'mutations' => array( + array( + array( + 'from' => 'email1', + 'to' => 'user_email', + 'cast' => 'copy', + ), + ), + array( + array( + 'from' => 'user_email', + 'to' => 'email', + 'cast' => 'string', + ), + ), + ), + ), + 'form' => array( + 'fields' => array( + array( + 'label' => __( 'First Name', 'forms-bridge' ), + 'name' => 'firstname', + 'type' => 'text', + 'required' => true, + ), + array( + 'label' => __( 'Last Name', 'forms-bridge' ), + 'name' => 'lastname', + 'type' => 'text', + 'required' => true, + ), + array( + 'label' => __( 'Company Name', 'forms-bridge' ), + 'name' => 'accountname', + 'type' => 'text', + 'required' => true, + ), + array( + 'label' => __( 'Email', 'forms-bridge' ), + 'name' => 'email1', + 'type' => 'email', + 'required' => true, + ), + array( + 'label' => __( 'Title', 'forms-bridge' ), + 'name' => 'title', + 'type' => 'text', + ), + array( + 'label' => __( 'Phone', 'forms-bridge' ), + 'name' => 'phone', + 'type' => 'tel', + ), + array( + 'label' => __( 'Website', 'forms-bridge' ), + 'name' => 'website', + 'type' => 'url', + ), + array( + 'label' => __( 'Address', 'forms-bridge' ), + 'name' => 'bill_street', + 'type' => 'text', + ), + array( + 'label' => __( 'City', 'forms-bridge' ), + 'name' => 'bill_city', + 'type' => 'text', + ), + array( + 'label' => __( 'Postal Code', 'forms-bridge' ), + 'name' => 'bill_code', + 'type' => 'text', + ), + array( + 'label' => __( 'State', 'forms-bridge' ), + 'name' => 'bill_state', + 'type' => 'text', + ), + array( + 'label' => __( 'Country', 'forms-bridge' ), + 'name' => 'bill_country', + 'type' => 'text', + ), + array( + 'label' => __( 'Description', 'forms-bridge' ), + 'name' => 'description', + 'type' => 'textarea', + ), + ), + ), +); diff --git a/forms-bridge/addons/vtiger/templates/contacts.php b/forms-bridge/addons/vtiger/templates/contacts.php new file mode 100644 index 00000000..275bed72 --- /dev/null +++ b/forms-bridge/addons/vtiger/templates/contacts.php @@ -0,0 +1,154 @@ + __( 'Contacts', 'forms-bridge' ), + 'description' => __( + 'Contact form bridge template. The resulting bridge will convert form submissions into Vtiger contacts.', + 'forms-bridge' + ), + 'fields' => array( + array( + 'ref' => '#form', + 'name' => 'title', + 'default' => __( 'Contacts', 'forms-bridge' ), + ), + array( + 'ref' => '#bridge', + 'name' => 'endpoint', + 'value' => 'Contacts', + ), + array( + 'ref' => '#bridge/custom_fields[]', + 'name' => 'assigned_user_id', + 'label' => __( 'Assigned User', 'forms-bridge' ), + 'type' => 'select', + 'options' => array( + 'endpoint' => 'Users', + 'finger' => array( + 'value' => 'result[].id', + 'label' => 'result[].user_name', + ), + ), + ), + array( + 'ref' => '#bridge/custom_fields[]', + 'name' => 'leadsource', + 'label' => __( 'Lead Source', 'forms-bridge' ), + 'type' => 'select', + 'options' => array( + array( + 'value' => 'Web Site', + 'label' => __( 'Web Site', 'forms-bridge' ), + ), + array( + 'value' => 'Cold Call', + 'label' => __( 'Cold Call', 'forms-bridge' ), + ), + array( + 'value' => 'Direct Mail', + 'label' => __( 'Direct Mail', 'forms-bridge' ), + ), + array( + 'value' => 'Existing Customer', + 'label' => __( 'Existing Customer', 'forms-bridge' ), + ), + array( + 'value' => 'Employee', + 'label' => __( 'Employee', 'forms-bridge' ), + ), + array( + 'value' => 'Partner', + 'label' => __( 'Partner', 'forms-bridge' ), + ), + array( + 'value' => 'Public Relations', + 'label' => __( 'Public Relations', 'forms-bridge' ), + ), + array( + 'value' => 'Word of Mouth', + 'label' => __( 'Word of Mouth', 'forms-bridge' ), + ), + array( + 'value' => 'Conference', + 'label' => __( 'Conference', 'forms-bridge' ), + ), + array( + 'value' => 'Other', + 'label' => __( 'Other', 'forms-bridge' ), + ), + ), + 'default' => 'Web Site', + ), + ), + 'bridge' => array( + 'endpoint' => 'Contacts', + 'method' => 'create', + 'workflow' => array( 'skip-contact' ), + ), + 'form' => array( + 'fields' => array( + array( + 'label' => __( 'First Name', 'forms-bridge' ), + 'name' => 'firstname', + 'type' => 'text', + 'required' => true, + ), + array( + 'label' => __( 'Last Name', 'forms-bridge' ), + 'name' => 'lastname', + 'type' => 'text', + 'required' => true, + ), + array( + 'label' => __( 'Email', 'forms-bridge' ), + 'name' => 'email', + 'type' => 'email', + 'required' => true, + ), + array( + 'label' => __( 'Phone', 'forms-bridge' ), + 'name' => 'phone', + 'type' => 'tel', + ), + array( + 'label' => __( 'Address', 'forms-bridge' ), + 'name' => 'mailingstreet', + 'type' => 'text', + ), + array( + 'label' => __( 'City', 'forms-bridge' ), + 'name' => 'mailingcity', + 'type' => 'text', + ), + array( + 'label' => __( 'Postal Code', 'forms-bridge' ), + 'name' => 'mailingzip', + 'type' => 'text', + ), + array( + 'label' => __( 'State', 'forms-bridge' ), + 'name' => 'mailingstate', + 'type' => 'text', + ), + array( + 'label' => __( 'Country', 'forms-bridge' ), + 'name' => 'mailingcountry', + 'type' => 'text', + ), + array( + 'label' => __( 'Description', 'forms-bridge' ), + 'name' => 'description', + 'type' => 'textarea', + ), + ), + ), +); diff --git a/forms-bridge/addons/vtiger/templates/leads.php b/forms-bridge/addons/vtiger/templates/leads.php new file mode 100644 index 00000000..0789408f --- /dev/null +++ b/forms-bridge/addons/vtiger/templates/leads.php @@ -0,0 +1,196 @@ + __( 'Leads', 'forms-bridge' ), + 'description' => __( + 'Lead capture form template. The resulting bridge will convert form submissions into Vtiger leads.', + 'forms-bridge' + ), + 'fields' => array( + array( + 'ref' => '#form', + 'name' => 'title', + 'default' => __( 'Leads', 'forms-bridge' ), + ), + array( + 'ref' => '#bridge', + 'name' => 'endpoint', + 'value' => 'Leads', + ), + array( + 'ref' => '#bridge/custom_fields[]', + 'name' => 'assigned_user_id', + 'label' => __( 'Assigned User', 'forms-bridge' ), + 'type' => 'select', + 'options' => array( + 'endpoint' => 'Users', + 'finger' => array( + 'value' => 'result[].id', + 'label' => 'result[].user_name', + ), + ), + ), + array( + 'ref' => '#bridge/custom_fields[]', + 'name' => 'leadstatus', + 'label' => __( 'Lead Status', 'forms-bridge' ), + 'type' => 'select', + 'options' => array( + array( + 'value' => 'Not Contacted', + 'label' => __( 'Not Contacted', 'forms-bridge' ), + ), + array( + 'value' => 'Contacted', + 'label' => __( 'Contacted', 'forms-bridge' ), + ), + array( + 'value' => 'Attempted to Contact', + 'label' => __( 'Attempted to Contact', 'forms-bridge' ), + ), + array( + 'value' => 'Contact in Future', + 'label' => __( 'Contact in Future', 'forms-bridge' ), + ), + array( + 'value' => 'Cold', + 'label' => __( 'Cold', 'forms-bridge' ), + ), + array( + 'value' => 'Warm', + 'label' => __( 'Warm', 'forms-bridge' ), + ), + array( + 'value' => 'Hot', + 'label' => __( 'Hot', 'forms-bridge' ), + ), + array( + 'value' => 'Lost Lead', + 'label' => __( 'Lost Lead', 'forms-bridge' ), + ), + array( + 'value' => 'Pre Qualified', + 'label' => __( 'Pre Qualified', 'forms-bridge' ), + ), + array( + 'value' => 'Qualified', + 'label' => __( 'Junk Lead', 'forms-bridge' ), + ), + array( + 'value' => 'Junk Lead', + 'label' => __( 'Junk Lead', 'forms-bridge' ), + ), + ), + 'default' => 'Not Contacted', + ), + array( + 'ref' => '#bridge/custom_fields[]', + 'name' => 'leadsource', + 'label' => __( 'Lead Source', 'forms-bridge' ), + 'type' => 'select', + 'options' => array( + array( + 'value' => 'Web Site', + 'label' => __( 'Web Site', 'forms-bridge' ), + ), + array( + 'value' => 'Cold Call', + 'label' => __( 'Cold Call', 'forms-bridge' ), + ), + array( + 'value' => 'Direct Mail', + 'label' => __( 'Direct Mail', 'forms-bridge' ), + ), + array( + 'value' => 'Existing Customer', + 'label' => __( 'Existing Customer', 'forms-bridge' ), + ), + array( + 'value' => 'Employee', + 'label' => __( 'Employee', 'forms-bridge' ), + ), + array( + 'value' => 'Partner', + 'label' => __( 'Partner', 'forms-bridge' ), + ), + array( + 'value' => 'Public Relations', + 'label' => __( 'Public Relations', 'forms-bridge' ), + ), + array( + 'value' => 'Word of Mouth', + 'label' => __( 'Word of Mouth', 'forms-bridge' ), + ), + array( + 'value' => 'Conference', + 'label' => __( 'Conference', 'forms-bridge' ), + ), + array( + 'value' => 'Other', + 'label' => __( 'Other', 'forms-bridge' ), + ), + ), + 'default' => 'Web Site', + ), + ), + 'bridge' => array( + 'endpoint' => 'Leads', + 'method' => 'create', + ), + 'form' => array( + 'fields' => array( + array( + 'label' => __( 'First Name', 'forms-bridge' ), + 'name' => 'firstname', + 'type' => 'text', + 'required' => true, + ), + array( + 'label' => __( 'Last Name', 'forms-bridge' ), + 'name' => 'lastname', + 'type' => 'text', + 'required' => true, + ), + array( + 'label' => __( 'Email', 'forms-bridge' ), + 'name' => 'email', + 'type' => 'email', + 'required' => true, + ), + array( + 'label' => __( 'Phone', 'forms-bridge' ), + 'name' => 'phone', + 'type' => 'tel', + ), + array( + 'label' => __( 'Company', 'forms-bridge' ), + 'name' => 'company', + 'type' => 'text', + ), + array( + 'label' => __( 'Designation', 'forms-bridge' ), + 'name' => 'designation', + 'type' => 'text', + ), + array( + 'label' => __( 'Website', 'forms-bridge' ), + 'name' => 'website', + 'type' => 'url', + ), + array( + 'label' => __( 'Message', 'forms-bridge' ), + 'name' => 'description', + 'type' => 'textarea', + ), + ), + ), +); diff --git a/forms-bridge/addons/vtiger/templates/meetings.php b/forms-bridge/addons/vtiger/templates/meetings.php new file mode 100644 index 00000000..0005ced4 --- /dev/null +++ b/forms-bridge/addons/vtiger/templates/meetings.php @@ -0,0 +1,389 @@ + __( 'Meetings', 'forms-bridge' ), + 'description' => __( + 'Meetings form bridge template. The resulting bridge will convert form submissions into Vtiger meetings.', + 'forms-bridge', + ), + 'fields' => array( + array( + 'ref' => '#form', + 'name' => 'title', + 'default' => __( 'Meetings', 'forms-bridge' ), + ), + array( + 'ref' => '#bridge', + 'name' => 'endpoint', + 'value' => 'Events', + ), + array( + 'ref' => '#bridge/custom_fields[]', + 'name' => 'duration_hours', + 'label' => __( 'Duration (Hours)', 'forms-bridge' ), + 'type' => 'number', + 'default' => 1, + 'required' => true, + ), + array( + 'ref' => '#bridge/custom_fields[]', + 'name' => 'duration_minutes', + 'label' => __( 'Duration (Minutes)', 'forms-bridge' ), + 'type' => 'select', + 'options' => array( + array( + 'value' => '0', + 'label' => '.00', + ), + array( + 'value' => '15', + 'label' => '.15', + ), + array( + 'value' => '30', + 'label' => '.30', + ), + array( + 'value' => '45', + 'label' => '.45', + ), + ), + 'default' => '00', + 'required' => true, + ), + array( + 'ref' => '#bridge/custom_fields[]', + 'name' => 'assigned_user_id', + 'label' => __( 'Assigned User', 'forms-bridge' ), + 'type' => 'select', + 'options' => array( + 'endpoint' => 'Users', + 'finger' => array( + 'value' => 'result[].id', + 'label' => 'result[].user_name', + ), + ), + 'required' => true, + ), + array( + 'ref' => '#bridge/custom_fields[]', + 'name' => 'leadsource', + 'label' => __( 'Lead Source', 'forms-bridge' ), + 'type' => 'select', + 'options' => array( + array( + 'value' => 'Web Site', + 'label' => __( 'Web Site', 'forms-bridge' ), + ), + array( + 'value' => 'Cold Call', + 'label' => __( 'Cold Call', 'forms-bridge' ), + ), + array( + 'value' => 'Direct Mail', + 'label' => __( 'Direct Mail', 'forms-bridge' ), + ), + array( + 'value' => 'Existing Customer', + 'label' => __( 'Existing Customer', 'forms-bridge' ), + ), + array( + 'value' => 'Employee', + 'label' => __( 'Employee', 'forms-bridge' ), + ), + array( + 'value' => 'Partner', + 'label' => __( 'Partner', 'forms-bridge' ), + ), + array( + 'value' => 'Public Relations', + 'label' => __( 'Public Relations', 'forms-bridge' ), + ), + array( + 'value' => 'Word of Mouth', + 'label' => __( 'Word of Mouth', 'forms-bridge' ), + ), + array( + 'value' => 'Conference', + 'label' => __( 'Conference', 'forms-bridge' ), + ), + array( + 'value' => 'Other', + 'label' => __( 'Other', 'forms-bridge' ), + ), + ), + 'default' => 'Web Site', + ), + ), + 'bridge' => array( + 'endpoint' => 'Events', + 'method' => 'create', + 'custom_fields' => array( + array( + 'name' => 'eventstatus', + 'value' => 'Planned', + ), + array( + 'name' => 'activitytype', + 'value' => 'Meeting', + ), + ), + 'workflow' => array( 'date-fields-to-date', 'event-date-time', 'contact' ), + 'mutations' => array( + array(), + array(), + array( + array( + 'from' => 'assigned_user_id', + 'to' => 'meeting_assigned_user_id', + 'cast' => 'copy', + ), + array( + 'from' => 'firstname', + 'to' => 'subject[0]', + 'cast' => 'copy', + ), + array( + 'from' => 'lastname', + 'to' => 'subject[1]', + 'cast' => 'copy', + ), + array( + 'from' => 'subject', + 'to' => 'subject', + 'cast' => 'concat', + ), + ), + array( + array( + 'from' => 'meeting_assigned_user_id', + 'to' => 'assigned_user_id', + 'cast' => 'string', + ), + array( + 'from' => 'event_description', + 'to' => 'description', + 'cast' => 'string', + ), + array( + 'from' => 'duration_hours', + 'to' => 'duration_hours', + 'cast' => 'integer', + ), + array( + 'from' => 'duration_minutes', + 'to' => 'duration_minutes', + 'cast' => 'integer', + ), + ), + ), + ), + 'form' => array( + 'title' => __( 'Meetings', 'forms-bridge' ), + 'fields' => array( + array( + 'label' => __( 'First Name', 'forms-bridge' ), + 'name' => 'firstname', + 'type' => 'text', + 'required' => true, + ), + array( + 'label' => __( 'Last Name', 'forms-bridge' ), + 'name' => 'lastname', + 'type' => 'text', + 'required' => true, + ), + array( + 'label' => __( 'Email', 'forms-bridge' ), + 'name' => 'email', + 'type' => 'email', + 'required' => true, + ), + array( + 'label' => __( 'Phone', 'forms-bridge' ), + 'name' => 'phone', + 'type' => 'tel', + ), + array( + 'name' => 'date', + 'label' => __( 'Date', 'forms-bridge' ), + 'type' => 'date', + 'required' => true, + ), + array( + 'name' => 'hour', + 'label' => __( 'Hour', 'forms-bridge' ), + 'type' => 'select', + 'required' => true, + 'options' => array( + array( + 'label' => __( '1 AM', 'forms-bridge' ), + 'value' => '01', + ), + array( + 'label' => __( '2 AM', 'forms-bridge' ), + 'value' => '02', + ), + array( + 'label' => __( '3 AM', 'forms-bridge' ), + 'value' => '03', + ), + array( + 'label' => __( '4 AM', 'forms-bridge' ), + 'value' => '04', + ), + array( + 'label' => __( '5 AM', 'forms-bridge' ), + 'value' => '05', + ), + array( + 'label' => __( '6 AM', 'forms-bridge' ), + 'value' => '06', + ), + array( + 'label' => __( '7 AM', 'forms-bridge' ), + 'value' => '07', + ), + array( + 'label' => __( '8 AM', 'forms-bridge' ), + 'value' => '08', + ), + array( + 'label' => __( '9 AM', 'forms-bridge' ), + 'value' => '09', + ), + array( + 'label' => __( '10 AM', 'forms-bridge' ), + 'value' => '10', + ), + array( + 'label' => __( '11 AM', 'forms-bridge' ), + 'value' => '11', + ), + array( + 'label' => __( '12 AM', 'forms-bridge' ), + 'value' => '12', + ), + array( + 'label' => __( '1 PM', 'forms-bridge' ), + 'value' => '13', + ), + array( + 'label' => __( '2 PM', 'forms-bridge' ), + 'value' => '14', + ), + array( + 'label' => __( '3 PM', 'forms-bridge' ), + 'value' => '15', + ), + array( + 'label' => __( '4 PM', 'forms-bridge' ), + 'value' => '16', + ), + array( + 'label' => __( '5 PM', 'forms-bridge' ), + 'value' => '17', + ), + array( + 'label' => __( '6 PM', 'forms-bridge' ), + 'value' => '18', + ), + array( + 'label' => __( '7 PM', 'forms-bridge' ), + 'value' => '19', + ), + array( + 'label' => __( '8 PM', 'forms-bridge' ), + 'value' => '20', + ), + array( + 'label' => __( '9 PM', 'forms-bridge' ), + 'value' => '21', + ), + array( + 'label' => __( '10 PM', 'forms-bridge' ), + 'value' => '22', + ), + array( + 'label' => __( '11 PM', 'forms-bridge' ), + 'value' => '23', + ), + array( + 'label' => __( '12 PM', 'forms-bridge' ), + 'value' => '24', + ), + ), + ), + array( + 'name' => 'minute', + 'label' => __( 'Minute', 'forms-bridge' ), + 'type' => 'select', + 'required' => true, + 'options' => array( + array( + 'label' => '00', + 'value' => '00.0', + ), + array( + 'label' => '05', + 'value' => '05', + ), + array( + 'label' => '10', + 'value' => '10', + ), + array( + 'label' => '15', + 'value' => '15', + ), + array( + 'label' => '20', + 'value' => '20', + ), + array( + 'label' => '25', + 'value' => '25', + ), + array( + 'label' => '30', + 'value' => '30', + ), + array( + 'label' => '35', + 'value' => '35', + ), + array( + 'label' => '40', + 'value' => '40', + ), + array( + 'label' => '45', + 'value' => '45', + ), + array( + 'label' => '50', + 'value' => '50', + ), + array( + 'label' => '55', + 'value' => '55', + ), + ), + ), + array( + 'name' => 'event_description', + 'type' => 'textarea', + 'label' => __( 'Comments', 'forms-bridge' ), + ), + ), + ), +); diff --git a/forms-bridge/readme.txt b/forms-bridge/readme.txt index 2125bb08..03bd41c7 100644 --- a/forms-bridge/readme.txt +++ b/forms-bridge/readme.txt @@ -45,6 +45,7 @@ Forms Bridge has the following addons: * [Rocket.Chat](https://formsbridge.codeccoop.org/documentation/rocket-chat/) * [Slack](https://formsbridge.codeccoop.org/documentation/slack/) * [SuiteCRM](https://formsbridge.codeccoop.org/documentation/suitecrm/) +* [Vtiger](https://formsbridge.codeccoop.org/documentation/vtiger/) * [Zoho CRM](https://formsbridge.codeccoop.org/documentation/zoho-crm/) * [Zulip](https://formsbridge.codeccoop.org/documentation/zulip/) diff --git a/src/components/Templates/Wizard/Steps/FormStep.jsx b/src/components/Templates/Wizard/Steps/FormStep.jsx index ef1d5a37..8eece2b3 100644 --- a/src/components/Templates/Wizard/Steps/FormStep.jsx +++ b/src/components/Templates/Wizard/Steps/FormStep.jsx @@ -91,8 +91,7 @@ export default function FormStep({ fields, data, setData, integration }) { useEffect(() => { if (!form) return; - const data = { id: form.id }; - schema.fields.forEach((schema) => { + const data = schema.fields.reduce((data, schema) => { switch (schema.type) { case "object": data[schema.name] = []; @@ -103,8 +102,12 @@ export default function FormStep({ fields, data, setData, integration }) { default: data[schema.name] = ""; } - }); + return data; + }, {}); + + data.id = form.id; + data.title = form.title; setData(data); }, [form]); diff --git a/tests/addons/test-odoo.php b/tests/addons/test-odoo.php index 2b4c816e..d433dd38 100644 --- a/tests/addons/test-odoo.php +++ b/tests/addons/test-odoo.php @@ -250,19 +250,25 @@ private static function get_mock_response_data( $method, $params ) { array( 'result' => array( 'id' => array( - 'name' => 'id', - 'string' => 'ID', - 'type' => 'integer', + 'name' => 'id', + 'string' => 'ID', + 'type' => 'integer', + 'required' => false, + 'readonly' => false, ), 'name' => array( - 'name' => 'name', - 'string' => 'Name', - 'type' => 'char', + 'name' => 'name', + 'string' => 'Name', + 'type' => 'char', + 'required' => true, + 'readonly' => false, ), 'email' => array( - 'name' => 'email', - 'string' => 'Email', - 'type' => 'char', + 'name' => 'email', + 'string' => 'Email', + 'type' => 'char', + 'required' => false, + 'readonly' => false, ), ), ), diff --git a/tests/addons/test-suitecrm.php b/tests/addons/test-suitecrm.php index dd841068..1cadbb58 100644 --- a/tests/addons/test-suitecrm.php +++ b/tests/addons/test-suitecrm.php @@ -565,56 +565,6 @@ public function test_addon_get_endpoint_schema() { $this->assertContains( 'last_name', $field_names ); } - /** - * Test that templates exist and are valid. - */ - public function test_templates_exist() { - $templates_dir = dirname( dirname( __DIR__ ) ) . '/forms-bridge/addons/suitecrm/templates/'; - - $this->assertFileExists( $templates_dir . 'contacts.php' ); - $this->assertFileExists( $templates_dir . 'leads.php' ); - $this->assertFileExists( $templates_dir . 'accounts.php' ); - } - - /** - * Test contacts template structure. - */ - public function test_contacts_template_structure() { - $template = include dirname( dirname( __DIR__ ) ) . '/forms-bridge/addons/suitecrm/templates/contacts.php'; - - $this->assertIsArray( $template ); - $this->assertArrayHasKey( 'title', $template ); - $this->assertArrayHasKey( 'description', $template ); - $this->assertArrayHasKey( 'fields', $template ); - $this->assertArrayHasKey( 'bridge', $template ); - $this->assertArrayHasKey( 'form', $template ); - - $this->assertEquals( 'Contacts', $template['bridge']['endpoint'] ); - $this->assertEquals( 'set_entry', $template['bridge']['method'] ); - } - - /** - * Test leads template structure. - */ - public function test_leads_template_structure() { - $template = include dirname( dirname( __DIR__ ) ) . '/forms-bridge/addons/suitecrm/templates/leads.php'; - - $this->assertIsArray( $template ); - $this->assertEquals( 'Leads', $template['bridge']['endpoint'] ); - $this->assertEquals( 'set_entry', $template['bridge']['method'] ); - } - - /** - * Test accounts template structure. - */ - public function test_accounts_template_structure() { - $template = include dirname( dirname( __DIR__ ) ) . '/forms-bridge/addons/suitecrm/templates/accounts.php'; - - $this->assertIsArray( $template ); - $this->assertEquals( 'Contacts', $template['bridge']['endpoint'] ); - $this->assertEquals( 'set_entry', $template['bridge']['method'] ); - } - /** * Test bridge schema hook is applied. */ diff --git a/tests/addons/test-vtiger.php b/tests/addons/test-vtiger.php new file mode 100644 index 00000000..4ab3a6e4 --- /dev/null +++ b/tests/addons/test-vtiger.php @@ -0,0 +1,829 @@ + 'vtiger-test-credential', + 'schema' => 'Basic', + 'client_id' => 'admin', + 'client_secret' => 'accessKey123', + ) + ), + ); + } + + /** + * Test backend provider. + * + * @return Backend[] + */ + public static function backends_provider() { + return array( + new Backend( + array( + 'name' => 'vtiger-test-backend', + 'base_url' => 'https://vtiger.example.coop', + 'credential' => 'vtiger-test-credential', + 'headers' => array( + array( + 'name' => 'Content-Type', + 'value' => 'application/x-www-form-urlencoded', + ), + array( + 'name' => 'Accept', + 'value' => 'application/json', + ), + ), + ) + ), + ); + } + + /** + * HTTP requests interceptor. + * + * @param mixed $pre Initial pre hook value. + * @param array $args Request arguments. + * @param string $url Request URL. + * + * @return array + */ + public static function pre_http_request( $pre, $args, $url ) { + self::$request = array( + 'args' => $args, + 'url' => $url, + ); + + // Parse URL to determine the operation being called. + $parsed_url = wp_parse_url( $url ); + $query = array(); + if ( ! empty( $parsed_url['query'] ) ) { + parse_str( $parsed_url['query'], $query ); + } + + $operation = $query['operation'] ?? ''; + + // Check if this is a POST request (for create/update/delete operations). + $body = array(); + if ( ! empty( $args['body'] ) ) { + if ( is_string( $args['body'] ) ) { + parse_str( $args['body'], $body ); + } else { + $body = $args['body']; + } + $operation = $body['operation'] ?? $operation; + } + + // Return appropriate mock response based on operation. + if ( self::$mock_response ) { + $response_body = self::$mock_response; + } else { + $response_body = self::get_mock_response( $operation, $query, $body ); + } + + return array( + 'response' => array( + 'code' => 200, + 'message' => 'Success', + ), + 'headers' => array( 'Content-Type' => 'application/json' ), + 'cookies' => array(), + 'body' => wp_json_encode( $response_body ), + 'http_response' => null, + ); + } + + /** + * Get mock response based on API operation. + * + * @param string $operation API operation name. + * @param array $query Query parameters. + * @param array $body Request body. + * + * @return array Mock response. + */ + private static function get_mock_response( $operation, $query, $body ) { + switch ( $operation ) { + case 'getchallenge': + return array( + 'success' => true, + 'result' => array( + 'token' => 'test-challenge-token-12345', + 'serverTime' => time(), + 'expireTime' => time() + 300, + ), + ); + + case 'login': + return array( + 'success' => true, + 'result' => array( + 'sessionName' => 'test-session-id-67890', + 'userId' => '19x1', + 'version' => '7.4.0', + 'vtigerVersion' => '7.4.0', + ), + ); + + case 'listtypes': + return array( + 'success' => true, + 'result' => array( + 'types' => array( + 'Contacts', + 'Leads', + 'Accounts', + 'Potentials', + 'Calendar', + ), + 'information' => array( + 'Contacts' => array( + 'isEntity' => true, + 'label' => 'Contacts', + 'singular' => 'Contact', + ), + 'Leads' => array( + 'isEntity' => true, + 'label' => 'Leads', + 'singular' => 'Lead', + ), + 'Accounts' => array( + 'isEntity' => true, + 'label' => 'Accounts', + 'singular' => 'Account', + ), + 'Potentials' => array( + 'isEntity' => true, + 'label' => 'Potentials', + 'singular' => 'Potential', + ), + 'Calendar' => array( + 'isEntity' => true, + 'label' => 'Calendar', + 'singular' => 'Event', + ), + ), + ), + ); + + case 'describe': + return array( + 'success' => true, + 'result' => array( + 'label' => 'Contacts', + 'name' => 'Contacts', + 'createable' => true, + 'updateable' => true, + 'deleteable' => true, + 'retrieveable' => true, + 'fields' => array( + array( + 'name' => 'firstname', + 'label' => 'First Name', + 'mandatory' => false, + 'type' => array( + 'name' => 'string', + ), + 'nullable' => true, + 'editable' => true, + ), + array( + 'name' => 'lastname', + 'label' => 'Last Name', + 'mandatory' => true, + 'type' => array( + 'name' => 'string', + ), + 'nullable' => false, + 'editable' => true, + ), + array( + 'name' => 'email', + 'label' => 'Email', + 'mandatory' => false, + 'type' => array( + 'name' => 'email', + ), + 'nullable' => true, + 'editable' => true, + ), + array( + 'name' => 'phone', + 'label' => 'Office Phone', + 'mandatory' => false, + 'type' => array( + 'name' => 'phone', + ), + 'nullable' => true, + 'editable' => true, + ), + ), + ), + ); + + case 'query': + return array( + 'success' => true, + 'result' => array( + array( + 'id' => '4x11', + 'firstname' => 'John', + 'lastname' => 'Doe', + 'email' => 'john.doe@example.com', + ), + array( + 'id' => '4x12', + 'firstname' => 'Jane', + 'lastname' => 'Smith', + 'email' => 'jane.smith@example.com', + ), + ), + ); + + case 'retrieve': + return array( + 'success' => true, + 'result' => array( + 'id' => '4x11', + 'firstname' => 'John', + 'lastname' => 'Doe', + 'email' => 'john.doe@example.com', + 'phone' => '555-1234', + ), + ); + + case 'create': + $element = array(); + if ( ! empty( $body['element'] ) ) { + $element = json_decode( $body['element'], true ); + } + return array( + 'success' => true, + 'result' => array_merge( + array( + 'id' => '4x123', + 'assigned_user_id' => '19x1', + ), + $element + ), + ); + + case 'update': + $element = array(); + if ( ! empty( $body['element'] ) ) { + $element = json_decode( $body['element'], true ); + } + return array( + 'success' => true, + 'result' => $element, + ); + + case 'delete': + return array( + 'success' => true, + 'result' => array( + 'status' => 'successful', + ), + ); + + case 'sync': + return array( + 'success' => true, + 'result' => array( + 'updated' => array(), + 'deleted' => array(), + ), + ); + + default: + return array( + 'success' => true, + 'result' => array(), + ); + } + } + + /** + * Set up test fixtures. + */ + public function set_up() { + parent::set_up(); + + self::$request = null; + self::$mock_response = null; + + tests_add_filter( 'http_bridge_credentials', array( self::class, 'credentials_provider' ), 10, 0 ); + tests_add_filter( 'http_bridge_backends', array( self::class, 'backends_provider' ), 10, 0 ); + tests_add_filter( 'pre_http_request', array( self::class, 'pre_http_request' ), 10, 3 ); + } + + /** + * Tear down test filters. + */ + public function tear_down() { + remove_filter( 'http_bridge_credentials', array( self::class, 'credentials_provider' ), 10, 0 ); + remove_filter( 'http_bridge_backends', array( self::class, 'backends_provider' ), 10, 0 ); + remove_filter( 'pre_http_request', array( self::class, 'pre_http_request' ), 10, 3 ); + + parent::tear_down(); + } + + /** + * Test that the addon class exists and has correct constants. + */ + public function test_addon_class_exists() { + $this->assertTrue( class_exists( 'FORMS_BRIDGE\Vtiger_Addon' ) ); + $this->assertEquals( 'Vtiger', Vtiger_Addon::TITLE ); + $this->assertEquals( 'vtiger', Vtiger_Addon::NAME ); + $this->assertEquals( '\FORMS_BRIDGE\Vtiger_Form_Bridge', Vtiger_Addon::BRIDGE ); + } + + /** + * Test that the form bridge class exists. + */ + public function test_form_bridge_class_exists() { + $this->assertTrue( class_exists( 'FORMS_BRIDGE\Vtiger_Form_Bridge' ) ); + } + + /** + * Test bridge validation with valid data. + */ + public function test_bridge_validation() { + $bridge = new Vtiger_Form_Bridge( + array( + 'name' => 'test-vtiger-bridge', + 'backend' => 'vtiger-test-backend', + 'endpoint' => 'Contacts', + 'method' => 'create', + ) + ); + + $this->assertTrue( $bridge->is_valid ); + } + + /** + * Test bridge validation with invalid data. + */ + public function test_bridge_validation_invalid() { + $bridge = new Vtiger_Form_Bridge( + array( + 'name' => 'invalid-bridge', + // Missing required fields. + ) + ); + + $this->assertFalse( $bridge->is_valid ); + } + + /** + * Test successful challenge-response authentication flow. + */ + public function test_authentication_flow() { + $bridge = new Vtiger_Form_Bridge( + array( + 'name' => 'test-auth-bridge', + 'backend' => 'vtiger-test-backend', + 'endpoint' => 'Contacts', + 'method' => 'query', + ) + ); + + $response = $bridge->submit(); + + $this->assertFalse( is_wp_error( $response ) ); + + // Verify the request URL contains the Vtiger webservice endpoint. + $this->assertStringContainsString( '/webservice.php', self::$request['url'] ); + } + + /** + * Test listtypes operation. + */ + public function test_listtypes() { + $bridge = new Vtiger_Form_Bridge( + array( + 'name' => 'test-listtypes-bridge', + 'backend' => 'vtiger-test-backend', + 'endpoint' => '', + 'method' => 'listtypes', + ) + ); + + $response = $bridge->submit(); + + $this->assertFalse( is_wp_error( $response ) ); + $this->assertArrayHasKey( 'data', $response ); + $this->assertArrayHasKey( 'result', $response['data'] ); + $this->assertArrayHasKey( 'types', $response['data']['result'] ); + $this->assertContains( 'Contacts', $response['data']['result']['types'] ); + $this->assertContains( 'Leads', $response['data']['result']['types'] ); + $this->assertContains( 'Accounts', $response['data']['result']['types'] ); + } + + /** + * Test describe operation. + */ + public function test_describe() { + $bridge = new Vtiger_Form_Bridge( + array( + 'name' => 'test-describe-bridge', + 'backend' => 'vtiger-test-backend', + 'endpoint' => 'Contacts', + 'method' => 'describe', + ) + ); + + $response = $bridge->submit(); + + $this->assertFalse( is_wp_error( $response ) ); + $this->assertArrayHasKey( 'data', $response ); + $this->assertArrayHasKey( 'result', $response['data'] ); + $this->assertArrayHasKey( 'fields', $response['data']['result'] ); + $this->assertNotEmpty( $response['data']['result']['fields'] ); + } + + /** + * Test query operation. + */ + public function test_query() { + $bridge = new Vtiger_Form_Bridge( + array( + 'name' => 'test-query-bridge', + 'backend' => 'vtiger-test-backend', + 'endpoint' => 'Contacts', + 'method' => 'query', + ) + ); + + $response = $bridge->submit( + array( + 'query' => 'SELECT * FROM Contacts;', + ) + ); + + $this->assertFalse( is_wp_error( $response ) ); + $this->assertArrayHasKey( 'data', $response ); + $this->assertArrayHasKey( 'result', $response['data'] ); + $this->assertIsArray( $response['data']['result'] ); + $this->assertCount( 2, $response['data']['result'] ); + } + + /** + * Test retrieve operation. + */ + public function test_retrieve() { + $bridge = new Vtiger_Form_Bridge( + array( + 'name' => 'test-retrieve-bridge', + 'backend' => 'vtiger-test-backend', + 'endpoint' => 'Contacts', + 'method' => 'retrieve', + ) + ); + + $response = $bridge->submit( + array( + 'id' => '4x11', + ) + ); + + $this->assertFalse( is_wp_error( $response ) ); + $this->assertArrayHasKey( 'data', $response ); + $this->assertArrayHasKey( 'result', $response['data'] ); + $this->assertEquals( '4x11', $response['data']['result']['id'] ); + $this->assertEquals( 'John', $response['data']['result']['firstname'] ); + } + + /** + * Test create operation. + */ + public function test_create() { + $bridge = new Vtiger_Form_Bridge( + array( + 'name' => 'test-create-bridge', + 'backend' => 'vtiger-test-backend', + 'endpoint' => 'Contacts', + 'method' => 'create', + ) + ); + + $payload = array( + 'firstname' => 'John', + 'lastname' => 'Doe', + 'email' => 'john.doe@example.com', + ); + + $response = $bridge->submit( $payload ); + + $this->assertFalse( is_wp_error( $response ) ); + $this->assertArrayHasKey( 'data', $response ); + $this->assertArrayHasKey( 'result', $response['data'] ); + $this->assertArrayHasKey( 'id', $response['data']['result'] ); + $this->assertEquals( '4x123', $response['data']['result']['id'] ); + } + + /** + * Test update operation. + */ + public function test_update() { + $bridge = new Vtiger_Form_Bridge( + array( + 'name' => 'test-update-bridge', + 'backend' => 'vtiger-test-backend', + 'endpoint' => 'Contacts', + 'method' => 'update', + ) + ); + + $payload = array( + 'id' => '4x11', + 'firstname' => 'Jane', + 'lastname' => 'Doe', + ); + + $response = $bridge->submit( $payload ); + + $this->assertFalse( is_wp_error( $response ) ); + $this->assertArrayHasKey( 'data', $response ); + $this->assertArrayHasKey( 'result', $response['data'] ); + } + + /** + * Test delete operation. + */ + public function test_delete() { + $bridge = new Vtiger_Form_Bridge( + array( + 'name' => 'test-delete-bridge', + 'backend' => 'vtiger-test-backend', + 'endpoint' => 'Contacts', + 'method' => 'delete', + ) + ); + + $payload = array( + 'id' => '4x11', + ); + + $response = $bridge->submit( $payload ); + + $this->assertFalse( is_wp_error( $response ) ); + $this->assertArrayHasKey( 'data', $response ); + $this->assertTrue( $response['data']['success'] ); + } + + /** + * Test sync operation. + */ + public function test_sync() { + $bridge = new Vtiger_Form_Bridge( + array( + 'name' => 'test-sync-bridge', + 'backend' => 'vtiger-test-backend', + 'endpoint' => 'Contacts', + 'method' => 'sync', + ) + ); + + $response = $bridge->submit(); + + $this->assertFalse( is_wp_error( $response ) ); + $this->assertArrayHasKey( 'data', $response ); + $this->assertTrue( $response['data']['success'] ); + } + + /** + * Test error response handling. + */ + public function test_error_response_handling() { + self::$mock_response = array( + 'success' => false, + 'error' => array( + 'code' => 'INVALID_SESSIONID', + 'message' => 'Given sessionid is invalid', + ), + ); + + $bridge = new Vtiger_Form_Bridge( + array( + 'name' => 'test-error-bridge', + 'backend' => 'vtiger-test-backend', + 'endpoint' => 'Contacts', + 'method' => 'create', + ) + ); + + $response = $bridge->submit( array( 'firstname' => 'Test' ) ); + + $this->assertTrue( is_wp_error( $response ) ); + $this->assertStringContainsString( 'vtiger_', $response->get_error_code() ); + } + + /** + * Test addon ping method. + */ + public function test_addon_ping() { + $addon = Addon::addon( 'vtiger' ); + $response = $addon->ping( 'vtiger-test-backend' ); + + $this->assertTrue( $response ); + } + + /** + * Test addon get_endpoints method. + */ + public function test_addon_get_endpoints() { + Backend::temp_registration( + array( + 'name' => 'vtiger-test-backend', + 'base_url' => 'https://vtiger.example.coop', + 'credential' => 'vtiger-test-credential', + 'headers' => array(), + ) + ); + + $addon = Addon::addon( 'vtiger' ); + $endpoints = $addon->get_endpoints( 'vtiger-test-backend' ); + + $this->assertIsArray( $endpoints ); + $this->assertContains( 'Contacts', $endpoints ); + $this->assertContains( 'Leads', $endpoints ); + $this->assertContains( 'Accounts', $endpoints ); + $this->assertContains( 'Potentials', $endpoints ); + } + + /** + * Test addon get_endpoint_schema method. + */ + public function test_addon_get_endpoint_schema() { + Backend::temp_registration( + array( + 'name' => 'vtiger-test-backend', + 'base_url' => 'https://vtiger.example.coop', + 'credential' => 'vtiger-test-credential', + 'headers' => array(), + ) + ); + + $addon = Addon::addon( 'vtiger' ); + $schema = $addon->get_endpoint_schema( + 'Contacts', + 'vtiger-test-backend', + 'create' + ); + + $this->assertIsArray( $schema ); + $this->assertNotEmpty( $schema ); + + $field_names = array_column( $schema, 'name' ); + $this->assertContains( 'firstname', $field_names ); + $this->assertContains( 'lastname', $field_names ); + } + + /** + * Test bridge schema hook is applied. + */ + public function test_bridge_schema_hook() { + $schema = \FORMS_BRIDGE\Form_Bridge::schema( 'vtiger' ); + + $this->assertArrayHasKey( 'properties', $schema ); + $this->assertArrayHasKey( 'method', $schema['properties'] ); + + // Verify the method enum contains Vtiger-specific methods. + $this->assertContains( 'create', $schema['properties']['method']['enum'] ); + $this->assertContains( 'query', $schema['properties']['method']['enum'] ); + $this->assertContains( 'retrieve', $schema['properties']['method']['enum'] ); + $this->assertContains( 'update', $schema['properties']['method']['enum'] ); + $this->assertContains( 'delete', $schema['properties']['method']['enum'] ); + } + + /** + * Test MD5 access key hashing in login. + */ + public function test_access_key_md5_hashing() { + $bridge = new Vtiger_Form_Bridge( + array( + 'name' => 'test-md5-bridge', + 'backend' => 'vtiger-test-backend', + 'endpoint' => 'Contacts', + 'method' => 'query', + ) + ); + + $response = $bridge->submit(); + + // Check that a request was made. + $this->assertNotNull( self::$request ); + + // Verify the flow completed successfully. + $this->assertFalse( is_wp_error( $response ) ); + } + + /** + * Test invalid backend handling. + */ + public function test_invalid_backend() { + $bridge = new Vtiger_Form_Bridge( + array( + 'name' => 'test-invalid-backend-bridge', + 'backend' => 'non-existent-backend', + 'endpoint' => 'Contacts', + 'method' => 'create', + ) + ); + + $response = $bridge->submit( array( 'firstname' => 'Test' ) ); + + $this->assertTrue( is_wp_error( $response ) ); + $this->assertEquals( 'invalid_backend', $response->get_error_code() ); + } + + /** + * Test invalid credential handling. + */ + public function test_invalid_credential() { + Backend::temp_registration( + array( + 'name' => 'vtiger-no-cred-backend', + 'base_url' => 'https://vtiger.example.coop', + 'credential' => 'non-existent-credential', + 'headers' => array(), + ) + ); + + $bridge = new Vtiger_Form_Bridge( + array( + 'name' => 'test-invalid-credential-bridge', + 'backend' => 'vtiger-no-cred-backend', + 'endpoint' => 'Contacts', + 'method' => 'create', + ) + ); + + $response = $bridge->submit( array( 'firstname' => 'Test' ) ); + + $this->assertTrue( is_wp_error( $response ) ); + $this->assertEquals( 'invalid_credential', $response->get_error_code() ); + } + + /** + * Test that assigned_user_id is automatically set on create. + */ + public function test_auto_assigned_user_id() { + $bridge = new Vtiger_Form_Bridge( + array( + 'name' => 'test-auto-user-bridge', + 'backend' => 'vtiger-test-backend', + 'endpoint' => 'Contacts', + 'method' => 'create', + ) + ); + + $payload = array( + 'firstname' => 'John', + 'lastname' => 'Doe', + ); + + $response = $bridge->submit( $payload ); + + $this->assertFalse( is_wp_error( $response ) ); + $this->assertArrayHasKey( 'assigned_user_id', $response['data']['result'] ); + $this->assertEquals( '19x1', $response['data']['result']['assigned_user_id'] ); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 050ae5df..e331e707 100755 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -51,6 +51,14 @@ function ( $trigger, $function_name ) { require ABSPATH . 'wp-content/mu-plugins/wpforms/wpforms.php'; require ABSPATH . 'wp-content/mu-plugins/woocommerce/woocommerce.php'; + add_filter( + 'woocommerce_load_webhooks_limit', + function () { + return -1; + }, + 90, + ); + /* Plugin tests */ require dirname( __DIR__ ) . '/forms-bridge/deps/plugin/tests/bootstrap.php'; }