diff --git a/CHANGELOG.md b/CHANGELOG.md index a01d4860..1a3301f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file. This projects adheres to [Semantic Versioning](https://semver.org/) and [Keep a CHANGELOG](https://keepachangelog.com/). +## [5.6.0] + +### Added + +- Prevention of multiple form submissions from the same user until the API response is processed. + +### Changed + +- Decoupled Clearbit integration from sync submit and moved it to job queue. + ## [5.5.1] ### Added @@ -832,6 +842,7 @@ This projects adheres to [Semantic Versioning](https://semver.org/) and [Keep a - Initial production release. +[5.6.0]: https://github.com/infinum/eightshift-forms/compare/5.5.1...5.6.0 [5.5.1]: https://github.com/infinum/eightshift-forms/compare/5.5.0...5.5.1 [5.5.0]: https://github.com/infinum/eightshift-forms/compare/5.4.3...5.5.0 [5.4.3]: https://github.com/infinum/eightshift-forms/compare/5.4.2...5.4.3 diff --git a/eightshift-forms.php b/eightshift-forms.php index 26432797..d6451177 100644 --- a/eightshift-forms.php +++ b/eightshift-forms.php @@ -6,7 +6,7 @@ * Description: Eightshift Forms is a complete form builder plugin that utilizes modern Block editor features with multiple third-party integrations, bringing your project to a new level. * Author: WordPress team @Infinum * Author URI: https://eightshift.com/ - * Version: 5.5.1 + * Version: 5.6.0 * Text Domain: eightshift-forms * * @package EightshiftForms diff --git a/src/Activate.php b/src/Activate.php index 2fcb85da..32b9c919 100644 --- a/src/Activate.php +++ b/src/Activate.php @@ -10,6 +10,7 @@ namespace EightshiftForms; +use EightshiftForms\Db\CreateEntriesTable; use EightshiftForms\Permissions\Permissions; use EightshiftFormsVendor\EightshiftFormsUtils\Config\UtilsConfig; use EightshiftFormsVendor\EightshiftLibs\Plugin\HasActivationInterface; @@ -36,6 +37,9 @@ public function activate(): void } } + // Create DB table. + CreateEntriesTable::createTable(); + // Do a cleanup. \flush_rewrite_rules(); } diff --git a/src/Blocks/components/country/country-frontend-mandatory.scss b/src/Blocks/components/country/country-frontend-mandatory.scss index 2aa7b0b0..d1e7aac7 100644 --- a/src/Blocks/components/country/country-frontend-mandatory.scss +++ b/src/Blocks/components/country/country-frontend-mandatory.scss @@ -1,5 +1,291 @@ -@import './country-style'; +.choices__item { + $countries: ( + ac, + ad, + ae, + af, + ag, + ai, + al, + am, + ao, + aq, + ar, + as, + at, + au, + aw, + ax, + az, + ba, + bb, + bd, + be, + bf, + bg, + bh, + bi, + bj, + bl, + bm, + bn, + bo, + bq, + br, + bs, + bt, + bv, + bw, + by, + bz, + ca, + cc, + cd, + cf, + cg, + ch, + ci, + ck, + cl, + cm, + cn, + co, + cp, + cr, + cu, + cv, + cw, + cx, + cy, + cz, + de, + dg, + dj, + dk, + dm, + do, + dz, + ea, + ec, + ee, + eg, + eh, + er, + es, + et, + eu, + fi, + fj, + fk, + fm, + fo, + fr, + ga, + gb, + gd, + ge, + gf, + gg, + gh, + gi, + gl, + gm, + gn, + gp, + gq, + gr, + gs, + gt, + gu, + gw, + gy, + hk, + hm, + hn, + hr, + ht, + hu, + ic, + id, + ie, + il, + im, + in, + io, + iq, + ir, + is, + it, + je, + jm, + jo, + jp, + ke, + kg, + kh, + ki, + km, + kn, + kp, + kr, + kw, + ky, + kz, + la, + lb, + lc, + li, + lk, + lr, + ls, + lt, + lu, + lv, + ly, + ma, + mc, + md, + me, + mf, + mg, + mh, + mk, + ml, + mm, + mn, + mo, + mp, + mq, + mr, + ms, + mt, + mu, + mv, + mw, + mx, + my, + mz, + na, + nc, + ne, + nf, + ng, + ni, + nl, + no, + np, + nr, + nu, + nz, + om, + pa, + pe, + pf, + pg, + ph, + pk, + pl, + pm, + pn, + pr, + ps, + pt, + pw, + py, + qa, + re, + ro, + rs, + ru, + rw, + sa, + sb, + sc, + sd, + se, + sg, + sh, + si, + sj, + sk, + sl, + sm, + sn, + so, + sr, + ss, + st, + sv, + sx, + sy, + sz, + ta, + tc, + td, + tf, + tg, + th, + tj, + tk, + tl, + tm, + tn, + to, + tr, + tt, + tv, + tw, + tz, + ua, + ug, + um, + un, + us, + uy, + uz, + va, + vc, + ve, + vg, + vi, + vn, + vu, + wf, + ws, + xk, + ye, + yt, + za, + zm, + zw, + ); -.es-field--country { - @extend %country-choices-icons; + $width: 20px; + $height: 15px; + + &::before { + content: ''; + background-image: url('../public/flags.png'); + background-repeat: no-repeat; + inline-size: $width; + block-size: $height; + vertical-align: middle; + margin-inline-end: 10px; + display: inline-block; + } + + $i: 0; + + @each $country in $countries { + &[data-country-code="#{$country}"]::before { + background-position: -#{$i * $width} 0; + } + + $i: $i + 1; + } + + // If placeholder is used. + &:not([data-country-code])::before { + display: none !important; // stylelint-disable-line declaration-no-important + } } diff --git a/src/Blocks/components/country/country-style.scss b/src/Blocks/components/country/country-style.scss deleted file mode 100644 index fc71bd18..00000000 --- a/src/Blocks/components/country/country-style.scss +++ /dev/null @@ -1,295 +0,0 @@ -// stylelint-disable max-nesting-depth - -%country-choices-icons { - .choices__item { - $countries: ( - ac, - ad, - ae, - af, - ag, - ai, - al, - am, - ao, - aq, - ar, - as, - at, - au, - aw, - ax, - az, - ba, - bb, - bd, - be, - bf, - bg, - bh, - bi, - bj, - bl, - bm, - bn, - bo, - bq, - br, - bs, - bt, - bv, - bw, - by, - bz, - ca, - cc, - cd, - cf, - cg, - ch, - ci, - ck, - cl, - cm, - cn, - co, - cp, - cr, - cu, - cv, - cw, - cx, - cy, - cz, - de, - dg, - dj, - dk, - dm, - do, - dz, - ea, - ec, - ee, - eg, - eh, - er, - es, - et, - eu, - fi, - fj, - fk, - fm, - fo, - fr, - ga, - gb, - gd, - ge, - gf, - gg, - gh, - gi, - gl, - gm, - gn, - gp, - gq, - gr, - gs, - gt, - gu, - gw, - gy, - hk, - hm, - hn, - hr, - ht, - hu, - ic, - id, - ie, - il, - im, - in, - io, - iq, - ir, - is, - it, - je, - jm, - jo, - jp, - ke, - kg, - kh, - ki, - km, - kn, - kp, - kr, - kw, - ky, - kz, - la, - lb, - lc, - li, - lk, - lr, - ls, - lt, - lu, - lv, - ly, - ma, - mc, - md, - me, - mf, - mg, - mh, - mk, - ml, - mm, - mn, - mo, - mp, - mq, - mr, - ms, - mt, - mu, - mv, - mw, - mx, - my, - mz, - na, - nc, - ne, - nf, - ng, - ni, - nl, - no, - np, - nr, - nu, - nz, - om, - pa, - pe, - pf, - pg, - ph, - pk, - pl, - pm, - pn, - pr, - ps, - pt, - pw, - py, - qa, - re, - ro, - rs, - ru, - rw, - sa, - sb, - sc, - sd, - se, - sg, - sh, - si, - sj, - sk, - sl, - sm, - sn, - so, - sr, - ss, - st, - sv, - sx, - sy, - sz, - ta, - tc, - td, - tf, - tg, - th, - tj, - tk, - tl, - tm, - tn, - to, - tr, - tt, - tv, - tw, - tz, - ua, - ug, - um, - un, - us, - uy, - uz, - va, - vc, - ve, - vg, - vi, - vn, - vu, - wf, - ws, - xk, - ye, - yt, - za, - zm, - zw, - ); - - $width: 20px; - $height: 15px; - - &::before { - content: ''; - background-image: url('../public/flags.png'); - background-repeat: no-repeat; - inline-size: $width; - block-size: $height; - vertical-align: middle; - margin-inline-end: 10px; - display: inline-block; - } - - $i: 0; - - @each $country in $countries { - &[data-country-code="#{$country}"]::before { - background-position: -#{$i * $width} 0; - } - - $i: $i + 1; - } - - // If placeholder is used. - &:not([data-country-code])::before { - display: none !important; // stylelint-disable-line declaration-no-important - } - } -} diff --git a/src/Blocks/components/form/assets/form.js b/src/Blocks/components/form/assets/form.js index 5ae9b1d0..4762e7a2 100644 --- a/src/Blocks/components/form/assets/form.js +++ b/src/Blocks/components/form/assets/form.js @@ -273,6 +273,8 @@ export class Form { * @returns {void} */ formSubmit(formId, filter = {}) { + this.state.setStateFormIsProcessing(true, formId); + // Dispatch event. this.utils.dispatchFormEvent(formId, this.state.getStateEvent('beforeFormSubmit')); @@ -327,6 +329,8 @@ export class Form { } this.formSubmitAfter(formId, response); + + this.state.setStateFormIsProcessing(false, formId); }); this.FORM_DATA = new FormData(); @@ -341,6 +345,7 @@ export class Form { * @returns {void} */ formSubmitStep(formId, filter = {}) { + this.state.setStateFormIsProcessing(true, formId); this.setFormData(formId, filter); this.setFormDataStep(formId); @@ -371,6 +376,7 @@ export class Form { this.formSubmitBefore(formId, response); this.steps.formStepSubmit(formId, response); this.steps.formStepSubmitAfter(formId, response); + this.state.setStateFormIsProcessing(false, formId); }); this.FORM_DATA = new FormData(); @@ -1602,6 +1608,11 @@ export class Form { const formId = this.state.getFormIdByElement(event.target); + // Prevent multiple submits. + if (this.state.getStateFormIsProcessing(formId)) { + return; + } + if (this.state.getStateFormStepsIsUsed(formId)) { const button = event?.submitter; const field = this.state.getFormFieldElementByChild(button); diff --git a/src/Blocks/components/form/assets/state-init.js b/src/Blocks/components/form/assets/state-init.js index e234b76a..ef0df363 100644 --- a/src/Blocks/components/form/assets/state-init.js +++ b/src/Blocks/components/form/assets/state-init.js @@ -13,7 +13,8 @@ export const prefix = 'esForms'; // Enum object for all state items. export const StateEnum = { // State names. - ISLOADED: 'isloaded', + ISLOADED: 'isLoaded', + ISPROCESSING: 'isProcessing', ELEMENTS: 'elements', ELEMENTS_FIELDS: 'elementsFields', FORM: 'form', @@ -310,6 +311,7 @@ export function setStateFormInitial(formId) { setState([StateEnum.FORM, StateEnum.POST_ID],formElement?.getAttribute(getStateAttribute('postId')), formId); setState([StateEnum.FORM, StateEnum.ISLOADED], false, formId); + setState([StateEnum.FORM, StateEnum.ISPROCESSING], false, formId); setState([StateEnum.FORM, StateEnum.IS_ADMIN_SINGLE_SUBMIT], false, formId); setState([StateEnum.FORM, StateEnum.ELEMENT], formElement, formId); setState([StateEnum.FORM, StateEnum.TYPE], formElement?.getAttribute(getStateAttribute('formType')), formId); diff --git a/src/Blocks/components/form/assets/state.js b/src/Blocks/components/form/assets/state.js index 004916ec..6913e6fc 100644 --- a/src/Blocks/components/form/assets/state.js +++ b/src/Blocks/components/form/assets/state.js @@ -85,10 +85,16 @@ export class State { getStateFormLoader = (formId) => { return getState([StateEnum.FORM, StateEnum.LOADER], formId); }; + getStateFormIsProcessing = (formId) => { + return getState([StateEnum.FORM, StateEnum.ISPROCESSING], formId); + }; setStateFormIsLoaded = (value, formId) => { setState([StateEnum.FORM, StateEnum.ISLOADED], value, formId); }; + setStateFormIsProcessing = (value, formId) => { + setState([StateEnum.FORM, StateEnum.ISPROCESSING], value, formId); + }; setStateFormIsAdminSingleSubmit = (value, formId) => { setState([StateEnum.FORM, StateEnum.IS_ADMIN_SINGLE_SUBMIT], value, formId); }; diff --git a/src/Blocks/components/phone/phone-frontend-mandatory.scss b/src/Blocks/components/phone/phone-frontend-mandatory.scss index 2d475726..9dcd92af 100644 --- a/src/Blocks/components/phone/phone-frontend-mandatory.scss +++ b/src/Blocks/components/phone/phone-frontend-mandatory.scss @@ -1,9 +1,3 @@ -@import './../country/country-style'; - -.es-field--phone { - @extend %country-choices-icons; -} - // Disable phone picker. .es-form[data-phone-disable-picker='1'] { .es-field--phone { diff --git a/src/CronJobs/ClearbitJob.php b/src/CronJobs/ClearbitJob.php new file mode 100644 index 00000000..91e82736 --- /dev/null +++ b/src/CronJobs/ClearbitJob.php @@ -0,0 +1,172 @@ +formSubmitMailer = $formSubmitMailer; + $this->clearbitClient = $clearbitClient; + } + + /** + * Register all the hooks + * + * @return void + */ + public function register(): void + { + $use = \apply_filters(SettingsClearbit::FILTER_SETTINGS_GLOBAL_IS_VALID_NAME, false); + $useCron = UtilsSettingsHelper::isOptionCheckboxChecked(SettingsClearbit::SETTINGS_CLEARBIT_USE_JOBS_QUEUE_KEY, SettingsClearbit::SETTINGS_CLEARBIT_USE_JOBS_QUEUE_KEY); + + if (!$use || !$useCron) { + return; + } + + \add_action('admin_init', [$this, 'checkIfJobIsSet']); + \add_filter('cron_schedules', [$this, 'addJobToSchedule']); // phpcs:ignore WordPress.WP.CronInterval.CronSchedulesInterval + \add_action(self::JOB_NAME, [$this, 'getJobCallback']); + } + + /** + * Check if job is set and add it if not. + * + * @return void + */ + public function checkIfJobIsSet(): void + { + if (!\wp_next_scheduled(self::JOB_NAME)) { + \wp_schedule_event( + \time(), + 'esFormsEvery5Minutes', + self::JOB_NAME + ); + } + } + + /** + * Add job to schedule. + * + * @param array $schedules WP schedules list. + * + * @return array + */ + public function addJobToSchedule(array $schedules): array + { + $schedules['esFormsEvery5Minutes'] = [ + 'interval' => \MINUTE_IN_SECONDS * 5, + 'display' => \esc_html__('Every 5 minutes', 'eightshift-forms'), + ]; + + return $schedules; + } + + /** + * Run callback when event is triggered. + * + * @return void + */ + public function getJobCallback() + { + $jobs = UtilsSettingsHelper::getOptionValueGroup(SettingsClearbit::SETTINGS_CLEARBIT_JOBS_KEY); + + if (!$jobs) { + return; + } + + foreach ($jobs as $type => $job) { + foreach ($job as $formId => $emails) { + $isValid = \apply_filters(SettingsClearbit::FILTER_SETTINGS_IS_VALID_NAME, $formId, $type); + + if (!$isValid) { + continue; + } + + if (!$emails) { + continue; + } + + foreach ($emails as $key => $email) { + // Get Clearbit data. + $clearbitResponse = $this->clearbitClient->getApplication( + $email, + UtilsSettingsHelper::getOptionValueGroup(SettingsClearbit::SETTINGS_CLEARBIT_MAP_HUBSPOT_KEYS_KEY . '-' . $type), + (string) $formId + ); + + if ($clearbitResponse[UtilsConfig::IARD_CODE] >= UtilsConfig::API_RESPONSE_CODE_SUCCESS && $clearbitResponse[UtilsConfig::IARD_CODE] <= UtilsConfig::API_RESPONSE_CODE_SUCCESS_RANGE) { + if ($type === SettingsHubspot::SETTINGS_TYPE_KEY) { + \apply_filters( + UtilsHooksHelper::getFilterName(['integrations', $type, 'postContactProperty']), + [], + $clearbitResponse['email'] ?? '', + $clearbitResponse['data'] ?? [] + ); + } + } else { + // Send fallback email if error but ignore for unknown entry. + if ($clearbitResponse[UtilsConfig::IARD_CODE] !== UtilsConfig::API_RESPONSE_CODE_ERROR_MISSING) { + $formDetails[UtilsConfig::FD_RESPONSE_OUTPUT_DATA] = $clearbitResponse; + + $this->formSubmitMailer->sendFallbackIntegrationEmail($formDetails); + } + } + + unset($jobs[$type][$formId][$key]); + + \update_option(UtilsSettingsHelper::getOptionName(SettingsClearbit::SETTINGS_CLEARBIT_JOBS_KEY), $jobs); + } + } + } + } +} diff --git a/src/CronJobs/Security.php b/src/CronJobs/SecurityJob.php similarity index 92% rename from src/CronJobs/Security.php rename to src/CronJobs/SecurityJob.php index 3c2053be..0e36d0f0 100644 --- a/src/CronJobs/Security.php +++ b/src/CronJobs/SecurityJob.php @@ -1,7 +1,7 @@ [ 'data', @@ -226,6 +228,7 @@ private static function getPublicFilters(): array ], SettingsClearbit::SETTINGS_TYPE_KEY => [ 'map', + 'setQueue', ], SettingsActiveCampaign::SETTINGS_TYPE_KEY => [ 'data', @@ -336,6 +339,7 @@ private static function getPublicActions(): array 'twoToThreeGeneral', 'twoToThreeForms', 'twoToThreeLocale', + 'clearbit', ], ]; } diff --git a/src/Hooks/FiltersSettingsBuilder.php b/src/Hooks/FiltersSettingsBuilder.php index e09606f7..d5c59079 100644 --- a/src/Hooks/FiltersSettingsBuilder.php +++ b/src/Hooks/FiltersSettingsBuilder.php @@ -225,6 +225,7 @@ public function getSettingsFiltersData(): array 'type' => UtilsConfig::SETTINGS_INTERNAL_TYPE_INTEGRATION, 'integrationType' => UtilsConfig::INTEGRATION_TYPE_NO_BUILDER, 'use' => SettingsMailer::SETTINGS_MAILER_USE_KEY, + 'settingsForceShow' => true, 'emailTemplateTags' => [ // Empty string as we are not using it to match the value. 'mailerSuccessRedirectUrl' => '', @@ -248,6 +249,7 @@ public function getSettingsFiltersData(): array 'type' => UtilsConfig::SETTINGS_INTERNAL_TYPE_INTEGRATION, 'integrationType' => UtilsConfig::INTEGRATION_TYPE_DEFAULT, 'use' => SettingsMailchimp::SETTINGS_MAILCHIMP_USE_KEY, + 'settingsForceShow' => false, 'cache' => [ MailchimpClient::CACHE_MAILCHIMP_ITEMS_TRANSIENT_NAME, ], @@ -264,6 +266,7 @@ public function getSettingsFiltersData(): array 'type' => UtilsConfig::SETTINGS_INTERNAL_TYPE_INTEGRATION, 'integrationType' => UtilsConfig::INTEGRATION_TYPE_DEFAULT, 'use' => SettingsGreenhouse::SETTINGS_GREENHOUSE_USE_KEY, + 'settingsForceShow' => false, 'cache' => [ GreenhouseClient::CACHE_GREENHOUSE_ITEMS_TRANSIENT_NAME, ], @@ -281,6 +284,7 @@ public function getSettingsFiltersData(): array 'type' => UtilsConfig::SETTINGS_INTERNAL_TYPE_INTEGRATION, 'integrationType' => UtilsConfig::INTEGRATION_TYPE_DEFAULT, 'use' => SettingsHubspot::SETTINGS_HUBSPOT_USE_KEY, + 'settingsForceShow' => false, 'cache' => [ HubspotClient::CACHE_HUBSPOT_ITEMS_TRANSIENT_NAME, HubspotClient::CACHE_HUBSPOT_CONTACT_PROPERTIES_TRANSIENT_NAME, @@ -298,6 +302,7 @@ public function getSettingsFiltersData(): array 'type' => UtilsConfig::SETTINGS_INTERNAL_TYPE_INTEGRATION, 'integrationType' => UtilsConfig::INTEGRATION_TYPE_DEFAULT, 'use' => SettingsMailerlite::SETTINGS_MAILERLITE_USE_KEY, + 'settingsForceShow' => false, 'cache' => [ MailerliteClient::CACHE_MAILERLITE_ITEMS_TRANSIENT_NAME, ], @@ -314,6 +319,7 @@ public function getSettingsFiltersData(): array 'type' => UtilsConfig::SETTINGS_INTERNAL_TYPE_INTEGRATION, 'integrationType' => UtilsConfig::INTEGRATION_TYPE_DEFAULT, 'use' => SettingsGoodbits::SETTINGS_GOODBITS_USE_KEY, + 'settingsForceShow' => false, 'labels' => [ 'title' => \__('Goodbits', 'eightshift-forms'), 'desc' => \__('Goodbits integration settings.', 'eightshift-forms'), @@ -323,15 +329,11 @@ public function getSettingsFiltersData(): array ], SettingsClearbit::SETTINGS_TYPE_KEY => [ 'settingsGlobal' => SettingsClearbit::FILTER_SETTINGS_GLOBAL_NAME, + 'settings' => SettingsClearbit::FILTER_SETTINGS_NAME, 'fields' => Goodbits::FILTER_FORM_FIELDS_NAME, - 'integration' => [ - SettingsHubspot::SETTINGS_TYPE_KEY => [ - 'use' => SettingsHubspot::SETTINGS_HUBSPOT_USE_CLEARBIT_KEY, - 'map' => SettingsHubspot::SETTINGS_HUBSPOT_CLEARBIT_MAP_KEYS_KEY, - ], - ], 'type' => UtilsConfig::SETTINGS_INTERNAL_TYPE_INTEGRATION, 'use' => SettingsClearbit::SETTINGS_CLEARBIT_USE_KEY, + 'settingsForceShow' => true, 'labels' => [ 'title' => \__('Clearbit', 'eightshift-forms'), 'desc' => \__('Clearbit integration settings.', 'eightshift-forms'), @@ -345,6 +347,7 @@ public function getSettingsFiltersData(): array 'type' => UtilsConfig::SETTINGS_INTERNAL_TYPE_INTEGRATION, 'integrationType' => UtilsConfig::INTEGRATION_TYPE_COMPLEX, 'use' => SettingsActiveCampaign::SETTINGS_ACTIVE_CAMPAIGN_USE_KEY, + 'settingsForceShow' => false, 'cache' => [ ActiveCampaignClient::CACHE_ACTIVE_CAMPAIGN_ITEMS_TRANSIENT_NAME, ], @@ -361,6 +364,7 @@ public function getSettingsFiltersData(): array 'type' => UtilsConfig::SETTINGS_INTERNAL_TYPE_INTEGRATION, 'integrationType' => UtilsConfig::INTEGRATION_TYPE_DEFAULT, 'use' => SettingsAirtable::SETTINGS_AIRTABLE_USE_KEY, + 'settingsForceShow' => false, 'cache' => [ AirtableClient::CACHE_AIRTABLE_ITEMS_TRANSIENT_NAME, ], @@ -378,6 +382,7 @@ public function getSettingsFiltersData(): array 'type' => UtilsConfig::SETTINGS_INTERNAL_TYPE_INTEGRATION, 'integrationType' => UtilsConfig::INTEGRATION_TYPE_DEFAULT, 'use' => SettingsMoments::SETTINGS_MOMENTS_USE_KEY, + 'settingsForceShow' => false, 'cache' => [ MomentsClient::CACHE_MOMENTS_ITEMS_TRANSIENT_NAME, MomentsClient::CACHE_MOMENTS_TOKEN_TRANSIENT_NAME, @@ -395,6 +400,7 @@ public function getSettingsFiltersData(): array 'type' => UtilsConfig::SETTINGS_INTERNAL_TYPE_INTEGRATION, 'integrationType' => UtilsConfig::INTEGRATION_TYPE_DEFAULT, 'use' => SettingsWorkable::SETTINGS_WORKABLE_USE_KEY, + 'settingsForceShow' => false, 'cache' => [ WorkableClient::CACHE_WORKABLE_ITEMS_TRANSIENT_NAME, ], @@ -412,6 +418,7 @@ public function getSettingsFiltersData(): array 'type' => UtilsConfig::SETTINGS_INTERNAL_TYPE_INTEGRATION, 'integrationType' => UtilsConfig::INTEGRATION_TYPE_DEFAULT, 'use' => SettingsTalentlyft::SETTINGS_TALENTLYFT_USE_KEY, + 'settingsForceShow' => false, 'cache' => [ TalentlyftClient::CACHE_TALENTLYFT_ITEMS_TRANSIENT_NAME, ], @@ -428,6 +435,7 @@ public function getSettingsFiltersData(): array 'type' => UtilsConfig::SETTINGS_INTERNAL_TYPE_INTEGRATION, 'integrationType' => UtilsConfig::INTEGRATION_TYPE_NO_BUILDER, 'use' => SettingsJira::SETTINGS_JIRA_USE_KEY, + 'settingsForceShow' => false, 'cache' => [ JiraClient::CACHE_JIRA_PROJECTS_TRANSIENT_NAME, JiraClient::CACHE_JIRA_ISSUE_TYPE_TRANSIENT_NAME, @@ -450,6 +458,7 @@ public function getSettingsFiltersData(): array 'type' => UtilsConfig::SETTINGS_INTERNAL_TYPE_INTEGRATION, 'integrationType' => UtilsConfig::INTEGRATION_TYPE_NO_BUILDER, 'use' => SettingsCorvus::SETTINGS_CORVUS_USE_KEY, + 'settingsForceShow' => false, 'emailTemplateTags' => [], 'labels' => [ 'title' => \__('Corvus', 'eightshift-forms'), @@ -464,6 +473,7 @@ public function getSettingsFiltersData(): array 'type' => UtilsConfig::SETTINGS_INTERNAL_TYPE_INTEGRATION, 'integrationType' => UtilsConfig::INTEGRATION_TYPE_NO_BUILDER, 'use' => SettingsPaycek::SETTINGS_PAYCEK_USE_KEY, + 'settingsForceShow' => false, 'emailTemplateTags' => [], 'labels' => [ 'title' => \__('Paycek', 'eightshift-forms'), @@ -478,6 +488,7 @@ public function getSettingsFiltersData(): array 'type' => UtilsConfig::SETTINGS_INTERNAL_TYPE_INTEGRATION, 'integrationType' => UtilsConfig::INTEGRATION_TYPE_NO_BUILDER, 'use' => SettingsPipedrive::SETTINGS_PIPEDRIVE_USE_KEY, + 'settingsForceShow' => false, 'cache' => [ PipedriveClient::CACHE_PIPEDRIVE_PERSON_FIELDS_TRANSIENT_NAME, PipedriveClient::CACHE_PIPEDRIVE_LEADS_FIELDS_TRANSIENT_NAME, @@ -498,6 +509,7 @@ public function getSettingsFiltersData(): array 'type' => UtilsConfig::SETTINGS_INTERNAL_TYPE_INTEGRATION, 'integrationType' => UtilsConfig::INTEGRATION_TYPE_NO_BUILDER, 'use' => SettingsCalculator::SETTINGS_CALCULATOR_USE_KEY, + 'settingsForceShow' => false, 'labels' => [ 'title' => \__('Calculator', 'eightshift-forms'), 'desc' => \__('Calculator form type settings.', 'eightshift-forms'), diff --git a/src/Integrations/Clearbit/ClearbitClient.php b/src/Integrations/Clearbit/ClearbitClient.php index 9a59c2b4..eeb7644e 100644 --- a/src/Integrations/Clearbit/ClearbitClient.php +++ b/src/Integrations/Clearbit/ClearbitClient.php @@ -11,18 +11,19 @@ namespace EightshiftForms\Integrations\Clearbit; use EightshiftForms\Hooks\Variables; +use EightshiftForms\Integrations\Hubspot\SettingsHubspot; use EightshiftFormsVendor\EightshiftFormsUtils\Config\UtilsConfig; use EightshiftFormsVendor\EightshiftFormsUtils\Helpers\UtilsApiHelper; use EightshiftFormsVendor\EightshiftFormsUtils\Helpers\UtilsDeveloperHelper; +use EightshiftFormsVendor\EightshiftFormsUtils\Helpers\UtilsGeneralHelper; use EightshiftFormsVendor\EightshiftFormsUtils\Helpers\UtilsSettingsHelper; -use EightshiftFormsVendor\EightshiftFormsUtils\Helpers\UtilsHelper; use EightshiftFormsVendor\EightshiftFormsUtils\Helpers\UtilsHooksHelper; -use EightshiftFormsVendor\EightshiftLibs\Helpers\Helpers; +use EightshiftFormsVendor\EightshiftLibs\Services\ServiceInterface; /** * ClearbitClient integration class. */ -class ClearbitClient implements ClearbitClientInterface +class ClearbitClient implements ClearbitClientInterface, ServiceInterface { /** * Return Clearbit base url. @@ -31,23 +32,69 @@ class ClearbitClient implements ClearbitClientInterface */ private const BASE_URL = 'https://person-stream.clearbit.com/v2/'; + /** + * Register all the hooks + * + * @return void + */ + public function register(): void + { + \add_filter(UtilsHooksHelper::getFilterName(['integrations', SettingsClearbit::SETTINGS_TYPE_KEY, 'setQueue']), [$this, 'setQueue'], 10, 2); + } + + /** + * Set queue for Clearbit. + * + * @param bool $output Output status. + * @param array $formDetails Data passed from the `getFormDetailsApi` function. + * + * @return bool + */ + public function setQueue(bool $output, array $formDetails): bool + { + $formId = $formDetails[UtilsConfig::FD_FORM_ID] ?? ''; + $params = $formDetails[UtilsConfig::FD_PARAMS] ?? []; + $type = $formDetails[UtilsConfig::FD_TYPE] ?? ''; + + // Check if Hubspot is using Clearbit. + $use = \apply_filters(SettingsClearbit::FILTER_SETTINGS_IS_VALID_NAME, $formId, SettingsHubspot::SETTINGS_TYPE_KEY); + + if (!$use) { + return false; + } + + $email = UtilsGeneralHelper::getEmailParamsField($params); + + if (!$email) { + return false; + } + + $jobs = UtilsSettingsHelper::getOptionValueGroup(SettingsClearbit::SETTINGS_CLEARBIT_JOBS_KEY); + + $formJob = $jobs[$type][$formId] ?? []; + + if (isset(\array_flip($formJob)[$email])) { + return false; + } + + $jobs[$type][$formId][] = $email; + + return \update_option(UtilsSettingsHelper::getOptionName(SettingsClearbit::SETTINGS_CLEARBIT_JOBS_KEY), $jobs); + } + /** * API request to post application. * * @param string $email Email key to map in params. - * @param array $params Params array. - * @param array $mapData Map data from settings. - * @param string $itemId Item id to search. + * @param array $mapData Params array. * @param string $formId FormId value. * * @return array */ - public function getApplication(string $email, array $params, array $mapData, string $itemId, string $formId): array + public function getApplication(string $email, array $mapData, string $formId): array { $url = self::BASE_URL . "combined/find?email={$email}"; - $params = $this->prepareParamsOutput($params); - $response = \wp_remote_get( $url, [ @@ -60,9 +107,9 @@ public function getApplication(string $email, array $params, array $mapData, str SettingsClearbit::SETTINGS_TYPE_KEY, $response, $url, - $params, [], - $itemId, + [], + '', $formId ); @@ -117,39 +164,6 @@ public function getParams(): array return $output; } - /** - * Prepare params for api. - * - * @param array $params Params. - * - * @return array - */ - private function prepareParamsOutput(array $params = []): array - { - $output = []; - - $customFields = \array_flip(Helpers::flattenArray(UtilsHelper::getStateParams())); - - foreach ($params as $key => $param) { - // Remove unecesery fields. - if (isset($customFields[$key])) { - continue; - } - - if (!isset($param['value'])) { - continue; - } - - if (!isset($param['type']) || $param['type'] === 'hidden') { - continue; - } - - $output[$key] = $param; - } - - return $output; - } - /** * Prepare params * diff --git a/src/Integrations/Clearbit/ClearbitClientInterface.php b/src/Integrations/Clearbit/ClearbitClientInterface.php index eb9c3425..7c056e25 100644 --- a/src/Integrations/Clearbit/ClearbitClientInterface.php +++ b/src/Integrations/Clearbit/ClearbitClientInterface.php @@ -24,12 +24,10 @@ public function getParams(): array; * API request to post application. * * @param string $email Email key to map in params. - * @param array $params Params array. - * @param array $mapData Map data from settings. - * @param string $itemId Item id to search. + * @param array $mapData Params array. * @param string $formId FormId value. * * @return array */ - public function getApplication(string $email, array $params, array $mapData, string $itemId, string $formId): array; + public function getApplication(string $email, array $mapData, string $formId): array; } diff --git a/src/Integrations/Clearbit/SettingsClearbit.php b/src/Integrations/Clearbit/SettingsClearbit.php index abad144d..706cf359 100644 --- a/src/Integrations/Clearbit/SettingsClearbit.php +++ b/src/Integrations/Clearbit/SettingsClearbit.php @@ -13,15 +13,22 @@ use EightshiftFormsVendor\EightshiftFormsUtils\Helpers\UtilsSettingsOutputHelper; use EightshiftFormsVendor\EightshiftFormsUtils\Helpers\UtilsSettingsHelper; use EightshiftForms\Hooks\Variables; -use EightshiftFormsVendor\EightshiftFormsUtils\Config\UtilsConfig; +use EightshiftForms\Integrations\Hubspot\SettingsHubspot; +use EightshiftFormsVendor\EightshiftFormsUtils\Helpers\UtilsHooksHelper; use EightshiftFormsVendor\EightshiftFormsUtils\Settings\UtilsSettingGlobalInterface; +use EightshiftFormsVendor\EightshiftFormsUtils\Settings\UtilsSettingInterface; use EightshiftFormsVendor\EightshiftLibs\Services\ServiceInterface; /** * SettingsClearbit class. */ -class SettingsClearbit implements SettingsClearbitDataInterface, ServiceInterface, UtilsSettingGlobalInterface +class SettingsClearbit implements ServiceInterface, UtilsSettingGlobalInterface, UtilsSettingInterface { + /** + * Filter settings key. + */ + public const FILTER_SETTINGS_NAME = 'es_forms_settings_clearbit'; + /** * Filter global settings key. */ @@ -47,6 +54,11 @@ class SettingsClearbit implements SettingsClearbitDataInterface, ServiceInterfac */ public const SETTINGS_CLEARBIT_USE_KEY = 'clearbit-use'; + /** + * Clearbit Use jobs queue key. + */ + public const SETTINGS_CLEARBIT_USE_JOBS_QUEUE_KEY = 'clearbit-use-jobs-queue'; + /** * API Key. */ @@ -57,6 +69,21 @@ class SettingsClearbit implements SettingsClearbitDataInterface, ServiceInterfac */ public const SETTINGS_CLEARBIT_AVAILABLE_KEYS_KEY = 'clearbit-available-keys'; + /** + * Clearbit map keys key. + */ + public const SETTINGS_CLEARBIT_MAP_HUBSPOT_KEYS_KEY = 'clearbit-map-keys'; + + /** + * Use Clearbit settings key. + */ + public const SETTINGS_CLEARBIT_SETTINGS_USE_KEY = 'clearbit-settings-use'; + + /** + * Use Clearbit jobs key. + */ + public const SETTINGS_CLEARBIT_JOBS_KEY = 'clearbit-jobs'; + /** * Instance variable for Clearbit data. * @@ -81,6 +108,7 @@ public function __construct(ClearbitClientInterface $clearbitClient) */ public function register(): void { + \add_filter(self::FILTER_SETTINGS_NAME, [$this, 'getSettingsData']); \add_filter(self::FILTER_SETTINGS_GLOBAL_NAME, [$this, 'getSettingsGlobalData']); \add_filter(self::FILTER_SETTINGS_IS_VALID_NAME, [$this, 'isSettingsValid'], 10, 2); \add_filter(self::FILTER_SETTINGS_GLOBAL_IS_VALID_NAME, [$this, 'isSettingsGlobalValid']); @@ -96,25 +124,13 @@ public function register(): void */ public function isSettingsValid(string $formId, string $type): bool { - if (!$this->isSettingsGlobalValid()) { + if (!$this->isSettingsGlobalValid($type)) { return false; } - $typeItems = \apply_filters(UtilsConfig::FILTER_SETTINGS_DATA, [])[self::SETTINGS_TYPE_KEY]['integration']; + $use = UtilsSettingsHelper::isSettingCheckboxChecked(self::SETTINGS_CLEARBIT_SETTINGS_USE_KEY, self::SETTINGS_CLEARBIT_SETTINGS_USE_KEY, $formId); - if (!isset($typeItems[$type])) { - return false; - } - - $useClearbit = UtilsSettingsHelper::getSettingValue($typeItems[$type]['use'], $formId); - - if (empty($useClearbit)) { - return false; - } - - $mapSet = UtilsSettingsHelper::getOptionValueGroup($typeItems[$type]['map']); - - if (empty($mapSet)) { + if (!$use) { return false; } @@ -124,20 +140,71 @@ public function isSettingsValid(string $formId, string $type): bool /** * Determine if settings global are valid. * + * @param string $type Integration type. + * * @return boolean */ - public function isSettingsGlobalValid(): bool + public function isSettingsGlobalValid(string $type = ''): bool { $isUsed = UtilsSettingsHelper::isOptionCheckboxChecked(self::SETTINGS_CLEARBIT_USE_KEY, self::SETTINGS_CLEARBIT_USE_KEY); $apiKey = UtilsSettingsHelper::getSettingsDisabledOutputWithDebugFilter(Variables::getApiKeyClearbit(), self::SETTINGS_CLEARBIT_API_KEY_KEY)['value']; + $map = !empty($type) ? UtilsSettingsHelper::getOptionValueGroup(self::SETTINGS_CLEARBIT_MAP_HUBSPOT_KEYS_KEY . '-' . $type) : true; - if (!$isUsed || empty($apiKey)) { + if (!$isUsed || !$apiKey || !$map) { return false; } return true; } + /** + * Get Form settings data array + * + * @param string $formId Form Id. + * + * @return array> + */ + public function getSettingsData(string $formId): array + { + // Bailout if global config is not valid. + if (!$this->isSettingsGlobalValid()) { + return UtilsSettingsOutputHelper::getNoValidGlobalConfig(self::SETTINGS_TYPE_KEY); + } + + return [ + UtilsSettingsOutputHelper::getIntro(self::SETTINGS_TYPE_KEY), + [ + 'component' => 'tabs', + 'tabsContent' => [ + [ + 'component' => 'tab', + 'tabLabel' => \__('Options', 'eightshift-forms'), + 'tabContent' => [ + [ + 'component' => 'checkboxes', + 'checkboxesFieldLabel' => '', + 'checkboxesName' => UtilsSettingsHelper::getSettingName(self::SETTINGS_CLEARBIT_SETTINGS_USE_KEY), + 'checkboxesIsRequired' => false, + 'checkboxesContent' => [ + [ + 'component' => 'checkbox', + 'checkboxLabel' => \__('Use Clearbit integration', 'eightshift-forms'), + 'checkboxHelp' => \__('Use Clearbit integration to enrich your data on this form.', 'eightshift-forms'), + 'checkboxIsChecked' => UtilsSettingsHelper::isSettingCheckboxChecked(self::SETTINGS_CLEARBIT_SETTINGS_USE_KEY, self::SETTINGS_CLEARBIT_SETTINGS_USE_KEY, $formId), + 'checkboxValue' => self::SETTINGS_CLEARBIT_SETTINGS_USE_KEY, + 'checkboxSingleSubmit' => true, + 'checkboxAsToggle' => true, + 'checkboxAsToggleSize' => 'medium', + ], + ] + ], + ], + ], + ], + ], + ]; + } + /** * Get global settings array for building settings page. * @@ -167,31 +234,90 @@ public function getSettingsGlobalData(): array ), \__('API key', 'eightshift-forms'), ), - ], - ], - self::isSettingsGlobalValid() ? [ - 'component' => 'tab', - 'tabLabel' => \__('Available fields', 'eightshift-forms'), - 'tabContent' => [ + [ + 'component' => 'divider', + 'dividerExtraVSpacing' => true, + ], [ 'component' => 'checkboxes', - 'checkboxesFieldHideLabel' => true, - 'checkboxesName' => UtilsSettingsHelper::getOptionName(self::SETTINGS_CLEARBIT_AVAILABLE_KEYS_KEY), - 'checkboxesIsRequired' => true, - 'checkboxesContent' => \array_map( - function ($item) { - return [ - 'component' => 'checkbox', - 'checkboxLabel' => $item, - 'checkboxIsChecked' => UtilsSettingsHelper::isOptionCheckboxChecked($item, self::SETTINGS_CLEARBIT_AVAILABLE_KEYS_KEY), - 'checkboxValue' => $item, - ]; - }, - $this->clearbitClient->getParams() - ), + 'checkboxesFieldLabel' => '', + 'checkboxesName' => UtilsSettingsHelper::getSettingName(self::SETTINGS_CLEARBIT_USE_JOBS_QUEUE_KEY), + 'checkboxesIsRequired' => false, + 'checkboxesContent' => [ + [ + 'component' => 'checkbox', + 'checkboxLabel' => \__('Use jobs queue', 'eightshift-forms'), + 'checkboxHelp' => \__('Turn on your jobs queue to process Clearbit data using CRON.', 'eightshift-forms'), + 'checkboxIsChecked' => UtilsSettingsHelper::isOptionCheckboxChecked(self::SETTINGS_CLEARBIT_USE_JOBS_QUEUE_KEY, self::SETTINGS_CLEARBIT_USE_JOBS_QUEUE_KEY), + 'checkboxValue' => self::SETTINGS_CLEARBIT_USE_JOBS_QUEUE_KEY, + 'checkboxSingleSubmit' => true, + 'checkboxAsToggle' => true, + 'checkboxAsToggleSize' => 'medium', + ], + ] + ], + ], + ], + ...($this->isSettingsGlobalValid() ? [ + [ + 'component' => 'tab', + 'tabLabel' => \__('Available fields', 'eightshift-forms'), + 'tabContent' => [ + [ + 'component' => 'checkboxes', + 'checkboxesFieldHideLabel' => true, + 'checkboxesName' => UtilsSettingsHelper::getOptionName(self::SETTINGS_CLEARBIT_AVAILABLE_KEYS_KEY), + 'checkboxesIsRequired' => true, + 'checkboxesContent' => \array_map( + function ($item) { + return [ + 'component' => 'checkbox', + 'checkboxLabel' => $item, + 'checkboxIsChecked' => UtilsSettingsHelper::isOptionCheckboxChecked($item, self::SETTINGS_CLEARBIT_AVAILABLE_KEYS_KEY), + 'checkboxValue' => $item, + ]; + }, + $this->clearbitClient->getParams() + ), + ], ], ], - ] : [], + [ + 'component' => 'tab', + 'tabLabel' => \__('Queue jobs', 'eightshift-forms'), + 'tabContent' => [ + [ + 'component' => 'textarea', + 'textareaFieldLabel' => \__('Queue jobs', 'eightshift-forms'), + 'textareaFieldHelp' => \__('Emails in queue that are still not processed.', 'eightshift-forms'), + 'textareaIsReadOnly' => true, + 'textareaIsPreventSubmit' => true, + 'textareaName' => 'queue', + 'textareaValue' => \wp_json_encode(UtilsSettingsHelper::getOptionValueGroup(SettingsClearbit::SETTINGS_CLEARBIT_JOBS_KEY), \JSON_PRETTY_PRINT | \JSON_UNESCAPED_UNICODE), + 'textareaSize' => 'huge', + 'textareaLimitHeight' => true, + ], + ], + ], + (\apply_filters(SettingsHubspot::FILTER_SETTINGS_GLOBAL_IS_VALID_NAME, false) ? [ + 'component' => 'tab', + 'tabLabel' => \__('HubSpot', 'eightshift-forms'), + 'tabContent' => [ + [ + 'component' => 'intro', + 'introSubtitle' => \__('Map Clearbit fields to HubSpot properties.', 'eightshift-forms'), + ], + [ + 'component' => 'divider', + 'dividerExtraVSpacing' => true, + ], + $this->getSettingsGlobalMap( + \apply_filters(UtilsHooksHelper::getFilterName(['integrations', SettingsHubspot::SETTINGS_TYPE_KEY, 'getContactProperties']), []), + self::SETTINGS_CLEARBIT_MAP_HUBSPOT_KEYS_KEY . '-' . SettingsHubspot::SETTINGS_TYPE_KEY + ), + ], + ] : []), + ] : []), [ 'component' => 'tab', 'tabLabel' => \__('Help', 'eightshift-forms'), @@ -213,65 +339,16 @@ function ($item) { ]; } - /** - * Output array settings for form. - * - * @param string $formId Form ID. - * @param string $key Key for use toggle. - * - * @return array>|bool|string>>|string> - */ - public function getOutputClearbit(string $formId, string $key): array - { - $useClearbit = \apply_filters(SettingsClearbit::FILTER_SETTINGS_GLOBAL_IS_VALID_NAME, $formId); - - if (!$useClearbit) { - return []; - } - - $isUsed = UtilsSettingsHelper::isSettingCheckboxChecked($key, $key, $formId); - - $output = [ - [ - 'component' => 'checkboxes', - 'checkboxesFieldLabel' => '', - 'checkboxesName' => UtilsSettingsHelper::getSettingName($key), - 'checkboxesIsRequired' => false, - 'checkboxesContent' => [ - [ - 'component' => 'checkbox', - 'checkboxLabel' => \__('Clearbit integration', 'eightshift-forms'), - 'checkboxIsChecked' => $isUsed, - 'checkboxValue' => $key, - 'checkboxSingleSubmit' => true, - 'checkboxAsToggle' => true, - 'checkboxAsToggleSize' => 'medium', - ], - ] - ], - ]; - - return [ - 'component' => 'tab', - 'tabLabel' => \__('Clearbit', 'eightshift-forms'), - 'tabContent' => [ - ...$output, - ], - ]; - } - /** * Output array settings for form. * * @param array $properties Array of properties from integration. - * @param array $keys Array of keys to get data from. + * @param string $key Key for saving the settings. * * @return array>|string>>|bool|string>>|string>>|string> */ - public function getOutputGlobalClearbit(array $properties, array $keys): array + public function getSettingsGlobalMap(array $properties, string $key): array { - $mapKey = $keys['map'] ?? ''; - $isValid = $this->isSettingsGlobalValid(); if (!$isValid) { @@ -280,61 +357,51 @@ public function getOutputGlobalClearbit(array $properties, array $keys): array $clearbitAvailableKeys = UtilsSettingsHelper::getOptionCheckboxValues(SettingsClearbit::SETTINGS_CLEARBIT_AVAILABLE_KEYS_KEY); - $clearbitMapValue = UtilsSettingsHelper::getOptionValueGroup($mapKey); + $clearbitMapValue = UtilsSettingsHelper::getOptionValueGroup($key); + + if (!$clearbitAvailableKeys) { + return []; + } return [ - 'component' => 'tab', - 'tabLabel' => \__('Clearbit', 'eightshift-forms'), - 'tabContent' => [ - [ - 'component' => 'intro', - 'introSubtitle' => \__('Map Clearbit fields to HubSpot properties.', 'eightshift-forms'), - ], - [ - 'component' => 'divider', - 'dividerExtraVSpacing' => true, - ], - $clearbitAvailableKeys ? [ - 'component' => 'group', - 'groupName' => UtilsSettingsHelper::getOptionName($mapKey), - 'groupSaveOneField' => true, - 'groupStyle' => 'default-listing', - 'groupContent' => [ - [ - 'component' => 'field', - 'fieldLabel' => '' . \__('Clearbit field', 'eightshift-forms') . '', - 'fieldContent' => '' . \__('HubSpot property', 'eightshift-forms') . '', - 'fieldBeforeContent' => ' ', // "Em space" to pad it out a bit. - 'fieldIsFiftyFiftyHorizontal' => true, - ], - ...\array_map( - static function ($item) use ($clearbitMapValue, $properties) { - $selectedValue = $clearbitMapValue[$item] ?? ''; + 'component' => 'group', + 'groupName' => UtilsSettingsHelper::getOptionName($key), + 'groupSaveOneField' => true, + 'groupStyle' => 'default-listing', + 'groupContent' => [ + [ + 'component' => 'field', + 'fieldLabel' => '' . \__('Clearbit field', 'eightshift-forms') . '', + 'fieldContent' => '' . \__('HubSpot property', 'eightshift-forms') . '', + 'fieldBeforeContent' => ' ', // "Em space" to pad it out a bit. + 'fieldIsFiftyFiftyHorizontal' => true, + ], + ...\array_map( + static function ($item) use ($clearbitMapValue, $properties) { + $selectedValue = $clearbitMapValue[$item] ?? ''; + return [ + 'component' => 'select', + 'selectName' => $item, + 'selectFieldLabel' => '' . $item . '', + 'selectFieldBeforeContent' => '→', + 'selectFieldIsFiftyFiftyHorizontal' => true, + 'selectPlaceholder' => \__('Select option', 'eightshift-forms'), + 'selectContent' => \array_map( + static function ($option) use ($selectedValue) { return [ - 'component' => 'select', - 'selectName' => $item, - 'selectFieldLabel' => '' . $item . '', - 'selectFieldBeforeContent' => '→', - 'selectFieldIsFiftyFiftyHorizontal' => true, - 'selectPlaceholder' => \__('Select option', 'eightshift-forms'), - 'selectContent' => \array_map( - static function ($option) use ($selectedValue) { - return [ - 'component' => 'select-option', - 'selectOptionLabel' => $option, - 'selectOptionValue' => $option, - 'selectOptionIsSelected' => $selectedValue === $option, - ]; - }, - $properties - ), + 'component' => 'select-option', + 'selectOptionLabel' => $option, + 'selectOptionValue' => $option, + 'selectOptionIsSelected' => $selectedValue === $option, ]; }, - $clearbitAvailableKeys + $properties ), - ], - ] : [], - ], + ]; + }, + $clearbitAvailableKeys + ), + ], ]; } } diff --git a/src/Integrations/Clearbit/SettingsClearbitDataInterface.php b/src/Integrations/Clearbit/SettingsClearbitDataInterface.php deleted file mode 100644 index 9f75f721..00000000 --- a/src/Integrations/Clearbit/SettingsClearbitDataInterface.php +++ /dev/null @@ -1,37 +0,0 @@ ->|bool|string>>|string> - */ - public function getOutputClearbit(string $formId, string $key): array; - - /** - * Output array settings for form - global. - * - * @param array $properties Array of properties from integration. - * @param array $keys Array of keys to get data from. - * - * @return array>|string>>|bool|string>>|string>>|string> - */ - public function getOutputGlobalClearbit(array $properties, array $keys): array; -} diff --git a/src/Integrations/Hubspot/HubspotClient.php b/src/Integrations/Hubspot/HubspotClient.php index 5dfd3820..74098c69 100644 --- a/src/Integrations/Hubspot/HubspotClient.php +++ b/src/Integrations/Hubspot/HubspotClient.php @@ -24,11 +24,12 @@ use EightshiftFormsVendor\EightshiftFormsUtils\Helpers\UtilsHelper; use EightshiftFormsVendor\EightshiftFormsUtils\Helpers\UtilsHooksHelper; use EightshiftFormsVendor\EightshiftLibs\Helpers\Helpers; +use EightshiftFormsVendor\EightshiftLibs\Services\ServiceInterface; /** * HubspotClient integration class. */ -class HubspotClient implements HubspotClientInterface +class HubspotClient implements HubspotClientInterface, ServiceInterface { /** * Transient cache name for items. @@ -80,6 +81,17 @@ public function __construct( $this->security = $security; } + /** + * Register all the hooks + * + * @return void + */ + public function register(): void + { + \add_filter(UtilsHooksHelper::getFilterName(['integrations', SettingsHubspot::SETTINGS_TYPE_KEY, 'getContactProperties']), [$this, 'getContactProperties']); + \add_filter(UtilsHooksHelper::getFilterName(['integrations', SettingsHubspot::SETTINGS_TYPE_KEY, 'postContactProperty']), [$this, 'postContactProperty'], 10, 3); + } + /** * Return items. * @@ -310,12 +322,13 @@ public function postApplication(string $itemId, array $params, array $files, str /** * Post contact property to HubSpot. * + * @param array $output Output array. * @param string $email Email to connect data to. * @param array $params Params array. * * @return array */ - public function postContactProperty(string $email, array $params): array + public function postContactProperty(array $output, string $email, array $params): array { $properties = []; diff --git a/src/Integrations/Hubspot/HubspotClientInterface.php b/src/Integrations/Hubspot/HubspotClientInterface.php index 06347f53..43871825 100644 --- a/src/Integrations/Hubspot/HubspotClientInterface.php +++ b/src/Integrations/Hubspot/HubspotClientInterface.php @@ -25,10 +25,11 @@ public function getContactProperties(): array; /** * Post contact property to HubSpot. * + * @param array $output Output array. * @param string $email Email to connect data to. * @param array $params Params array. * * @return array */ - public function postContactProperty(string $email, array $params): array; + public function postContactProperty(array $output, string $email, array $params): array; } diff --git a/src/Integrations/Hubspot/SettingsHubspot.php b/src/Integrations/Hubspot/SettingsHubspot.php index 6a04e1fa..9c3015c8 100644 --- a/src/Integrations/Hubspot/SettingsHubspot.php +++ b/src/Integrations/Hubspot/SettingsHubspot.php @@ -12,7 +12,6 @@ use EightshiftFormsVendor\EightshiftFormsUtils\Helpers\UtilsSettingsHelper; use EightshiftForms\Hooks\Variables; -use EightshiftForms\Integrations\Clearbit\SettingsClearbitDataInterface; use EightshiftFormsVendor\EightshiftFormsUtils\Settings\UtilsSettingInterface; use EightshiftFormsVendor\EightshiftFormsUtils\Settings\UtilsSettingGlobalInterface; use EightshiftFormsVendor\EightshiftFormsUtils\Helpers\UtilsSettingsOutputHelper; @@ -35,6 +34,11 @@ class SettingsHubspot extends AbstractSettingsIntegrations implements UtilsSetti */ public const FILTER_SETTINGS_GLOBAL_NAME = 'es_forms_settings_global_hubspot'; + /** + * Filter settings global is Valid key. + */ + public const FILTER_SETTINGS_GLOBAL_IS_VALID_NAME = 'es_forms_settings_global_is_valid_hubspot'; + /** * Settings key. */ @@ -65,28 +69,11 @@ class SettingsHubspot extends AbstractSettingsIntegrations implements UtilsSetti */ public const SETTINGS_GLOBAL_HUBSPOT_UPLOAD_ALLOWED_TYPES_KEY = 'hubspot-global-upload-allowed-types'; - /** - * Use Clearbit Key. - */ - public const SETTINGS_HUBSPOT_USE_CLEARBIT_KEY = 'hubspot-use-clearbit'; - - /** - * Use Clearbit map keys Key. - */ - public const SETTINGS_HUBSPOT_CLEARBIT_MAP_KEYS_KEY = 'hubspot-clearbit-map-keys'; - /** * Skip integration. */ public const SETTINGS_HUBSPOT_SKIP_INTEGRATION_KEY = 'hubspot-skip-integration'; - /** - * Instance variable for Clearbit settings. - * - * @var SettingsClearbitDataInterface - */ - protected $settingsClearbit; - /** * Instance variable for Hubspot data. * @@ -104,16 +91,13 @@ class SettingsHubspot extends AbstractSettingsIntegrations implements UtilsSetti /** * Create a new instance. * - * @param SettingsClearbitDataInterface $settingsClearbit Inject Clearbit which holds Clearbit settings data. * @param HubspotClientInterface $hubspotClient Inject Hubspot which holds Hubspot connect data. * @param SettingsFallbackDataInterface $settingsFallback Inject Fallback which holds Fallback settings data. */ public function __construct( - SettingsClearbitDataInterface $settingsClearbit, HubspotClientInterface $hubspotClient, SettingsFallbackDataInterface $settingsFallback ) { - $this->settingsClearbit = $settingsClearbit; $this->hubspotClient = $hubspotClient; $this->settingsFallback = $settingsFallback; } @@ -127,6 +111,7 @@ public function register(): void { \add_filter(self::FILTER_SETTINGS_NAME, [$this, 'getSettingsData']); \add_filter(self::FILTER_SETTINGS_GLOBAL_NAME, [$this, 'getSettingsGlobalData']); + \add_filter(self::FILTER_SETTINGS_GLOBAL_IS_VALID_NAME, [$this, 'isSettingsGlobalValid']); } /** @@ -165,11 +150,38 @@ public function getSettingsData(string $formId): array [ 'component' => 'tabs', 'tabsContent' => [ - $this->getOutputFilemanager($formId), - $this->settingsClearbit->getOutputClearbit( - $formId, - self::SETTINGS_HUBSPOT_USE_CLEARBIT_KEY - ), + [ + 'component' => 'tab', + 'tabLabel' => \__('Options', 'eightshift-forms'), + 'tabContent' => [ + [ + 'component' => 'input', + 'inputName' => UtilsSettingsHelper::getSettingName(self::SETTINGS_HUBSPOT_FILEMANAGER_FOLDER_KEY), + 'inputPlaceholder' => HubspotClient::HUBSPOT_FILEMANAGER_DEFAULT_FOLDER_KEY, + 'inputFieldLabel' => \__('File uploads folder', 'eightshift-forms'), + 'inputFieldHelp' => \__('All of the uploaded files will land inside this folder in the HubSpot file manager.', 'eightshift-forms'), + 'inputType' => 'text', + 'inputValue' => UtilsSettingsHelper::getSettingValue(self::SETTINGS_HUBSPOT_FILEMANAGER_FOLDER_KEY, $formId), + ], + [ + 'component' => 'divider', + 'dividerExtraVSpacing' => true, + ], + [ + 'component' => 'input', + 'inputName' => UtilsSettingsHelper::getSettingName(self::SETTINGS_HUBSPOT_UPLOAD_ALLOWED_TYPES_KEY), + 'inputFieldLabel' => \__('Upload allowed file types', 'eightshift-forms'), + 'inputFieldHelp' => \sprintf( + // Translators: %s will be replaced with the link. + \__('Comma-separated list of file type identifiers (MIME types), e.g. pdf, jpg, txt.', 'eightshift-forms'), + 'https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types' + ), + 'inputPlaceholder' => UtilsSettingsHelper::getOptionValue(self::SETTINGS_GLOBAL_HUBSPOT_UPLOAD_ALLOWED_TYPES_KEY), + 'inputType' => 'text', + 'inputValue' => UtilsSettingsHelper::getSettingValue(self::SETTINGS_HUBSPOT_UPLOAD_ALLOWED_TYPES_KEY, $formId), + ], + ], + ], ], ], ]; @@ -261,13 +273,6 @@ public function getSettingsGlobalData(): array ], ], ], - $this->isSettingsGlobalValid() ? - $this->settingsClearbit->getOutputGlobalClearbit( - $this->hubspotClient->getContactProperties(), - [ - 'map' => self::SETTINGS_HUBSPOT_CLEARBIT_MAP_KEYS_KEY, - ] - ) : [], $this->settingsFallback->getOutputGlobalFallback(SettingsHubspot::SETTINGS_TYPE_KEY), [ 'component' => 'tab', @@ -291,47 +296,4 @@ public function getSettingsGlobalData(): array ], ]; } - - /** - * Output array - file manager. - * - * @param string $formId Form ID. - * - * @return array>|string> - */ - private function getOutputFilemanager(string $formId): array - { - return [ - 'component' => 'tab', - 'tabLabel' => \__('Options', 'eightshift-forms'), - 'tabContent' => [ - [ - 'component' => 'input', - 'inputName' => UtilsSettingsHelper::getSettingName(self::SETTINGS_HUBSPOT_FILEMANAGER_FOLDER_KEY), - 'inputPlaceholder' => HubspotClient::HUBSPOT_FILEMANAGER_DEFAULT_FOLDER_KEY, - 'inputFieldLabel' => \__('File uploads folder', 'eightshift-forms'), - 'inputFieldHelp' => \__('All of the uploaded files will land inside this folder in the HubSpot file manager.', 'eightshift-forms'), - 'inputType' => 'text', - 'inputValue' => UtilsSettingsHelper::getSettingValue(self::SETTINGS_HUBSPOT_FILEMANAGER_FOLDER_KEY, $formId), - ], - [ - 'component' => 'divider', - 'dividerExtraVSpacing' => true, - ], - [ - 'component' => 'input', - 'inputName' => UtilsSettingsHelper::getSettingName(self::SETTINGS_HUBSPOT_UPLOAD_ALLOWED_TYPES_KEY), - 'inputFieldLabel' => \__('Upload allowed file types', 'eightshift-forms'), - 'inputFieldHelp' => \sprintf( - // Translators: %s will be replaced with the link. - \__('Comma-separated list of file type identifiers (MIME types), e.g. pdf, jpg, txt.', 'eightshift-forms'), - 'https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types' - ), - 'inputPlaceholder' => UtilsSettingsHelper::getOptionValue(self::SETTINGS_GLOBAL_HUBSPOT_UPLOAD_ALLOWED_TYPES_KEY), - 'inputType' => 'text', - 'inputValue' => UtilsSettingsHelper::getSettingValue(self::SETTINGS_HUBSPOT_UPLOAD_ALLOWED_TYPES_KEY, $formId), - ], - ] - ]; - } } diff --git a/src/Migration/SettingsMigration.php b/src/Migration/SettingsMigration.php index b552715c..7c72d9d3 100644 --- a/src/Migration/SettingsMigration.php +++ b/src/Migration/SettingsMigration.php @@ -56,6 +56,11 @@ class SettingsMigration implements UtilsSettingGlobalInterface, ServiceInterface */ public const VERSION_2_3_LOCALE = '2-3-locale'; + /** + * Version 5.5.1-5.6 Clearbit key. + */ + public const VERSION_CLEARBIT = 'clearbit'; + /** * Register all the hooks * @@ -106,6 +111,26 @@ public function getSettingsGlobalData(): array 'component' => 'layout', 'layoutType' => 'layout-v-stack-card', 'layoutContent' => [ + [ + 'component' => 'card-inline', + 'cardInlineTitle' => \__('Version 5.5.1 → Version 5.6 - Clearbit', 'eightshift-forms'), + 'cardInlineSubTitle' => \__('Breaking changes for Clearbit integration.', 'eightshift-forms'), + 'cardInlineRightContent' => [ + [ + 'component' => 'submit', + 'submitValue' => \__('Migrate', 'eightshift-forms'), + 'submitVariant' => 'ghost', + 'submitAttrs' => [ + UtilsHelper::getStateAttribute('migrationType') => self::VERSION_CLEARBIT, + ], + 'additionalClass' => UtilsHelper::getStateSelectorAdmin('migration'), + ], + ], + ], + [ + 'component' => 'divider', + 'dividerExtraVSpacing' => true, + ], [ 'component' => 'card-inline', 'cardInlineTitle' => \__('Version 2 → Version 3 - locale', 'eightshift-forms'), diff --git a/src/Rest/Routes/Integrations/Hubspot/FormSubmitHubspotRoute.php b/src/Rest/Routes/Integrations/Hubspot/FormSubmitHubspotRoute.php index 7b1d0155..095549df 100644 --- a/src/Rest/Routes/Integrations/Hubspot/FormSubmitHubspotRoute.php +++ b/src/Rest/Routes/Integrations/Hubspot/FormSubmitHubspotRoute.php @@ -11,8 +11,6 @@ namespace EightshiftForms\Rest\Routes\Integrations\Hubspot; use EightshiftForms\Captcha\CaptchaInterface; -use EightshiftFormsVendor\EightshiftFormsUtils\Helpers\UtilsGeneralHelper; -use EightshiftFormsVendor\EightshiftFormsUtils\Helpers\UtilsSettingsHelper; use EightshiftForms\Integrations\Clearbit\ClearbitClientInterface; use EightshiftForms\Integrations\Clearbit\SettingsClearbit; use EightshiftForms\Integrations\Hubspot\HubspotClientInterface; @@ -24,6 +22,7 @@ use EightshiftForms\Validation\ValidationPatternsInterface; use EightshiftForms\Validation\ValidatorInterface; use EightshiftFormsVendor\EightshiftFormsUtils\Config\UtilsConfig; +use EightshiftFormsVendor\EightshiftFormsUtils\Helpers\UtilsHooksHelper; /** * Class FormSubmitHubspotRoute @@ -128,54 +127,10 @@ protected function submitAction(array $formDetails) */ protected function callIntegrationResponseSuccessCallback(array $formDetails, array $successAdditionalData): void { - $this->runClearbit($formDetails); - } - - /** - * Run Clearbit integration. - * - * @param array $formDetails Data passed from the `getFormDetailsApi` function. - * - * @return void - */ - private function runClearbit(array $formDetails): void - { - $itemId = $formDetails[UtilsConfig::FD_ITEM_ID] ?? ''; - $formId = $formDetails[UtilsConfig::FD_FORM_ID] ?? ''; - $params = $formDetails[UtilsConfig::FD_PARAMS] ?? []; - $response = $formDetails[UtilsConfig::FD_RESPONSE_OUTPUT_DATA] ?? []; - - // Check if Hubspot is using Clearbit. - $useClearbit = \apply_filters(SettingsClearbit::FILTER_SETTINGS_IS_VALID_NAME, $formId, SettingsHubspot::SETTINGS_TYPE_KEY); - - if (!$response[UtilsConfig::IARD_IS_DISABLED] && $useClearbit) { - $email = UtilsGeneralHelper::getEmailParamsField($params); - - if ($email) { - // Get Clearbit data. - $clearbitResponse = $this->clearbitClient->getApplication( - $email, - $params, - UtilsSettingsHelper::getOptionValueGroup(\apply_filters(UtilsConfig::FILTER_SETTINGS_DATA, [])[SettingsClearbit::SETTINGS_TYPE_KEY]['integration'][SettingsHubspot::SETTINGS_TYPE_KEY]['map'] ?? []), - $itemId, - $formId - ); - - // If Clearbit data is ok send data to Hubspot. - if ($clearbitResponse[UtilsConfig::IARD_CODE] >= UtilsConfig::API_RESPONSE_CODE_SUCCESS && $clearbitResponse[UtilsConfig::IARD_CODE] <= UtilsConfig::API_RESPONSE_CODE_SUCCESS_RANGE) { - $this->hubspotClient->postContactProperty( - $clearbitResponse['email'] ?? '', - $clearbitResponse['data'] ?? [] - ); - } else { - // Send fallback email if error but ignore for unknown entry. - if ($clearbitResponse[UtilsConfig::IARD_CODE] !== UtilsConfig::API_RESPONSE_CODE_ERROR_MISSING) { - $formDetails[UtilsConfig::FD_RESPONSE_OUTPUT_DATA] = $clearbitResponse; - - $this->getFormSubmitMailer()->sendFallbackIntegrationEmail($formDetails); - } - } - } - } + \apply_filters( + UtilsHooksHelper::getFilterName(['integrations', SettingsClearbit::SETTINGS_TYPE_KEY, 'setQueue']), + false, + $formDetails + ); } } diff --git a/src/Rest/Routes/Settings/MigrationRoute.php b/src/Rest/Routes/Settings/MigrationRoute.php index 5fd8c3ef..aff2e923 100644 --- a/src/Rest/Routes/Settings/MigrationRoute.php +++ b/src/Rest/Routes/Settings/MigrationRoute.php @@ -122,6 +122,8 @@ public function routeCallback(WP_REST_Request $request) return $this->getMigration2To3Forms(); case SettingsMigration::VERSION_2_3_LOCALE: return $this->getMigration2To3Locale(); + case SettingsMigration::VERSION_CLEARBIT: + return $this->getMigrationClearbit(); default: return UtilsApiHelper::getApiErrorPublicOutput( \__('Migration version type key was not provided or not valid.', 'eightshift-forms'), @@ -494,4 +496,90 @@ private function getMigration2To3Locale(): array $output ); } + + /** + * Migration version Clearbit + * + * @return array + */ + private function getMigrationClearbit(): array + { + global $wpdb; + + $output = [ + 'options' => [], + 'forms' => [], + ]; + + $theQuery = new WP_Query([ + 'post_type' => Forms::POST_TYPE_SLUG, + 'no_found_rows' => true, + 'update_post_term_cache' => false, + 'post_status' => 'any', + 'nopaging' => true, + 'posts_per_page' => 5000, // phpcs:ignore WordPress.WP.PostsPerPage.posts_per_page_posts_per_page + ]); + + $forms = $theQuery->posts; + \wp_reset_postdata(); + + if ($forms) { + foreach ($forms as $key => $form) { + $formId = (int) $form->ID; + + if (!$formId) { + continue; + } + + $settings = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching + $wpdb->prepare( + "SELECT meta_key name, meta_value as value + FROM $wpdb->postmeta + WHERE post_id=%d + AND meta_key = 'es-forms-hubspot-use-clearbit'", // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.LikeWildcardsInQuery + $form->ID + ) + ); + + foreach ($settings as $setting) { + $name = $setting->name ?? ''; + $value = $setting->value ?? ''; + + \add_post_meta($formId, 'es-forms-clearbit-settings-use', $value); + \delete_post_meta($formId, $name); + + $output['forms'][] = $formId; + } + } + } + + $options = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching + "SELECT option_name as name, option_value as value + FROM $wpdb->options + WHERE option_name = 'es-forms-hubspot-clearbit-map-keys'" + ); + + if ($options) { + foreach ($options as $option) { + $name = $option->name ?? ''; + $value = $option->value ?? ''; + + + \add_option('es-forms-clearbit-map-keys-hubspot', $value); + \delete_option($name); + + $output['options'][] = $name; + } + } + + $actionName = UtilsHooksHelper::getActionName(['migration', 'clearbit']); + if (\has_action($actionName)) { + \do_action($actionName, SettingsMigration::VERSION_CLEARBIT); + } + + return UtilsApiHelper::getApiSuccessPublicOutput( + \__('Migration version 5.5.1 to 5.6 Clearbit finished with success.', 'eightshift-forms'), + $output + ); + } } diff --git a/src/Settings/Settings/Settings.php b/src/Settings/Settings/Settings.php index a32dc83f..956fdf84 100644 --- a/src/Settings/Settings/Settings.php +++ b/src/Settings/Settings/Settings.php @@ -12,7 +12,6 @@ use EightshiftForms\Form\AbstractFormBuilder; use EightshiftFormsVendor\EightshiftFormsUtils\Helpers\UtilsGeneralHelper; -use EightshiftForms\Integrations\Mailer\SettingsMailer; use EightshiftFormsVendor\EightshiftFormsUtils\Config\UtilsConfig; use EightshiftFormsVendor\EightshiftFormsUtils\Helpers\UtilsSettingsHelper; use EightshiftFormsVendor\EightshiftFormsUtils\Helpers\UtilsHelper; @@ -47,10 +46,11 @@ public function getSettingsSidebar(string $formId = '', string $integrationTypeU } $type = $value['type']; + $settingsForceShow = $value['settingsForceShow'] ?? false; // Skip integration forms if they are not used in the Block editor. // Mailer should be available on all integrations because it can be used as a backup option. - if ($key !== SettingsMailer::SETTINGS_TYPE_KEY) { + if (!$settingsForceShow) { if ($formId && $type === UtilsConfig::SETTINGS_INTERNAL_TYPE_INTEGRATION && $key !== $integrationTypeUsed) { continue; }