diff --git a/CHANGELOG.md b/CHANGELOG.md index d754689c..53aebdf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ += 2.18.0 = + +* Added: Revised refresh process to be more permissible of failures that are not expired refresh token related. +* Updated: "Connect now" screen UI. +* Updated: details and visuals for the embed block. +* Updated: small visuals for WP 7.0. +* Updated: Extra error handling from empty API responses. +* Updated: internal code organization. +* Fixed: errors when deleting a form. +* Fixed: JS errors from CAPTCHA settings UI hiding, elsewhere in admin. + += 2.17.0 = + +* Added: Hide UI of non-selected Captcha services until selected for usage. +* Added: Details regarding list status in Constant Contact account, to our forms list. +* Added: Email status or address destination column to Forms list table. +* Added: Messaging regarding user accounts on connect screen if a non-production install. +* Fixed: Issues around website field type not saving to custom field +* Updated: Moved custom field cap to 50 to match allowed contact field limit. +* Updated: Minor UI details around accessibility, wording, capitalization, visual spacing. + = 2.16.0 = * Added: Cloudflare Turnstile support diff --git a/assets/images/form-example-connect.png b/assets/images/form-example-connect.png new file mode 100644 index 00000000..d99de54b Binary files /dev/null and b/assets/images/form-example-connect.png differ diff --git a/assets/js/ctct-plugin-admin/builder.js b/assets/js/ctct-plugin-admin/builder.js index 704e2972..0dc3b3c0 100644 --- a/assets/js/ctct-plugin-admin/builder.js +++ b/assets/js/ctct-plugin-admin/builder.js @@ -96,7 +96,7 @@ window.CTCTBuilder = {}; * Handles the beforeunload callback and display. * * @param e beforeunload event. - * @since NEXT + * @since 2.8.0 */ that.bindMessage = (e) => { e.preventDefault(); diff --git a/assets/js/ctct-plugin-admin/settings.js b/assets/js/ctct-plugin-admin/settings.js index a112203e..d159aeed 100644 --- a/assets/js/ctct-plugin-admin/settings.js +++ b/assets/js/ctct-plugin-admin/settings.js @@ -37,6 +37,9 @@ window.ctctsettings = {}; */ that.bindEvents = () => { const service = document.querySelector(that.cache.service); + if (null === service) { + return; + } const recaptcha = document.querySelector(that.cache.recaptcha); const hcaptcha = document.querySelector(that.cache.hcaptcha); const turnstile = document.querySelector(that.cache.turnstile); diff --git a/assets/sass/_admin-connect.scss b/assets/sass/_admin-connect.scss index e684fa4e..8ad0a098 100644 --- a/assets/sass/_admin-connect.scss +++ b/assets/sass/_admin-connect.scss @@ -41,7 +41,7 @@ text-align: left; } - &::before{ + &.connected::before { width: 46px; height: 46px; position: absolute; @@ -205,14 +205,117 @@ } } } - .ctct-connection-notice { - color: variables.$color-red; + + .ctct-connection-notice { + color: variables.$color-red; } } +.ctct-wrap { + margin: 35px auto 0; + padding: 35px 45px 0; -// Connection Details + max-width: 850px; + background-color: variables.$color-connect-background; + border-radius: 12px; + border: solid variables.$color-connect-border 1px; + box-shadow: 0 0 2px variables.$color-connect-text; + + .not-connected { + box-sizing: border-box; + + * { + box-sizing: border-box; + } + a { + color: variables.$color-connect-button-primary; + } + + + color: variables.$color-connect-text; + display: flex; + flex-direction: column; + justify-content: space-between; + gap: 20px; + + + @include mixins.wider-than('small') { + flex-direction: row; + } + + h2 { + color: variables.$color-connect-text; + font-size: 1.8rem; + line-height: 1.5; + margin-top: 0; + } + + .ctct-logo { + background: transparent url('../images/ctct_ripple.svg') no-repeat; + background-size: 75%; + height: 50px; + width: 50px; + } + .ctct-cta-left, + .ctct-cta-right { + flex: 2 1; + + .button.ctct-button { + border-radius: 8px; + color: variables.$color-connect-button-secondary; + font-size: 16px; + height: auto; + line-height: 1; + padding: 14px 30px; + transition: all 0.15s ease; + + &.button-blue { + background-color: variables.$color-connect-button-primary; + border: 1px solid color.adjust(variables.$color-connect-button-primary, $lightness: -10%); + color: variables.$color-white; + + &:hover, + &:focus { + color: variables.$color-white; + background-color: color.adjust(variables.$color-connect-button-primary, $lightness: 10%); + } + } + &.ctct-signup { + background: transparent; + border-color: variables.$color-connect-button-secondary; + color: variables.$color-connect-button-secondary; + } + } + } + + .ctct-cta-left { + flex: 2; + .cta-buttons { + display: flex; + gap: 10px; + flex-direction: column; + text-align: center; + @include mixins.wider-than('small') { + flex-direction: row; + } + @include mixins.wider-than('small') { + text-align: left; + } + } + } + + .ctct-cta-right { + flex: 1; + } + } + + .ctct-connection-notice { + color: variables.$color-red; + } +} + +// Connection Details .ctct-connected-wrap{ .ctct-connection-details{ @@ -287,27 +390,6 @@ } } - -// Connect to GA - -.ctct-connected-opt-in{ - display: none; - width: 100%; - text-align: left; - - .ctct-connect-ga-optin{ - display: flex; - flex-wrap: nowrap; - padding: 15px 0 0; - - .button{ - text-align: center; - margin: 0 10px 0 0; - width: auto; - } - } -} - .ctct-error { background: none !important; border: 1px solid variables.$color-red; diff --git a/assets/sass/_admin-forms.scss b/assets/sass/_admin-forms.scss index 8ff7de3a..f46215c5 100644 --- a/assets/sass/_admin-forms.scss +++ b/assets/sass/_admin-forms.scss @@ -300,3 +300,11 @@ body.post-type-ctct_forms #titlediv #title { } } } + + +[class*="version-7"] { + .cmb-repeatable-group .cmb-shift-rows .dashicons-arrow-down-alt2, + .cmb-repeatable-group .cmb-shift-rows .dashicons-arrow-up-alt2 { + margin-top: .4em; + } +} diff --git a/assets/sass/_variables.scss b/assets/sass/_variables.scss index a537f4aa..589eb0c2 100644 --- a/assets/sass/_variables.scss +++ b/assets/sass/_variables.scss @@ -40,6 +40,13 @@ $color-modal-text: #555; $color-light-silver: #747e88; $color-med-gray: #999; +// connect page +$color-connect-border: #DCE2EC; +$color-connect-background: #f7f9fd; +$color-connect-text: #3F4B63; +$color-connect-button-primary: #186DED; +$color-connect-button-secondary: #8D9BB6; + // box model $padding: 1em; $radius: 4px; diff --git a/composer.lock b/composer.lock index 6627317d..416092e3 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "861d640d549d8ca7e53929f32ed26edc", + "content-hash": "07ce2e68ed36d2e2f54b806c933e0d70", "packages": [ { "name": "cmb2/cmb2", @@ -536,16 +536,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.11.1", + "version": "1.13.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { @@ -553,11 +553,12 @@ }, "conflict": { "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3,<3.2.2" + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { "doctrine/collections": "^1.6.8", "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", @@ -583,7 +584,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" }, "funding": [ { @@ -591,24 +592,25 @@ "type": "tidelift" } ], - "time": "2023-03-08T13:26:56+00:00" + "time": "2025-08-01T08:46:24+00:00" }, { "name": "phar-io/manifest", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "ext-phar": "*", "ext-xmlwriter": "*", "phar-io/version": "^3.0.1", @@ -649,9 +651,15 @@ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.3" + "source": "https://github.com/phar-io/manifest/tree/2.0.4" }, - "time": "2021-07-20T11:28:43+00:00" + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" }, { "name": "phar-io/version", @@ -768,28 +776,28 @@ }, { "name": "phpcompatibility/phpcompatibility-paragonie", - "version": "1.3.2", + "version": "1.3.4", "source": { "type": "git", "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git", - "reference": "bba5a9dfec7fcfbd679cfaf611d86b4d3759da26" + "reference": "244d7b04fc4bc2117c15f5abe23eb933b5f02bbf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/bba5a9dfec7fcfbd679cfaf611d86b4d3759da26", - "reference": "bba5a9dfec7fcfbd679cfaf611d86b4d3759da26", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/244d7b04fc4bc2117c15f5abe23eb933b5f02bbf", + "reference": "244d7b04fc4bc2117c15f5abe23eb933b5f02bbf", "shasum": "" }, "require": { "phpcompatibility/php-compatibility": "^9.0" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", "paragonie/random_compat": "dev-master", "paragonie/sodium_compat": "dev-master" }, "suggest": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." }, "type": "phpcodesniffer-standard", @@ -819,33 +827,53 @@ ], "support": { "issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues", + "security": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/security/policy", "source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie" }, - "time": "2022-10-25T01:46:02+00:00" + "funding": [ + { + "url": "https://github.com/PHPCompatibility", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcompatibility", + "type": "thanks_dev" + } + ], + "time": "2025-09-19T17:43:28+00:00" }, { "name": "phpcompatibility/phpcompatibility-wp", - "version": "2.1.4", + "version": "2.1.8", "source": { "type": "git", "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git", - "reference": "b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5" + "reference": "7c8d18b4d90dac9e86b0869a608fa09158e168fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5", - "reference": "b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/7c8d18b4d90dac9e86b0869a608fa09158e168fa", + "reference": "7c8d18b4d90dac9e86b0869a608fa09158e168fa", "shasum": "" }, "require": { "phpcompatibility/php-compatibility": "^9.0", - "phpcompatibility/phpcompatibility-paragonie": "^1.0" + "phpcompatibility/phpcompatibility-paragonie": "^1.0", + "squizlabs/php_codesniffer": "^3.3" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7" + "dealerdirect/phpcodesniffer-composer-installer": "^1.0" }, "suggest": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." }, "type": "phpcodesniffer-standard", @@ -874,22 +902,41 @@ ], "support": { "issues": "https://github.com/PHPCompatibility/PHPCompatibilityWP/issues", + "security": "https://github.com/PHPCompatibility/PHPCompatibilityWP/security/policy", "source": "https://github.com/PHPCompatibility/PHPCompatibilityWP" }, - "time": "2022-10-24T09:00:36+00:00" + "funding": [ + { + "url": "https://github.com/PHPCompatibility", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcompatibility", + "type": "thanks_dev" + } + ], + "time": "2025-10-18T00:05:59+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "7.0.15", + "version": "7.0.17", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "819f92bba8b001d4363065928088de22f25a3a48" + "reference": "40a4ed114a4aea5afd6df8d0f0c9cd3033097f66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/819f92bba8b001d4363065928088de22f25a3a48", - "reference": "819f92bba8b001d4363065928088de22f25a3a48", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/40a4ed114a4aea5afd6df8d0f0c9cd3033097f66", + "reference": "40a4ed114a4aea5afd6df8d0f0c9cd3033097f66", "shasum": "" }, "require": { @@ -941,7 +988,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/7.0.15" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/7.0.17" }, "funding": [ { @@ -949,20 +996,20 @@ "type": "github" } ], - "time": "2021-07-26T12:20:09+00:00" + "time": "2024-03-02T06:09:37+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "2.0.5", + "version": "2.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5" + "reference": "69deeb8664f611f156a924154985fbd4911eb36b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5", - "reference": "42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/69deeb8664f611f156a924154985fbd4911eb36b", + "reference": "69deeb8664f611f156a924154985fbd4911eb36b", "shasum": "" }, "require": { @@ -1001,7 +1048,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.5" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.6" }, "funding": [ { @@ -1009,7 +1056,7 @@ "type": "github" } ], - "time": "2021-12-02T12:42:26+00:00" + "time": "2024-03-01T13:39:50+00:00" }, { "name": "phpunit/php-text-template", @@ -1058,16 +1105,16 @@ }, { "name": "phpunit/php-timer", - "version": "2.1.3", + "version": "2.1.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662" + "reference": "a691211e94ff39a34811abd521c31bd5b305b0bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/2454ae1765516d20c4ffe103d85a58a9a3bd5662", - "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/a691211e94ff39a34811abd521c31bd5b305b0bb", + "reference": "a691211e94ff39a34811abd521c31bd5b305b0bb", "shasum": "" }, "require": { @@ -1105,7 +1152,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/2.1.3" + "source": "https://github.com/sebastianbergmann/php-timer/tree/2.1.4" }, "funding": [ { @@ -1113,7 +1160,7 @@ "type": "github" } ], - "time": "2020-11-30T08:20:02+00:00" + "time": "2024-03-01T13:42:41+00:00" }, { "name": "phpunit/php-token-stream", @@ -1177,48 +1224,48 @@ }, { "name": "phpunit/phpunit", - "version": "8.5.33", + "version": "8.5.52", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "7d1ff0e8c6b35db78ff13e3e05517d7cbf7aa32e" + "reference": "1015741814413c156abb0f53d7db7bbd03c6e858" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/7d1ff0e8c6b35db78ff13e3e05517d7cbf7aa32e", - "reference": "7d1ff0e8c6b35db78ff13e3e05517d7cbf7aa32e", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1015741814413c156abb0f53d7db7bbd03c6e858", + "reference": "1015741814413c156abb0f53d7db7bbd03c6e858", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1", + "doctrine/instantiator": "^1.5.0", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.0", - "phar-io/manifest": "^2.0.3", - "phar-io/version": "^3.0.2", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", "php": ">=7.2", - "phpunit/php-code-coverage": "^7.0.12", - "phpunit/php-file-iterator": "^2.0.4", + "phpunit/php-code-coverage": "^7.0.17", + "phpunit/php-file-iterator": "^2.0.6", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^2.1.2", - "sebastian/comparator": "^3.0.5", - "sebastian/diff": "^3.0.2", - "sebastian/environment": "^4.2.3", - "sebastian/exporter": "^3.1.5", - "sebastian/global-state": "^3.0.0", - "sebastian/object-enumerator": "^3.0.3", - "sebastian/resource-operations": "^2.0.1", - "sebastian/type": "^1.1.3", + "phpunit/php-timer": "^2.1.4", + "sebastian/comparator": "^3.0.7", + "sebastian/diff": "^3.0.6", + "sebastian/environment": "^4.2.5", + "sebastian/exporter": "^3.1.8", + "sebastian/global-state": "^3.0.6", + "sebastian/object-enumerator": "^3.0.5", + "sebastian/resource-operations": "^2.0.3", + "sebastian/type": "^1.1.5", "sebastian/version": "^2.0.1" }, "suggest": { - "ext-soap": "*", - "ext-xdebug": "*", - "phpunit/php-invoker": "^2.0.0" + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage", + "phpunit/php-invoker": "To allow enforcing time limits" }, "bin": [ "phpunit" @@ -1254,7 +1301,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/8.5.33" + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/8.5.52" }, "funding": [ { @@ -1265,25 +1313,33 @@ "url": "https://github.com/sebastianbergmann", "type": "github" }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, { "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", "type": "tidelift" } ], - "time": "2023-02-27T13:04:50+00:00" + "time": "2026-01-27T05:20:18+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.2", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619" + "reference": "92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619", - "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54", + "reference": "92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54", "shasum": "" }, "require": { @@ -1317,7 +1373,7 @@ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2" + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.3" }, "funding": [ { @@ -1325,20 +1381,20 @@ "type": "github" } ], - "time": "2020-11-30T08:15:22+00:00" + "time": "2024-03-01T13:45:45+00:00" }, { "name": "sebastian/comparator", - "version": "3.0.5", + "version": "3.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "1dc7ceb4a24aede938c7af2a9ed1de09609ca770" + "reference": "bc7d8ac2fe1cce229bff9b5fd4efe65918a1ff52" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1dc7ceb4a24aede938c7af2a9ed1de09609ca770", - "reference": "1dc7ceb4a24aede938c7af2a9ed1de09609ca770", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/bc7d8ac2fe1cce229bff9b5fd4efe65918a1ff52", + "reference": "bc7d8ac2fe1cce229bff9b5fd4efe65918a1ff52", "shasum": "" }, "require": { @@ -1391,28 +1447,40 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/3.0.5" + "source": "https://github.com/sebastianbergmann/comparator/tree/3.0.7" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" } ], - "time": "2022-09-14T12:31:48+00:00" + "time": "2026-01-24T09:20:25+00:00" }, { "name": "sebastian/diff", - "version": "3.0.4", + "version": "3.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "6296a0c086dd0117c1b78b059374d7fcbe7545ae" + "reference": "98ff311ca519c3aa73ccd3de053bdb377171d7b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/6296a0c086dd0117c1b78b059374d7fcbe7545ae", - "reference": "6296a0c086dd0117c1b78b059374d7fcbe7545ae", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/98ff311ca519c3aa73ccd3de053bdb377171d7b6", + "reference": "98ff311ca519c3aa73ccd3de053bdb377171d7b6", "shasum": "" }, "require": { @@ -1457,7 +1525,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/3.0.4" + "source": "https://github.com/sebastianbergmann/diff/tree/3.0.6" }, "funding": [ { @@ -1465,20 +1533,20 @@ "type": "github" } ], - "time": "2023-05-07T05:30:20+00:00" + "time": "2024-03-02T06:16:36+00:00" }, { "name": "sebastian/environment", - "version": "4.2.4", + "version": "4.2.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0" + "reference": "56932f6049a0482853056ffd617c91ffcc754205" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/d47bbbad83711771f167c72d4e3f25f7fcc1f8b0", - "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/56932f6049a0482853056ffd617c91ffcc754205", + "reference": "56932f6049a0482853056ffd617c91ffcc754205", "shasum": "" }, "require": { @@ -1520,7 +1588,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/4.2.4" + "source": "https://github.com/sebastianbergmann/environment/tree/4.2.5" }, "funding": [ { @@ -1528,24 +1596,24 @@ "type": "github" } ], - "time": "2020-11-30T07:53:42+00:00" + "time": "2024-03-01T13:49:59+00:00" }, { "name": "sebastian/exporter", - "version": "3.1.5", + "version": "3.1.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "73a9676f2833b9a7c36968f9d882589cd75511e6" + "reference": "64cfeaa341951ceb2019d7b98232399d57bb2296" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/73a9676f2833b9a7c36968f9d882589cd75511e6", - "reference": "73a9676f2833b9a7c36968f9d882589cd75511e6", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/64cfeaa341951ceb2019d7b98232399d57bb2296", + "reference": "64cfeaa341951ceb2019d7b98232399d57bb2296", "shasum": "" }, "require": { - "php": ">=7.0", + "php": ">=7.2", "sebastian/recursion-context": "^3.0" }, "require-dev": { @@ -1597,28 +1665,40 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/3.1.5" + "source": "https://github.com/sebastianbergmann/exporter/tree/3.1.8" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" } ], - "time": "2022-09-14T06:00:17+00:00" + "time": "2025-09-24T05:55:14+00:00" }, { "name": "sebastian/global-state", - "version": "3.0.2", + "version": "3.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "de036ec91d55d2a9e0db2ba975b512cdb1c23921" + "reference": "800689427e3e8cf57a8fe38fcd1d4344c9b2f046" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/de036ec91d55d2a9e0db2ba975b512cdb1c23921", - "reference": "de036ec91d55d2a9e0db2ba975b512cdb1c23921", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/800689427e3e8cf57a8fe38fcd1d4344c9b2f046", + "reference": "800689427e3e8cf57a8fe38fcd1d4344c9b2f046", "shasum": "" }, "require": { @@ -1661,28 +1741,40 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/3.0.2" + "source": "https://github.com/sebastianbergmann/global-state/tree/3.0.6" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", + "type": "tidelift" } ], - "time": "2022-02-10T06:55:38+00:00" + "time": "2025-08-10T05:40:12+00:00" }, { "name": "sebastian/object-enumerator", - "version": "3.0.4", + "version": "3.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2" + "reference": "ac5b293dba925751b808e02923399fb44ff0d541" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", - "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/ac5b293dba925751b808e02923399fb44ff0d541", + "reference": "ac5b293dba925751b808e02923399fb44ff0d541", "shasum": "" }, "require": { @@ -1718,7 +1810,7 @@ "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.4" + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.5" }, "funding": [ { @@ -1726,20 +1818,20 @@ "type": "github" } ], - "time": "2020-11-30T07:40:27+00:00" + "time": "2024-03-01T13:54:02+00:00" }, { "name": "sebastian/object-reflector", - "version": "1.1.2", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d" + "reference": "1d439c229e61f244ff1f211e5c99737f90c67def" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", - "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/1d439c229e61f244ff1f211e5c99737f90c67def", + "reference": "1d439c229e61f244ff1f211e5c99737f90c67def", "shasum": "" }, "require": { @@ -1773,7 +1865,7 @@ "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.2" + "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.3" }, "funding": [ { @@ -1781,20 +1873,20 @@ "type": "github" } ], - "time": "2020-11-30T07:37:18+00:00" + "time": "2024-03-01T13:56:04+00:00" }, { "name": "sebastian/recursion-context", - "version": "3.0.1", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb" + "reference": "8fe7e75986a9d24b4cceae847314035df7703a5a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/367dcba38d6e1977be014dc4b22f47a484dac7fb", - "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/8fe7e75986a9d24b4cceae847314035df7703a5a", + "reference": "8fe7e75986a9d24b4cceae847314035df7703a5a", "shasum": "" }, "require": { @@ -1836,28 +1928,40 @@ "homepage": "http://www.github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.1" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.3" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" } ], - "time": "2020-11-30T07:34:24+00:00" + "time": "2025-08-10T05:25:53+00:00" }, { "name": "sebastian/resource-operations", - "version": "2.0.2", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3" + "reference": "72a7f7674d053d548003b16ff5a106e7e0e06eee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/31d35ca87926450c44eae7e2611d45a7a65ea8b3", - "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/72a7f7674d053d548003b16ff5a106e7e0e06eee", + "reference": "72a7f7674d053d548003b16ff5a106e7e0e06eee", "shasum": "" }, "require": { @@ -1887,8 +1991,7 @@ "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/2.0.2" + "source": "https://github.com/sebastianbergmann/resource-operations/tree/2.0.3" }, "funding": [ { @@ -1896,20 +1999,20 @@ "type": "github" } ], - "time": "2020-11-30T07:30:19+00:00" + "time": "2024-03-01T13:59:09+00:00" }, { "name": "sebastian/type", - "version": "1.1.4", + "version": "1.1.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4" + "reference": "18f071c3a29892b037d35e6b20ddf3ea39b42874" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/0150cfbc4495ed2df3872fb31b26781e4e077eb4", - "reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/18f071c3a29892b037d35e6b20ddf3ea39b42874", + "reference": "18f071c3a29892b037d35e6b20ddf3ea39b42874", "shasum": "" }, "require": { @@ -1944,7 +2047,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/1.1.4" + "source": "https://github.com/sebastianbergmann/type/tree/1.1.5" }, "funding": [ { @@ -1952,7 +2055,7 @@ "type": "github" } ], - "time": "2020-11-30T07:25:11+00:00" + "time": "2024-03-01T14:04:07+00:00" }, { "name": "sebastian/version", @@ -2006,12 +2109,12 @@ "version": "3.6.2", "source": { "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", "shasum": "" }, @@ -2055,20 +2158,34 @@ "source": "https://github.com/squizlabs/PHP_CodeSniffer", "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], "time": "2021-12-12T21:44:58+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.1", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", "shasum": "" }, "require": { @@ -2097,7 +2214,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" }, "funding": [ { @@ -2105,7 +2222,7 @@ "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" + "time": "2025-11-17T20:03:58+00:00" }, { "name": "wp-coding-standards/wpcs", @@ -2168,5 +2285,5 @@ "php": "^7.4|~8.0" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/constant-contact-forms.php b/constant-contact-forms.php index d6af4927..e43c2795 100644 --- a/constant-contact-forms.php +++ b/constant-contact-forms.php @@ -12,7 +12,7 @@ * Plugin Name: Constant Contact Forms for WordPress * Plugin URI: https://www.constantcontact.com * Description: Be a better marketer. All it takes is Constant Contact email marketing. - * Version: 2.17.0 + * Version: 2.18.0 * Author: Constant Contact * Author URI: https://www.constantcontact.com/index?pn=miwordpress * Requires PHP: 8.1 @@ -45,9 +45,9 @@ * @param string $class_name Name of the class being requested. * @return null */ -function constant_contact_autoload_classes( $class_name ) { +function constant_contact_autoload_classes( string $class_name ) { if ( ! str_starts_with( $class_name, 'ConstantContact_' ) ) { - return; + return null; } $filename = strtolower( @@ -59,6 +59,8 @@ function constant_contact_autoload_classes( $class_name ) { ); Constant_Contact::include_file( $filename ); + + return null; } spl_autoload_register( 'constant_contact_autoload_classes' ); @@ -75,7 +77,7 @@ class Constant_Contact { * @since 1.0.0 * @var string */ - const VERSION = '2.17.0'; + const VERSION = '2.18.0'; /** * URL of plugin directory. @@ -309,6 +311,13 @@ class Constant_Contact { */ private ConstantContact_Health $health; + /** + * An instance of the ConstantContact_API_Utility class. + * @since 2.18.0 + * @var ConstantContact_API_Utility + */ + private ConstantContact_API_Utility $utility; + /** * Option name for where we store the timestamp of when the plugin was activated. * @@ -333,7 +342,7 @@ class Constant_Contact { * * @return Constant_Contact A single instance of this class. */ - public static function get_instance() { + public static function get_instance(): Constant_Contact { if ( null === self::$single_instance ) { self::$single_instance = new self(); } @@ -385,7 +394,7 @@ protected function __construct() { * * @since 1.0.1 */ - public function minimum_version() { + public function minimum_version(): void { echo '

' . esc_html__( 'Constant Contact Forms requires PHP 7.4 or higher. Your hosting provider or website administrator should be able to assist in updating your PHP version.', 'constant-contact-forms' ) . '

'; } @@ -395,7 +404,8 @@ public function minimum_version() { * @since 1.0.0 */ public function plugin_classes() { - $this->api = new ConstantContact_API( $this ); + $this->utility = new ConstantContact_API_Utility( $this ); + $this->api = new ConstantContact_API( $this ); if ( class_exists( 'FLBuilder' ) ) { // Load if Beaver Builder is active. $this->beaver_builder = new ConstantContact_Beaver_Builder( $this ); @@ -429,7 +439,7 @@ public function plugin_classes() { * * @since 1.0.0 */ - public function admin_plugin_classes() { + public function admin_plugin_classes(): void { $this->admin = new ConstantContact_Admin( $this, $this->basename ); $this->admin_pages = new ConstantContact_Admin_Pages( $this ); $this->block = new ConstantContact_Block( $this ); @@ -442,7 +452,7 @@ public function admin_plugin_classes() { * * @return void */ - public function hooks() { + public function hooks(): void { if ( ! $this->meets_php_requirements() ) { add_action( 'admin_notices', [ $this, 'minimum_version' ] ); return; @@ -471,7 +481,7 @@ public function hooks() { * * @since 1.0.0 */ - public function activate() { + public function activate(): void { update_option( self::$activated_date_option, time() ); } @@ -482,7 +492,7 @@ public function activate() { * * @return void */ - public function deactivate() { + public function deactivate(): void { if ( ! $this->meets_php_requirements() ) { return; @@ -511,7 +521,7 @@ public function deactivate() { * * @since 1.6.0 */ - public function uninstall() { + public function uninstall(): void { $uninstaller = new ConstantContact_Uninstall(); $uninstaller->run(); } @@ -532,7 +542,7 @@ public function meets_php_requirements() : bool { * * @since 1.0.0 */ - public function init() { + public function init(): void { $this->init_debug_log(); } @@ -543,7 +553,7 @@ public function init() { * * @return void */ - protected function init_debug_log() { + protected function init_debug_log(): void { if ( ! constant_contact_debugging_enabled() ) { return; @@ -558,7 +568,7 @@ protected function init_debug_log() { * * @since 1.0.0 */ - public function load_libs() { + public function load_libs(): void { // Load what we can, automagically. require_once $this->dir( 'vendor_prefixed/autoload.php' ); @@ -610,16 +620,11 @@ public function ajax_save_clear_first_form() { * @return mixed */ public function __get( $field ) { - switch ( $field ) { - case 'version': - return self::VERSION; - case 'basename': - case 'path': - case 'url': - return $this->$field; - default: - throw new Exception( 'Invalid ' . __CLASS__ . ' property: ' . $field ); - } + return match ( $field ) { + 'version' => self::VERSION, + 'basename', 'path', 'url' => $this->$field, + default => throw new Exception( 'Invalid ' . __CLASS__ . ' property: ' . $field ), + }; } /** @@ -848,6 +853,15 @@ public function get_updates(): ConstantContact_Updates { return $this->updates; } + /** + * API Utility getter. + * @return ConstantContact_API_Utility + * @since 2.18.0 + */ + public function get_api_utility(): ConstantContact_API_Utility { + return $this->utility; + } + /** * Include a file from the classes directory. * @@ -857,7 +871,7 @@ public function get_updates(): ConstantContact_Updates { * @param bool $include_class Whether or ot to include the class. * @return bool Result of include call. */ - public static function include_file( string $filename, bool $include_class = true ) { + public static function include_file( string $filename, bool $include_class = true ): bool { // By default, all files are named 'class-something.php'. if ( $include_class ) { @@ -883,7 +897,7 @@ public static function include_file( string $filename, bool $include_class = tru */ public static function dir( string $path = '' ) : string { static $dir; - $dir = $dir ? $dir : trailingslashit( __DIR__ ); + $dir = $dir ?: trailingslashit( __DIR__ ); return $dir . $path; } @@ -897,7 +911,7 @@ public static function dir( string $path = '' ) : string { */ public static function url( string $path = '' ) : string { static $url; - $url = $url ? $url : trailingslashit( plugin_dir_url( __FILE__ ) ); + $url = $url ?: trailingslashit( plugin_dir_url( __FILE__ ) ); return $url . $path; } @@ -908,7 +922,7 @@ public static function url( string $path = '' ) : string { * * @return string License text. */ - public function get_license_text() { + public function get_license_text(): string { $license = self::url( self::LICENSE_FILE ); $license_content = wp_remote_get( $license ); @@ -927,7 +941,7 @@ public function get_license_text() { * @param array $classes Existing body classes. * @return array Amended body classes. */ - public function body_classes( $classes = [] ) : array { + public function body_classes( array $classes = [] ) : array { $theme = wp_get_theme()->get_template(); $classes[] = "ctct-{$theme}"; // Prefixing for user knowledge of source. @@ -939,7 +953,7 @@ public function body_classes( $classes = [] ) : array { * * @since 1.4.0 */ - public function register_admin_assets() { + public function register_admin_assets(): void { wp_register_style( 'constant-contact-forms-admin', @@ -954,7 +968,7 @@ public function register_admin_assets() { * * @since 1.4.0 */ - public function register_front_assets() { + public function register_front_assets(): void { if ( constant_contact_disable_frontend_css() ) { return; @@ -1013,7 +1027,7 @@ public function is_constant_contact() : bool { * * @return Constant_Contact Singleton instance of plugin class. */ -function constant_contact() { +function constant_contact(): Constant_Contact { return Constant_Contact::get_instance(); } @@ -1022,7 +1036,7 @@ function constant_contact() { * * @since 1.6.0 */ -function constant_contact_uninstall() { +function constant_contact_uninstall(): void { $instance = Constant_Contact::get_instance(); $instance->uninstall(); } diff --git a/includes/class-admin-pages.php b/includes/class-admin-pages.php index 82a2dd0f..dfeca08b 100644 --- a/includes/class-admin-pages.php +++ b/includes/class-admin-pages.php @@ -66,7 +66,7 @@ public function about_page() { $new_link = ''; if ( ! constant_contact()->get_api()->is_connected() ) { - $new_link = constant_contact()->get_api()->get_signup_link(); + $new_link = constant_contact()->get_api_utility()->get_signup_link(); $auth_link = admin_url( 'edit.php?post_type=ctct_forms&page=ctct_options_connect' ); } ?> diff --git a/includes/class-api-utility.php b/includes/class-api-utility.php new file mode 100644 index 00000000..d9c7aa72 --- /dev/null +++ b/includes/class-api-utility.php @@ -0,0 +1,166 @@ +plugin = $plugin; + } + + /** + * Obfuscate the left side of email addresses at the `@`. + * + * @since 1.7.0 + * @since 2.18.0 Moved to utility class. + * + * @param array $contact Contact data. + * @return array + */ + public function clear_email( array $contact ): array { + $clean = []; + foreach ( $contact as $contact_key => $contact_value ) { + if ( is_array( $contact_value ) ) { + $clean[ $contact_key ] = $this->clear_email( $contact_value ); + } elseif ( is_email( $contact_value ) ) { + $email_parts = explode( '@', $contact_value ); + $clean[ $contact_key ] = implode( '@', [ '***', $email_parts[1] ] ); + } else { + $clean[ $contact_key ] = $contact_value; + } + } + return $clean; + } + + /** + * Obfuscate phone numbers. + * + * @since 1.13.0 + * @since 2.18.0 Moved to utility class. + * + * @param array $contact Contact data. + * @return array + */ + public function clear_phone( array $contact ): array { + $clean = $contact; + foreach ( $contact as $contact_key => $contact_value ) { + if ( is_array( $contact_value ) && ! empty( $contact_value['key'] ) && $contact_value['key'] === 'phone_number' ) { + $clean[ $contact_key ]['val'] = '***-***-****'; + } + } + + return $clean; + } + + /** + * Remove hCaptcha data from logged data. + * + * @since 2.9.0 + * @since 2.18.0 Moved to utility class. + * + * @param array $contact Contact data. + * @return array + */ + public function clear_hcaptcha( array $contact ): array { + if ( array_key_exists( 'h-captcha-response', $contact ) ) { + unset( $contact['h-captcha-response'] ); + } + + return $contact; + } + + /** + * Pushes all error to api_error_message. + * + * @since 1.0.0 + * @since 2.18.0 Moved to utility class. + * + * @throws Exception Throws Exception if encountered while attempting to log errors. + * + * @param array $errors Errors from API. + */ + public function log_errors( $errors ): void { + if ( is_array( $errors ) ) { + foreach ( $errors as $error ) { + constant_contact_maybe_log_it( + 'API', + $error + ); + } + } + } + + /** + * Helper method to output a link for our connect modal. + * + * @since 1.0.0 + * @since 2.18.0 Moved to utility class. + * + * @return string Signup URL. + */ + public function get_signup_link(): string { + return 'https://www.constantcontact.com/signup'; + } + + + /** + * Base64 encode URL. + * + * @since 2.0.0 + * @since 2.18.0 Moved to utility class. + * + * @param string $data + * + * @return string + */ + public function base64url_encode( string $data ): string { + return rtrim( strtr( base64_encode( $data ), '+/', '-_' ), '=' ); + } + + + /** + * Obfuscate a value in our debug logs. + * + * Helps keep things private and not put into a potentially publicly accessed file. + * + * @since 2.1.0 + * @since 2.18.0 Moved to utility class. + * + * @param string $data_item Item to obfuscate. + * + * @return string + */ + public function obfuscate_api_data_item( string $data_item ): string { + $start = substr( $data_item, 0, 8 ); + return $start . '***'; + } +} diff --git a/includes/class-api.php b/includes/class-api.php index 618128aa..79d27dc0 100644 --- a/includes/class-api.php +++ b/includes/class-api.php @@ -286,231 +286,394 @@ public function get_api_token() { } /** - * Returns Refresh API token. - * - * @since 2.0.0 + * Exchange an authorization code for an access token. + * Make this call by passing in the code present when the account owner is redirected back to you. + * The response will contain an 'access_token' and 'refresh_token' * - * @param string $type api key type. - * @return string Refresh Token. + * @param array of get parameters passed to redirect URL */ - public function get_refresh_token() { - return $this->refresh_token; - } + public function acquire_access_token(): bool { - /** - * Info of the connected CTCT account. - * - * @since 1.0.0 - * - * @return array Current connected ctct account info. - */ - public function get_account_info() { + if ( ! empty( $_POST['action'] ) && 'heartbeat' === sanitize_text_field( $_POST['action'] ) ) { + return false; + } - if ( ! $this->is_connected() ) { - return []; + if ( ! empty( $_POST['ctct-disconnect'] ) && 'true' === sanitize_text_field( $_POST['ctct-disconnect'] ) ) { + return false; + } + $options = get_option( 'ctct_options_settings' ); + if ( empty( $options ) ) { + return false; } - $acct_data = get_transient( 'constant_contact_acct_info' ); + if ( empty( $options['_ctct_form_state_authcode'] ) ) { + return false; + } - /** - * Filters whether or not to bypass transient with a filter. - * - * @since 1.0.0 - * - * @param bool $value Whether or not to bypass. - */ - $bypass_acct_cache = apply_filters( 'constant_contact_bypass_acct_info_cache', false ); + $code_state = $options['_ctct_form_state_authcode']; - if ( false === $acct_data || $bypass_acct_cache ) { + parse_str( $code_state, $parsed_code_state ); + $parsed_code_state = array_values( $parsed_code_state ); - try { - $acct_data = $this->cc()->get_account_info(); - if ( array_key_exists( 'error_key', $acct_data ) && 'unauthorized' === $acct_data['error_key'] ) { - $this->refresh_token(); + if ( empty( $parsed_code_state[0] ) || empty( $parsed_code_state[1] ) ) { + $this->status_code = 0; + $this->last_error = 'Invalid state or auth code'; + constant_contact_maybe_log_it( 'Error: ', $this->last_error ); - $acct_data = $this->cc()->get_account_info(); - } + return false; + } else { + $code = $parsed_code_state[0]; + $state = $parsed_code_state[1]; + } - if ( $acct_data && ! array_key_exists( 'error_key', $acct_data ) ) { - set_transient( 'constant_contact_acct_info', $acct_data, 12 * HOUR_IN_SECONDS ); - } - } catch ( CtctException $ex ) { - add_filter( 'constant_contact_force_logging', '__return_true' ); - $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); - $errors = $ex->getErrors(); - $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; - $this->log_errors( $our_errors ); - constant_contact_forms_maybe_set_exception_notice( $ex ); - } catch ( Exception $ex ) { - $error = new stdClass(); - $error->error_key = get_class( $ex ); - $error->error_message = $ex->getMessage(); + $expected_state = get_option( 'CtctConstantContactState' ); - add_filter( 'constant_contact_force_logging', '__return_true' ); - constant_contact_forms_maybe_set_exception_notice( $ex ); + if ( ( $state ?? 'undefined' ) != $expected_state ) { + $this->status_code = 0; + $this->last_error = 'state is not correct'; + constant_contact_maybe_log_it( 'Error: ', $this->last_error ); - $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); - $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; - $this->log_errors( $our_errors ); - } + return false; } + // Create full request URL + $body = [ + 'client_id' => $this->client_api_key, + 'code' => $code, + 'redirect_uri' => $this->redirect_URI, + 'grant_type' => 'authorization_code', + ]; - return $acct_data; - } + $body['code_verifier'] = get_option( 'CtctConstantContactcode_verifier' ); - /** - * Contacts of the connected CTCT account. - * - * @since 1.0.0 - * - * @return array Current connect ctct account contacts. - */ - public function get_contacts() { - if ( ! $this->is_connected() ) { - return []; - } + $headers = $this->set_authorization(); - $contacts = get_transient( 'ctct_contact' ); + $url = $this->oauth2_url; - if ( false === $contacts ) { - try { - $contacts = $this->cc()->get_contacts(); - if ( array_key_exists( 'error_key', $contacts ) && 'unauthorized' === $contacts['error_key'] ) { - $this->refresh_token(); + $options = [ + 'body' => $body, + 'headers' => $headers, + ]; - $contacts = $this->cc()->get_contacts(); - } + // This will be either true or false. + $result = $this->exec( $url, $options ); - set_transient( 'ctct_contact', $contacts, 1 * DAY_IN_SECONDS ); - return $contacts; - } catch ( CtctException $ex ) { - add_filter( 'constant_contact_force_logging', '__return_true' ); - $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); - $errors = $ex->getErrors(); - $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; - $this->log_errors( $our_errors ); - constant_contact_forms_maybe_set_exception_notice( $ex ); - } catch ( Exception $ex ) { - $error = new stdClass(); - $error->error_key = get_class( $ex ); - $error->error_message = $ex->getMessage(); + if ( false === $result ) { + constant_contact_set_needs_manual_reconnect( 'true' ); + } else { - add_filter( 'constant_contact_force_logging', '__return_true' ); - constant_contact_forms_maybe_set_exception_notice( $ex ); + /** + * Fires after successful access token acquisition. + * @since 2.3.0 + */ + do_action( 'ctct_access_token_acquired' ); - $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); - $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; - $this->log_errors( $our_errors ); - } + constant_contact_set_needs_manual_reconnect( 'false' ); } - return $contacts; + + return $result; } /** - * Lists of the connected CTCT account. - * - * @since 1.0.0 - * - * @param bool $force_skip_cache Whether or not to skip cache. - * @return array Current connect ctct lists. + * Refresh the access token. + * @return array + * @throws Exception + * @since 2.0.0 */ - public function get_lists( bool $force_skip_cache = false ) { + public function refresh_token() { - if ( ! $this->is_connected() ) { - return []; - } + $status = []; + $failures = (int) get_option( 'ctct_refresh_failures', 0 ); - $lists = get_transient( 'ctct_lists' ); + // Only require manual reconnect after multiple consecutive failures. + // This prevents a single transient error (network blip, brief API outage, + // container restart) from permanently breaking the connection. + if ( constant_contact_get_needs_manual_reconnect() ) { + $status['success'] = false; + $status['reason'] = 'manual_reconnect'; - if ( $force_skip_cache ) { - $lists = false; + return $status; } - if ( false === $lists ) { + $token = constant_contact()->get_connect()->e_get( '_ctct_refresh_token' ); + if ( empty( $token ) ) { + $status['success'] = false; + $status['reason'] = 'no available token'; - try { - $results = $this->cc()->get_lists(); - $lists = $results['lists'] ?? []; + return $status; + } - if ( array_key_exists( 'error_key', $results ) && 'unauthorized' === $results['error_key'] ) { - $this->refresh_token(); + constant_contact_maybe_log_it( 'Refresh Token:', 'Refresh token triggered' ); - $results = $this->cc()->get_lists(); - $lists = $results['lists'] ?? []; - } + // Create full request URL + $body = [ + 'client_id' => $this->client_api_key, + 'refresh_token' => constant_contact()->get_connect()->e_get( '_ctct_refresh_token' ), + 'redirect_uri' => $this->redirect_URI, + 'grant_type' => 'refresh_token', + ]; - if ( ! empty( $lists ) ) { - set_transient( 'ctct_lists', $lists, 12 * HOUR_IN_SECONDS ); - return $lists; - } elseif ( array_key_exists( 'error_key', $results ) ) { - set_transient( 'ctct_lists', $lists, DAY_IN_SECONDS ); - add_filter( 'constant_contact_force_logging', '__return_true' ); - $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); - $our_errors[] = $extra . $results['error_key'] . ': ' . $results['error_message']; - $this->log_errors($our_errors); - constant_contact_forms_maybe_set_exception_notice(); - } - } catch ( CtctException $ex ) { - add_filter( 'constant_contact_force_logging', '__return_true' ); - $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); - $errors = $ex->getErrors(); - $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; - $this->log_errors( $our_errors ); - constant_contact_forms_maybe_set_exception_notice( $ex ); - } catch ( Exception $ex ) { - $error = new stdClass(); - $error->error_key = get_class( $ex ); - $error->error_message = $ex->getMessage(); + $url = $this->oauth2_url; + $headers = $this->set_authorization(); - add_filter( 'constant_contact_force_logging', '__return_true' ); - constant_contact_forms_maybe_set_exception_notice( $ex ); + $options = [ + 'body' => $body, + 'headers' => $headers, + ]; - $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); - $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; - $this->log_errors( $our_errors ); + $result = $this->exec( $url, $options ); + + if ( false === $result ) { + $failures ++; + update_option( 'ctct_refresh_failures', $failures ); + + // Distinguish between a definitive auth failure and a transient error. + // Only require manual reconnect for invalid_grant (revoked/expired refresh + // token) or after 5 consecutive failures of any kind. + if ( false !== strpos( $this->last_error, 'invalid_grant' ) ) { + constant_contact_maybe_log_it( 'Refresh Token:', 'Refresh token revoked (invalid_grant). Manual reconnect required.' ); + constant_contact_set_needs_manual_reconnect( 'true' ); + $status['reason'] = 'expired'; + } elseif ( $failures >= 5 ) { + constant_contact_maybe_log_it( 'Refresh Token:', 'Refresh failed ' . $failures . ' consecutive times. Manual reconnect required.' ); + constant_contact_set_needs_manual_reconnect( 'true' ); + $status['reason'] = 'max_retries_exceeded'; + } else { + constant_contact_maybe_log_it( 'Refresh Token:', 'Refresh failed (attempt ' . $failures . '/5). Will retry. Attempted at ' . current_datetime()->format( 'Y-n-d, H:i' ) ); + $status['reason'] = 'transient_failure'; } + + $status['success'] = false; + } else { + delete_transient( 'ctct_lists' ); + update_option( 'ctct_access_token_timestamp', time() ); + update_option( 'ctct_refresh_failures', 0 ); + constant_contact_set_needs_manual_reconnect( 'false' ); + + $status['success'] = true; + $status['reason'] = 'refreshed'; } - return $lists; + return $status; } /** - * Get v2 to v3 API lists of the connected CTCT account. - * - * @since 2.0.0 - * - * @param string $old_ids_string Comma separated list of old (v2 API) list ids. - * @param bool $force_skip_cache Whether or not to skip cache. - * - * @return array API v2 to v3 List ID cross references. + * Check if our current access token is expired. + * Based on access token issued timestamp + expires in timestamp and current time. + * @return bool + * @since 2.2.0 */ - public function get_v2_list_id_x_refs( string $old_ids_string, bool $force_skip_cache = false ) { - - if ( ! $this->is_connected() ) { - return []; - } + private function access_token_maybe_expired() { - $list_x_refs = get_transient('ctct_list_xrefs'); + $issued_time = get_option( 'ctct_access_token_timestamp', '' ); + if ( empty( $issued_time ) ) { + // It's not expired because it doesn't exist. + // This should be filled in by now though. + return false; + } - if ( $force_skip_cache ) { - $list_x_refs = false; + $current_time = time(); + $expiration_time = (int) $issued_time + (int) $this->expires_in; + + // If we're currently above the expiration time, we're expired. + return $current_time >= $expiration_time; + } + + /** + * Generate the URL an account owner would use to allow your app + * to access their account. + * After visiting the URL, the account owner is prompted to log in and allow your app to access their account. + * They are then redirected to your redirect URL with the authorization code appended as a query parameter. e.g.: + * http://localhost:8888/?code={authorization_code} + */ + public function get_authorization_url(): string { + + $auth_url = get_option( 'ctct_auth_url' ); + + if ( $auth_url ) { + return $auth_url; } - if ( false === $list_x_refs ) { + $scopes = implode( '+', array_keys( $this->scopes ) ); + [ $code_verifier, $code_challenge ] = $this->code_challenge(); + + $state = bin2hex( random_bytes( 8 ) ); + + update_option( 'CtctConstantContactState', $state ); + + $params = [ + 'client_id' => $this->client_api_key, + 'redirect_uri' => $this->redirect_URI, + 'response_type' => 'code', + 'code_challenge' => $code_challenge, + 'code_challenge_method' => 'S256', + 'state' => $state, + 'scope' => $scopes, + ]; + + // Store generated random state and code challenge based on RFC 7636 + // https://datatracker.ietf.org/doc/html/rfc7636#section-6.1 + update_option( 'CtctConstantContactcode_verifier', $code_verifier ); + + $url = add_query_arg( $params, $this->authorize_url ); + + update_option( 'ctct_auth_url', $url ); + + return $url; + } + + /** + * Set our authorization headers. + * @return string[] + * @since 2.0.0 + */ + private function set_authorization(): array { + + // Set authorization header + // Make string of "API_KEY:SECRET" + $auth = $this->client_api_key; + // Base64 encode it + $credentials = base64_encode( $auth ); + // Create and set the Authorization header to use the encoded credentials + $headers = [ 'Authorization: Basic ' . $credentials, 'cache-control: no-cache' ]; + + return $headers; + } + + /** + * Make sure we don't over-do API requests, helper method to check if we're connected. + * @return boolean If connected. + * @since 1.0.0 + */ + public function is_connected() { + static $token = null; + + if ( constant_contact()->get_connect()->e_get( '_ctct_access_token' ) ) { + $token = constant_contact()->get_connect()->e_get( '_ctct_access_token' ) ? true : false; + } + + return $token; + } + + /** + * Execute our API request for token acquisition. + * + * @param string $url URL to make request to. + * @param array $options Request options. + * + * @return bool + * @throws Exception + * @since 2.0.0 + */ + private function exec( $url, $options ): bool { + $response = wp_safe_remote_post( $url, $options ); + + $this->last_error = ''; + $this->status_code = 0; + + if ( ! is_wp_error( $response ) ) { + if ( empty( $response['body'] ) ) { + constant_contact_maybe_log_it( + 'Response error: ', implode( ":", $response['response'] ) + ); + return false; + } + $data = json_decode( $response['body'], true ); + $json_last_error = json_last_error(); + if ( JSON_ERROR_NONE !== $json_last_error ) { + constant_contact_maybe_log_it( 'JSON error: ', json_last_error_msg() ); + } + + // check if the body contains error + if ( isset( $data['error'] ) ) { + if ( 'invalid_grant' === $data['error'] ) { + $this->api_errors_admin_email(); + } + $this->last_error = $data['error'] . ': ' . ( $data['error_description'] ?? 'Undefined' ); + constant_contact_maybe_log_it( 'Error: ', $this->last_error ); + + return false; + } + + if ( ! empty( $data['access_token'] ) ) { + + constant_contact_maybe_log_it( 'Refresh Token: ', 'Old Refresh Token: ' . constant_contact()->get_api_utility()->obfuscate_api_data_item( $this->refresh_token ) ); + constant_contact_maybe_log_it( 'Access Token: ', 'Old Access Token: ' . constant_contact()->get_api_utility()->obfuscate_api_data_item( $this->access_token ) ); + + constant_contact()->get_connect()->e_set( '_ctct_access_token', $data['access_token'] ); + constant_contact()->get_connect()->e_set( '_ctct_refresh_token', $data['refresh_token'] ); + constant_contact()->get_connect()->e_set( '_ctct_expires_in', (string) $data['expires_in'] ); + + $this->access_token = $data['access_token'] ?? ''; + $this->refresh_token = $data['refresh_token'] ?? ''; + $this->expires_in = $data['expires_in'] ?? ''; + + delete_option( 'ctct_auth_url' ); + $dateObj = current_datetime(); + $expDateObj = $dateObj->modify( '+' . $data['expires_in'] . ' seconds' ); + + constant_contact_maybe_log_it( 'Refresh Token: ', 'Refresh token successfully received' ); + constant_contact_maybe_log_it( 'Refresh Token: ', 'New Refresh Token: ' . constant_contact()->get_api_utility()->obfuscate_api_data_item( $this->refresh_token ) ); + constant_contact_maybe_log_it( 'Access Token: ', 'New Access Token: ' . constant_contact()->get_api_utility()->obfuscate_api_data_item( $this->access_token ) ); + constant_contact_maybe_log_it( + 'Expiration time:', + 'Current time: ' . $dateObj->format( 'Y-n-d, H:i' ) . ' Estimated expiration time: ' . $expDateObj->format( 'Y-n-d, H:i' ) + ); + + return isset( $data['access_token'], $data['refresh_token'] ); + } + } else { + $this->status_code = 0; + $this->last_error = $response->get_error_message(); + constant_contact_maybe_log_it( 'Error: ', $this->last_error ); + } + + return false; + } + + /** + * Info of the connected CTCT account. + * + * @since 1.0.0 + * + * @return array Current connected ctct account info. + */ + public function get_account_info() { + + if ( ! $this->is_connected() ) { + return []; + } + + $acct_data = get_transient( 'constant_contact_acct_info' ); + + /** + * Filters whether or not to bypass transient with a filter. + * + * @since 1.0.0 + * + * @param bool $value Whether or not to bypass. + */ + $bypass_acct_cache = apply_filters( 'constant_contact_bypass_acct_info_cache', false ); + + if ( false === $acct_data || $bypass_acct_cache ) { try { - $list_x_refs = $this->cc()->get_updated_lists_ids( $old_ids_string ); - if ( is_array( $list_x_refs ) ) { - set_transient('ctct_list_xrefs', $list_x_refs, HOUR_IN_SECONDS ); - return $list_x_refs; + $acct_data = $this->cc()->get_account_info(); + if ( array_key_exists( 'error_key', $acct_data ) && 'unauthorized' === $acct_data['error_key'] ) { + $this->refresh_token(); + + $acct_data = $this->cc()->get_account_info(); + } + + if ( $acct_data && ! array_key_exists( 'error_key', $acct_data ) ) { + set_transient( 'constant_contact_acct_info', $acct_data, 12 * HOUR_IN_SECONDS ); } } catch ( CtctException $ex ) { add_filter( 'constant_contact_force_logging', '__return_true' ); $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $errors = $ex->getErrors(); $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; - $this->log_errors( $our_errors ); + constant_contact()->get_api_utility()->log_errors( $our_errors ); constant_contact_forms_maybe_set_exception_notice( $ex ); } catch ( Exception $ex ) { $error = new stdClass(); @@ -522,50 +685,44 @@ public function get_v2_list_id_x_refs( string $old_ids_string, bool $force_skip_ $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; - $this->log_errors( $our_errors ); + constant_contact()->get_api_utility()->log_errors( $our_errors ); } } - return $list_x_refs; + return $acct_data; } /** - * Get an individual list by ID. + * Contacts of the connected CTCT account. * * @since 1.0.0 * - * @param string $id List ID. - * @return mixed + * @return array Current connect ctct account contacts. */ - public function get_list( string $id ) { - - if ( ! esc_attr( $id ) ) { - return []; - } - + public function get_contacts() { if ( ! $this->is_connected() ) { return []; } - $list = get_transient( 'ctct_list_' . $id ); + $contacts = get_transient( 'ctct_contact' ); - if ( false === $list ) { + if ( false === $contacts ) { try { - $list = $this->cc()->get_list( $id ); - if ( array_key_exists( 'error_key', $list ) && 'unauthorized' === $list['error_key'] ) { + $contacts = $this->cc()->get_contacts(); + if ( array_key_exists( 'error_key', $contacts ) && 'unauthorized' === $contacts['error_key'] ) { $this->refresh_token(); - $list = $this->cc()->get_list( $id ); + $contacts = $this->cc()->get_contacts(); } - set_transient( 'ctct_lists_' . $id, $list, 1 * DAY_IN_SECONDS ); - return $list; + set_transient( 'ctct_contact', $contacts, 1 * DAY_IN_SECONDS ); + return $contacts; } catch ( CtctException $ex ) { add_filter( 'constant_contact_force_logging', '__return_true' ); $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $errors = $ex->getErrors(); $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; - $this->log_errors( $our_errors ); + constant_contact()->get_api_utility()->log_errors( $our_errors ); constant_contact_forms_maybe_set_exception_notice( $ex ); } catch ( Exception $ex ) { $error = new stdClass(); @@ -577,236 +734,43 @@ public function get_list( string $id ) { $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; - $this->log_errors( $our_errors ); + constant_contact()->get_api_utility()->log_errors( $our_errors ); } } - return $list; + return $contacts; } - /** - * Add List to the connected CTCT account. + * Create a new contact or update an existing contact. + * This method uses the email_address string value you include in the + * request body to determine if it should create an new contact or update + * an existing contact. * - * @since 1.0.0 + * @param array $new_contact New contact data. + * @param int $form_id ID of the form being processed. * - * @param array $new_list API data for new list. - * @return array Current connect ctct lists. + * @return array Current connect contact. + * @since 1.3.0 Added $form_id parameter. + * @since 1.0.0 */ - public function add_list( $new_list = [] ) { + public function add_contact( $new_contact = [], $form_id = 0 ) { - if ( empty( $new_list ) || ! isset( $new_list['id'] ) ) { + if ( ! isset( $new_contact['email'] ) ) { return []; } - $return_list = []; + $email = sanitize_email( $new_contact['email'] ); + // Set our list data. If we didn't get passed a list and got this far, just generate a random ID. + $list = $new_contact['list'] ?? 'cc_' . wp_generate_password( 15, false ); + + $return_contact = false; try { - $list = $this->cc()->get_list( esc_attr( $new_list['id'] ) ); - if ( array_key_exists( 'error_key', $list ) && 'unauthorized' === $list['error_key'] ) { - $this->refresh_token(); - $list = $this->cc()->get_list( esc_attr( $new_list['id'] ) ); - } - } catch ( CtctException $ex ) { - add_filter( 'constant_contact_force_logging', '__return_true' ); - $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); - $errors = $ex->getErrors(); - $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; - $this->log_errors( $our_errors ); - constant_contact_forms_maybe_set_exception_notice( $ex ); - } catch ( Exception $ex ) { - $error = new stdClass(); - $error->error_key = get_class( $ex ); - $error->error_message = $ex->getMessage(); - - add_filter( 'constant_contact_force_logging', '__return_true' ); - constant_contact_forms_maybe_set_exception_notice( $ex ); - - $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); - $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; - $this->log_errors( $our_errors ); - } - - if ( ! isset( $list[0]['error_key'] ) ) { - return $list; - } - - try { - - $list = new ContactList(); - - $list->name = isset( $new_list['name'] ) ? esc_attr( $new_list['name'] ) : ''; - - /** - * Filters the list status to use when adding a list. - * - * @since 1.0.0 - * - * @param string $value List status to use. - */ - $list->status = apply_filters( 'constant_contact_list_status', 'HIDDEN' ); - - $return_list = $this->cc()->add_list( (array) $list ); - if ( isset( $return_list[0]['error_message'] ) ) { - // TODO: check why it's not going to catch - throw new Exception( $return_list[0]['error_message'] ); - } - } catch ( CtctException $ex ) { - add_filter( 'constant_contact_force_logging', '__return_true' ); - $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); - $errors = $ex->getErrors(); - $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; - $this->log_errors( $our_errors ); - constant_contact_forms_maybe_set_exception_notice( $ex ); - } catch ( Exception $ex ) { - $error = new stdClass(); - $error->error_key = get_class( $ex ); - $error->error_message = $ex->xdebug_message; - - add_filter( 'constant_contact_force_logging', '__return_true' ); - constant_contact_forms_maybe_set_exception_notice( $ex ); - - $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); - $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; - $this->log_errors( $our_errors ); - } - - return $return_list; - } - - /** - * Update List from the connected CTCT account. - * - * @since 1.0.0 - * - * @param array $updated_list api data for list. - * @return array current connect ctct list - */ - public function update_list( array $updated_list = [] ) { - - $return_list = false; - - try { - - $list = new ContactList(); - - $list->id = isset( $updated_list['id'] ) ? esc_attr( $updated_list['id'] ) : ''; - $list->name = isset( $updated_list['name'] ) ? esc_attr( $updated_list['name'] ) : ''; - $list->favorite = isset( $updated_list['favorite'] ) ? esc_attr( $updated_list['favorite'] ) : false; - - /** - * Filters the list status to use when updating a list. - * - * @since 1.0.0 - * - * @param string $value List status to use. - */ - $list->status = apply_filters( 'constant_contact_list_status', 'HIDDEN' ); - - $return_list = $this->cc()->update_list( (array) $list ); - if ( array_key_exists( 'error_key', $return_list ) && 'unauthorized' === $return_list['error_key'] ) { - $this->refresh_token(); - $return_list = $this->cc()->update_list( $list ); - } - } catch ( CtctException $ex ) { - add_filter( 'constant_contact_force_logging', '__return_true' ); - $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); - $errors = $ex->getErrors(); - $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; - $this->log_errors( $our_errors ); - constant_contact_forms_maybe_set_exception_notice( $ex ); - } catch ( Exception $ex ) { - $error = new stdClass(); - $error->error_key = get_class( $ex ); - $error->error_message = $ex->getMessage(); - - add_filter( 'constant_contact_force_logging', '__return_true' ); - constant_contact_forms_maybe_set_exception_notice( $ex ); - - $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); - $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; - $this->log_errors( $our_errors ); - } - - return $return_list; - } - - /** - * Delete List from the connected CTCT account. - * - * @since 1.0.0 - * - * @param array $updated_list API data for list. - * @return mixed Current connect ctct list. - */ - public function delete_list( array $updated_list = [] ) { - - if ( ! isset( $updated_list['id'] ) ) { - return false; - } - - $list = false; - - try { - $list = $this->cc()->delete_list( $updated_list['id'] ); - if ( array_key_exists( 'error_key', $list ) && 'unauthorized' === $list['error_key'] ) { - $this->refresh_token(); - $list = $this->cc()->delete_list( $updated_list['id'] ); - } - } catch ( CtctException $ex ) { - add_filter( 'constant_contact_force_logging', '__return_true' ); - $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); - $errors = $ex->getErrors(); - $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; - $this->log_errors( $our_errors ); - constant_contact_forms_maybe_set_exception_notice( $ex ); - } catch ( Exception $ex ) { - $error = new stdClass(); - $error->error_key = get_class( $ex ); - $error->error_message = $ex->getMessage(); - - add_filter( 'constant_contact_force_logging', '__return_true' ); - constant_contact_forms_maybe_set_exception_notice( $ex ); - - $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); - $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; - $this->log_errors( $our_errors ); - } - - return $list; - } - - /** - * Create a new contact or update an existing contact. - * This method uses the email_address string value you include in the - * request body to determine if it should create an new contact or update - * an existing contact. - * - * @since 1.0.0 - * @since 1.3.0 Added $form_id parameter. - * - * @param array $new_contact New contact data. - * @param int $form_id ID of the form being processed. - * @return array Current connect contact. - */ - public function add_contact( $new_contact = [], $form_id = 0 ) { - - if ( ! isset( $new_contact['email'] ) ) { - return []; - } - - $email = sanitize_email( $new_contact['email'] ); - // Set our list data. If we didn't get passed a list and got this far, just generate a random ID. - $list = $new_contact['list'] ?? 'cc_' . wp_generate_password( 15, false ); - - $return_contact = false; - - try { - - // Remove ctct-instance if present to avoid errors. - if ( array_key_exists( 'ctct-instance', $new_contact ) ) { - unset( $new_contact['ctct-instance'] ); + // Remove ctct-instance if present to avoid errors. + if ( array_key_exists( 'ctct-instance', $new_contact ) ) { + unset( $new_contact['ctct-instance'] ); } $return_contact = $this->create_update_contact( $list, $email, $new_contact, $form_id ); @@ -835,7 +799,7 @@ public function add_contact( $new_contact = [], $form_id = 0 ) { $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $errors = $ex->getErrors(); $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; - $this->log_errors( $our_errors ); + constant_contact()->get_api_utility()->log_errors( $our_errors ); constant_contact_forms_maybe_set_exception_notice( $ex ); } catch ( Exception $ex ) { $error = new stdClass(); @@ -847,7 +811,7 @@ public function add_contact( $new_contact = [], $form_id = 0 ) { $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; - $this->log_errors( $our_errors ); + constant_contact()->get_api_utility()->log_errors( $our_errors ); } if ( @@ -862,87 +826,27 @@ public function add_contact( $new_contact = [], $form_id = 0 ) { true ) ) { - $new_contact = $this->clear_email( $new_contact ); - $new_contact = $this->clear_phone( $new_contact ); - $new_contact = $this->clear_hcaptcha( $new_contact ); + $new_contact = constant_contact()->get_api_utility()->clear_email( $new_contact ); + $new_contact = constant_contact()->get_api_utility()->clear_phone( $new_contact ); + $new_contact = constant_contact()->get_api_utility()->clear_hcaptcha( $new_contact ); constant_contact_maybe_log_it( 'API', 'Submitted contact data', $new_contact ); } return $return_contact; } - /** - * Obfuscate the left side of email addresses at the `@`. - * - * @since 1.7.0 - * - * @param array $contact Contact data. - * @return array - */ - private function clear_email( array $contact ) { - $clean = []; - foreach ( $contact as $contact_key => $contact_value ) { - if ( is_array( $contact_value ) ) { - $clean[ $contact_key ] = $this->clear_email( $contact_value ); - } elseif ( is_email( $contact_value ) ) { - $email_parts = explode( '@', $contact_value ); - $clean[ $contact_key ] = implode( '@', [ '***', $email_parts[1] ] ); - } else { - $clean[ $contact_key ] = $contact_value; - } - } - return $clean; - } - - /** - * Obfuscate phone numbers. - * - * @author Scott Anderson - * @since 1.13.0 - * - * @param array $contact Contact data. - * @return array - */ - private function clear_phone( array $contact ) { - $clean = $contact; - foreach ( $contact as $contact_key => $contact_value ) { - if ( is_array( $contact_value ) && ! empty( $contact_value['key'] ) && $contact_value['key'] === 'phone_number' ) { - $clean[ $contact_key ]['val'] = '***-***-****'; - } - } - - return $clean; - } - - /** - * Remove hCaptcha data from logged data. - * - * @since 2.9.0 - * - * @param array $contact Contact data. - * @return array - */ - private function clear_hcaptcha( array $contact ) { - if ( array_key_exists( 'h-captcha-response', $contact ) ) { - unset( $contact['h-captcha-response'] ); - } - - return $contact; - } - /** * Helper method to update contact. * - * @since 1.0.0 - * @since 1.3.0 Added $form_id parameter. - * - * @throws CtctException API exception? - * * @param string|array $list List name(s). * @param array $user_data User data. - * @param string $email email to be updated. + * @param string $email email to be updated. * @param string $form_id Form ID being processed. + * * @return mixed Response from API. + * @throws CtctException API exception? + * @since 1.0.0 + * @since 1.3.0 Added $form_id parameter. */ public function create_update_contact( $list, $email, $user_data, $form_id ) { @@ -965,7 +869,7 @@ public function create_update_contact( $list, $email, $user_data, $form_id ) { $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $errors = $ex->getErrors(); $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; - $this->log_errors( $our_errors ); + constant_contact()->get_api_utility()->log_errors( $our_errors ); constant_contact_forms_maybe_set_exception_notice( $ex ); } catch ( Exception $ex ) { $error = new stdClass(); @@ -977,7 +881,7 @@ public function create_update_contact( $list, $email, $user_data, $form_id ) { $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; - $this->log_errors( $our_errors ); + constant_contact()->get_api_utility()->log_errors( $our_errors ); } $new_contact = $this->cc()->create_update_contact( @@ -999,29 +903,32 @@ public function create_update_contact( $list, $email, $user_data, $form_id ) { * Helper method to push as much data from a form as we can into the * Constant Contact contact thats in a list. * - * @since 1.0.0 - * @since 1.3.0 Added $form_id parameter. - * @since 1.4.5 Added $updated paramater. - * * @param object $contact Contact object. * @param array $user_data Bunch of user data. * @param string $form_id Form ID being processed. * @param bool $updated Whether or not we are updating a contact. Default false. - * @throws CtctException $error An exception error. + * * @return object Contact object, with new properties. + * @throws CtctException $error An exception error. + * @since 1.0.0 + * @since 1.3.0 Added $form_id parameter. + * @since 1.4.5 Added $updated paramater. */ public function set_contact_properties( $contact, $user_data, $form_id, $updated = false ) { if ( ! is_object( $contact ) || ! is_array( $user_data ) ) { $error = new CtctException(); - $error->setErrors( [ 'type', esc_html__( 'Not a valid contact to set properties to.', 'constant-contact-forms' ) ] ); + $error->setErrors( [ + 'type', + esc_html__( 'Not a valid contact to set properties to.', 'constant-contact-forms' ) + ] ); throw $error; } unset( $user_data['list'] ); - $address = []; - $count = 1; - $streets = []; + $address = []; + $count = 1; + $streets = []; if ( ! $updated ) { $contact->notes = []; } @@ -1072,7 +979,7 @@ public function set_contact_properties( $contact, $user_data, $form_id, $updated $address['city'] = $value; break; case 'state_address': - $address['state'] = $value; + $address['state'] = $value; if ( empty( $address['country'] ) ) { $address['country'] = 'United States'; } @@ -1136,7 +1043,7 @@ public function set_contact_properties( $contact, $user_data, $form_id, $updated ]; } - $count++; + $count ++; break; default: try { @@ -1145,7 +1052,7 @@ public function set_contact_properties( $contact, $user_data, $form_id, $updated $errors = []; $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $errors[] = $extra . $e->getErrors(); - $this->log_errors( $errors ); + constant_contact()->get_api_utility()->log_errors( $errors ); constant_contact_forms_maybe_set_exception_notice( $e ); break; } @@ -1166,521 +1073,528 @@ public function set_contact_properties( $contact, $user_data, $form_id, $updated } /** - * Pushes all error to api_error_message. + * Lists of the connected CTCT account. * * @since 1.0.0 * - * @throws Exception Throws Exception if encountered while attempting to log errors. - * - * @param array $errors Errors from API. + * @param bool $force_skip_cache Whether or not to skip cache. + * @return array Current connect ctct lists. */ - public function log_errors( $errors ) { - if ( is_array( $errors ) ) { - foreach ( $errors as $error ) { - constant_contact_maybe_log_it( - 'API', - $error - ); - } + public function get_lists( bool $force_skip_cache = false ) { + + if ( ! $this->is_connected() ) { + return []; } - } - /** - * Make sure we don't over-do API requests, helper method to check if we're connected. - * - * @since 1.0.0 - * - * @return boolean If connected. - */ - public function is_connected() { - static $token = null; + $lists = get_transient( 'ctct_lists' ); - if ( constant_contact()->get_connect()->e_get( '_ctct_access_token' ) ) { - $token = constant_contact()->get_connect()->e_get( '_ctct_access_token' ) ? true : false; + if ( $force_skip_cache ) { + $lists = false; } - return $token; - } + if ( false === $lists ) { - /** - * Helper method to output a link for our settings page tabs. - * - * @since 2022-10-24 - * @return string Settings tab URL. - */ - public function get_settings_link( $settings_tab = 'ctct_options_settings_general' ) { + try { + $results = $this->cc()->get_lists(); + $lists = $results['lists'] ?? []; - return add_query_arg( - [ - 'post_type' => 'ctct_forms', - 'page' => sanitize_text_field( $settings_tab ), - ], - admin_url( 'edit.php' ) - ); - } + if ( array_key_exists( 'error_key', $results ) && 'unauthorized' === $results['error_key'] ) { + $this->refresh_token(); - /** - * Helper method to output a link for our connect modal. - * - * @since 1.0.0 - * - * @return string Signup URL. - */ - public function get_signup_link() { - return 'https://www.constantcontact.com/signup'; + $results = $this->cc()->get_lists(); + $lists = $results['lists'] ?? []; + } + + if ( ! empty( $lists ) ) { + set_transient( 'ctct_lists', $lists, 12 * HOUR_IN_SECONDS ); + return $lists; + } elseif ( array_key_exists( 'error_key', $results ) ) { + set_transient( 'ctct_lists', $lists, DAY_IN_SECONDS ); + add_filter( 'constant_contact_force_logging', '__return_true' ); + $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); + $our_errors[] = $extra . $results['error_key'] . ': ' . $results['error_message']; + constant_contact()->get_api_utility()->log_errors($our_errors); + constant_contact_forms_maybe_set_exception_notice(); + } + } catch ( CtctException $ex ) { + add_filter( 'constant_contact_force_logging', '__return_true' ); + $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); + $errors = $ex->getErrors(); + $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; + constant_contact()->get_api_utility()->log_errors( $our_errors ); + constant_contact_forms_maybe_set_exception_notice( $ex ); + } catch ( Exception $ex ) { + $error = new stdClass(); + $error->error_key = get_class( $ex ); + $error->error_message = $ex->getMessage(); + + add_filter( 'constant_contact_force_logging', '__return_true' ); + constant_contact_forms_maybe_set_exception_notice( $ex ); + + $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); + $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; + constant_contact()->get_api_utility()->log_errors( $our_errors ); + } + } + + return $lists; } /** - * Maybe get the disclosure address from the API Organization Information. + * Get v2 to v3 API lists of the connected CTCT account. * - * @since 1.0.0 + * @since 2.0.0 * - * @param bool $as_parts If true return an array. - * @return mixed + * @param string $old_ids_string Comma separated list of old (v2 API) list ids. + * @param bool $force_skip_cache Whether or not to skip cache. + * + * @return array API v2 to v3 List ID cross references. */ - public function get_disclosure_info( $as_parts = false ) { - /* - * [ - * [name] => Business Name - * [address] => 555 Business Place Ln., Beverly Hills, CA, 90210 - * ] - */ - - static $address_fields = [ 'address_line1', 'address_line2', 'address_line3', 'city', 'state_code', 'postal_code' ]; - - // Grab disclosure info from the API. - $account_info = $this->get_account_info(); + public function get_v2_list_id_x_refs( string $old_ids_string, bool $force_skip_cache = false ) { - if ( empty( $account_info ) ) { - return $as_parts ? [] : ''; + if ( ! $this->is_connected() ) { + return []; } - $disclosure = [ - 'name' => empty( $account_info->organization_name ) ? constant_contact_get_option( '_ctct_disclose_name', '' ) : $account_info->organization_name, - 'address' => constant_contact_get_option( '_ctct_disclose_address', '' ), - ]; + $list_x_refs = get_transient('ctct_list_xrefs'); - if ( empty( $disclosure['name'] ) ) { - return $as_parts ? [] : ''; + if ( $force_skip_cache ) { + $list_x_refs = false; } - // Determine the address to use for disclosure from the API. - if ( - isset( $account_info['physical_address'] ) - && count( $account_info['physical_address'] ) - ) { - $organization_address = $account_info['physical_address']; - $disclosure_address = []; + if ( false === $list_x_refs ) { - if ( is_array( $address_fields ) ) { - foreach ( $address_fields as $field ) { - if ( isset( $organization_address[ $field ] ) && strlen( $organization_address[ $field ] ) ) { - $disclosure_address[] = $organization_address[ $field ]; - } + try { + $list_x_refs = $this->cc()->get_updated_lists_ids( $old_ids_string ); + if ( is_array( $list_x_refs ) ) { + set_transient('ctct_list_xrefs', $list_x_refs, HOUR_IN_SECONDS ); + return $list_x_refs; } - } + } catch ( CtctException $ex ) { + add_filter( 'constant_contact_force_logging', '__return_true' ); + $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); + $errors = $ex->getErrors(); + $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; + constant_contact()->get_api_utility()->log_errors( $our_errors ); + constant_contact_forms_maybe_set_exception_notice( $ex ); + } catch ( Exception $ex ) { + $error = new stdClass(); + $error->error_key = get_class( $ex ); + $error->error_message = $ex->getMessage(); - $disclosure['address'] = implode( ', ', $disclosure_address ); - } elseif ( empty( $disclosure['address'] ) ) { - unset( $disclosure['address'] ); - } + add_filter( 'constant_contact_force_logging', '__return_true' ); + constant_contact_forms_maybe_set_exception_notice( $ex ); - if ( ! empty( $account_info['website'] ) ) { - $disclosure['website'] = $account_info['website']; + $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); + $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; + constant_contact()->get_api_utility()->log_errors( $our_errors ); + } } - return $as_parts ? $disclosure : implode( ', ', array_values( $disclosure ) ); + return $list_x_refs; } /** - * Generate code_verifier and code_challenge for rfc7636 PKCE. - * https://datatracker.ietf.org/doc/html/rfc7636#appendix-B + * Get an individual list by ID. * - * @return array [code_verifier, code_challenge]. + * @since 1.0.0 + * + * @param string $id List ID. + * @return mixed */ - private function code_challenge( ?string $code_verifier = null ): array { - $gen = static function () { - $strings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~'; - $length = random_int( 43, 128 ); + public function get_list( string $id ) { - for ( $i = 0; $i < $length; $i++ ) { - yield $strings[ random_int( 0, 65 ) ]; - } - }; + if ( ! esc_attr( $id ) ) { + return []; + } - $code = $code_verifier ?? implode( '', iterator_to_array( $gen() ) ); + if ( ! $this->is_connected() ) { + return []; + } - if ( ! \preg_match( '/[A-Za-z0-9-._~]{43,128}/', $code ) ) { - return [ '', '' ]; + $list = get_transient( 'ctct_list_' . $id ); + + if ( false === $list ) { + try { + $list = $this->cc()->get_list( $id ); + if ( array_key_exists( 'error_key', $list ) && 'unauthorized' === $list['error_key'] ) { + $this->refresh_token(); + + $list = $this->cc()->get_list( $id ); + } + + set_transient( 'ctct_lists_' . $id, $list, 1 * DAY_IN_SECONDS ); + return $list; + } catch ( CtctException $ex ) { + add_filter( 'constant_contact_force_logging', '__return_true' ); + $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); + $errors = $ex->getErrors(); + $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; + constant_contact()->get_api_utility()->log_errors( $our_errors ); + constant_contact_forms_maybe_set_exception_notice( $ex ); + } catch ( Exception $ex ) { + $error = new stdClass(); + $error->error_key = get_class( $ex ); + $error->error_message = $ex->getMessage(); + + add_filter( 'constant_contact_force_logging', '__return_true' ); + constant_contact_forms_maybe_set_exception_notice( $ex ); + + $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); + $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; + constant_contact()->get_api_utility()->log_errors( $our_errors ); + } } - return [ $code, $this->base64url_encode( pack( 'H*', hash( 'sha256', $code ) ) ) ]; + return $list; } - /** - * Base64 encode URL. - * - * @since 2.0.0 - * - * @param string $data - * - * @return string - */ - private function base64url_encode( string $data ): string { - return rtrim( strtr( base64_encode( $data ), '+/', '-_' ), '=' ); - } /** - * Handle user session details. - * - * Not used. - * - * @since 2.0.0 + * Add List to the connected CTCT account. * - * @param string $key - * @param string|null $value + * @since 1.0.0 * - * @return mixed|string + * @param array $new_list API data for new list. + * @return array Current connect ctct lists. */ - public function session( string $key, ?string $value ) { - if ( $this->session_callback ) { - return call_user_func( $this->session_callback, $key, $value ); - } - if ( null === $value ) { - $value = get_user_meta( $this->this_user_id, $key, true ); - delete_user_meta( $this->this_user_id, $key, $value ); + public function add_list( $new_list = [] ) { - return $value; + if ( empty( $new_list ) || ! isset( $new_list['id'] ) ) { + return []; } - update_user_meta( $this->this_user_id, $key, $value ); + $return_list = []; - return $value; - } + try { + $list = $this->cc()->get_list( esc_attr( $new_list['id'] ) ); + if ( array_key_exists( 'error_key', $list ) && 'unauthorized' === $list['error_key'] ) { + $this->refresh_token(); - /** - * Generate the URL an account owner would use to allow your app - * to access their account. - * - * After visiting the URL, the account owner is prompted to log in and allow your app to access their account. - * They are then redirected to your redirect URL with the authorization code appended as a query parameter. e.g.: - * http://localhost:8888/?code={authorization_code} - */ - public function get_authorization_url(): string { + $list = $this->cc()->get_list( esc_attr( $new_list['id'] ) ); + } + } catch ( CtctException $ex ) { + add_filter( 'constant_contact_force_logging', '__return_true' ); + $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); + $errors = $ex->getErrors(); + $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; + constant_contact()->get_api_utility()->log_errors( $our_errors ); + constant_contact_forms_maybe_set_exception_notice( $ex ); + } catch ( Exception $ex ) { + $error = new stdClass(); + $error->error_key = get_class( $ex ); + $error->error_message = $ex->getMessage(); - $auth_url = get_option( 'ctct_auth_url' ); + add_filter( 'constant_contact_force_logging', '__return_true' ); + constant_contact_forms_maybe_set_exception_notice( $ex ); - if ( $auth_url ) { - return $auth_url; + $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); + $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; + constant_contact()->get_api_utility()->log_errors( $our_errors ); } - $scopes = implode( '+', array_keys( $this->scopes ) ); - [$code_verifier, $code_challenge] = $this->code_challenge(); - - $state = bin2hex( random_bytes( 8 ) ); + if ( ! isset( $list[0]['error_key'] ) ) { + return $list; + } - update_option( 'CtctConstantContactState', $state ); + try { - $params = [ - 'client_id' => $this->client_api_key, - 'redirect_uri' => $this->redirect_URI, - 'response_type' => 'code', - 'code_challenge' => $code_challenge, - 'code_challenge_method' => 'S256', - 'state' => $state, - 'scope' => $scopes, - ]; + $list = new ContactList(); - // Store generated random state and code challenge based on RFC 7636 - // https://datatracker.ietf.org/doc/html/rfc7636#section-6.1 - update_option( 'CtctConstantContactcode_verifier', $code_verifier ); + $list->name = isset( $new_list['name'] ) ? esc_attr( $new_list['name'] ) : ''; - $url = add_query_arg( $params, $this->authorize_url ); + /** + * Filters the list status to use when adding a list. + * + * @since 1.0.0 + * + * @param string $value List status to use. + */ + $list->status = apply_filters( 'constant_contact_list_status', 'HIDDEN' ); - update_option( 'ctct_auth_url', $url ); + $return_list = $this->cc()->add_list( (array) $list ); + if ( isset( $return_list[0]['error_message'] ) ) { + // TODO: check why it's not going to catch + throw new Exception( $return_list[0]['error_message'] ); + } + } catch ( CtctException $ex ) { + add_filter( 'constant_contact_force_logging', '__return_true' ); + $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); + $errors = $ex->getErrors(); + $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; + constant_contact()->get_api_utility()->log_errors( $our_errors ); + constant_contact_forms_maybe_set_exception_notice( $ex ); + } catch ( Exception $ex ) { + $error = new stdClass(); + $error->error_key = get_class( $ex ); + $error->error_message = $ex->xdebug_message; - return $url; - } + add_filter( 'constant_contact_force_logging', '__return_true' ); + constant_contact_forms_maybe_set_exception_notice( $ex ); - /** - * Add contact to one or more lists. - * - * @author Rebekah Van Epps - * @since 1.9.0 - * @todo Update addList to use v3 - * - * @param Contact $contact Contact object. - * @param string|array $list Single list ID or array of lists. - * @return void - */ - private function add_to_list( $contact, $list ) { - if ( empty( $list ) ) { - return; + $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); + $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; + constant_contact()->get_api_utility()->log_errors( $our_errors ); } - $list = is_array( $list ) ? $list : [ $list ]; - - foreach ( $list as $list_id ) { - $contact->list_memberships[] = esc_attr( $list_id ); - } + return $return_list; } /** - * Exchange an authorization code for an access token. + * Update List from the connected CTCT account. * - * Make this call by passing in the code present when the account owner is redirected back to you. - * The response will contain an 'access_token' and 'refresh_token' + * @since 1.0.0 * - * @param array of get parameters passed to redirect URL + * @param array $updated_list api data for list. + * @return array current connect ctct list */ - public function acquire_access_token(): bool { + public function update_list( array $updated_list = [] ) { - if ( ! empty( $_POST['action'] ) && 'heartbeat' === sanitize_text_field( $_POST['action'] ) ) { - return false; - } + $return_list = false; - if ( ! empty( $_POST['ctct-disconnect'] ) && 'true' === sanitize_text_field( $_POST['ctct-disconnect'] ) ) { - return false; - } - $options = get_option('ctct_options_settings'); - if ( empty( $options ) ) { - return false; - } + try { - if ( empty( $options['_ctct_form_state_authcode'] ) ) { - return false; - } + $list = new ContactList(); - $code_state = $options['_ctct_form_state_authcode']; + $list->id = isset( $updated_list['id'] ) ? esc_attr( $updated_list['id'] ) : ''; + $list->name = isset( $updated_list['name'] ) ? esc_attr( $updated_list['name'] ) : ''; + $list->favorite = isset( $updated_list['favorite'] ) ? esc_attr( $updated_list['favorite'] ) : false; - parse_str( $code_state, $parsed_code_state ); - $parsed_code_state = array_values( $parsed_code_state ); + /** + * Filters the list status to use when updating a list. + * + * @since 1.0.0 + * + * @param string $value List status to use. + */ + $list->status = apply_filters( 'constant_contact_list_status', 'HIDDEN' ); - if ( empty( $parsed_code_state[0] ) || empty( $parsed_code_state[1] ) ) { - $this->status_code = 0; - $this->last_error = 'Invalid state or auth code'; - constant_contact_maybe_log_it( 'Error: ', $this->last_error ); - return false; - } else { - $code = $parsed_code_state[0]; - $state = $parsed_code_state[1]; - } + $return_list = $this->cc()->update_list( (array) $list ); + if ( array_key_exists( 'error_key', $return_list ) && 'unauthorized' === $return_list['error_key'] ) { + $this->refresh_token(); + $return_list = $this->cc()->update_list( $list ); + } + } catch ( CtctException $ex ) { + add_filter( 'constant_contact_force_logging', '__return_true' ); + $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); + $errors = $ex->getErrors(); + $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; + constant_contact()->get_api_utility()->log_errors( $our_errors ); + constant_contact_forms_maybe_set_exception_notice( $ex ); + } catch ( Exception $ex ) { + $error = new stdClass(); + $error->error_key = get_class( $ex ); + $error->error_message = $ex->getMessage(); - $expected_state = get_option( 'CtctConstantContactState' ); + add_filter( 'constant_contact_force_logging', '__return_true' ); + constant_contact_forms_maybe_set_exception_notice( $ex ); - if ( ( $state ?? 'undefined' ) != $expected_state ) { - $this->status_code = 0; - $this->last_error = 'state is not correct'; - constant_contact_maybe_log_it( 'Error: ', $this->last_error ); - return false; + $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); + $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; + constant_contact()->get_api_utility()->log_errors( $our_errors ); } - // Create full request URL - $body = [ - 'client_id' => $this->client_api_key, - 'code' => $code, - 'redirect_uri' => $this->redirect_URI, - 'grant_type' => 'authorization_code', - ]; - - $body['code_verifier'] = get_option( 'CtctConstantContactcode_verifier' ); - $headers = $this->set_authorization(); - - $url = $this->oauth2_url; + return $return_list; + } - $options = [ - 'body' => $body, - 'headers' => $headers, - ]; + /** + * Delete List from the connected CTCT account. + * + * @since 1.0.0 + * + * @param array $updated_list API data for list. + * @return mixed Current connect ctct list. + */ + public function delete_list( array $updated_list = [] ) { - // This will be either true or false. - $result = $this->exec( $url, $options ); + if ( ! isset( $updated_list['id'] ) ) { + return false; + } - if ( false === $result ) { - constant_contact_set_needs_manual_reconnect( 'true' ); - } else { + $list = false; - /** - * Fires after successful access token acquisition. - * - * @since 2.3.0 - */ - do_action( 'ctct_access_token_acquired' ); + try { + $list = $this->cc()->delete_list( $updated_list['id'] ); + if ( array_key_exists( 'error_key', $list ) && 'unauthorized' === $list['error_key'] ) { + $this->refresh_token(); + $list = $this->cc()->delete_list( $updated_list['id'] ); + } + } catch ( CtctException $ex ) { + add_filter( 'constant_contact_force_logging', '__return_true' ); + $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); + $errors = $ex->getErrors(); + $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; + constant_contact()->get_api_utility()->log_errors( $our_errors ); + constant_contact_forms_maybe_set_exception_notice( $ex ); + } catch ( Exception $ex ) { + $error = new stdClass(); + $error->error_key = get_class( $ex ); + $error->error_message = $ex->getMessage(); - constant_contact_set_needs_manual_reconnect( 'false' ); - } + add_filter( 'constant_contact_force_logging', '__return_true' ); + constant_contact_forms_maybe_set_exception_notice( $ex ); + $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); + $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; + constant_contact()->get_api_utility()->log_errors( $our_errors ); + } - return $result; + return $list; } /** - * Refresh the access token. + * Add contact to one or more lists. * - * @since 2.0.0 + * @param Contact $contact Contact object. + * @param string|array $list Single list ID or array of lists. * - * @return array - * @throws Exception + * @return void + * @author Rebekah Van Epps + * @since 1.9.0 + * @todo Update addList to use v3 */ - public function refresh_token() { - - $status = []; - // Force prevent any further attempts until humans interject. - if ( constant_contact_get_needs_manual_reconnect() ) { - $status['success'] = false; - $status['reason'] = 'manual_reconnect'; - - return $status; - } - - $token = constant_contact()->get_connect()->e_get( '_ctct_refresh_token' ); - if ( empty( $token ) ) { - $status['success'] = false; - $status['reason'] = 'no available token'; - return $status; + private function add_to_list( $contact, $list ) { + if ( empty( $list ) ) { + return; } - constant_contact_maybe_log_it( 'Refresh Token:', 'Refresh token triggered' ); - - // Create full request URL - $body = [ - 'client_id' => $this->client_api_key, - 'refresh_token' => constant_contact()->get_connect()->e_get( '_ctct_refresh_token' ), - 'redirect_uri' => $this->redirect_URI, - 'grant_type' => 'refresh_token', - ]; - - $url = $this->oauth2_url; - $headers = $this->set_authorization(); - - $options = [ - 'body' => $body, - 'headers' => $headers, - ]; - - $result = $this->exec( $url, $options ); - - if ( false === $result ) { - constant_contact_maybe_log_it( 'Refresh Token:', 'Expired. Refresh attempted at ' . current_datetime()->format( 'Y-n-d, H:i' ) ); - constant_contact_set_needs_manual_reconnect( 'true' ); - $status['success'] = false; - $status['reason'] = 'expired'; - } else { - delete_transient( 'ctct_lists' ); - update_option( 'ctct_access_token_timestamp', time() ); - constant_contact_set_needs_manual_reconnect( 'false' ); + $list = is_array( $list ) ? $list : [ $list ]; - $status['success'] = true; - $status['reason'] = 'refreshed'; + foreach ( $list as $list_id ) { + $contact->list_memberships[] = esc_attr( $list_id ); } - - return $status; } /** - * Set our authorization headers. + * Helper method to output a link for our settings page tabs. * - * @since 2.0.0 - * @return string[] + * @since 2022-10-24 + * @return string Settings tab URL. */ - private function set_authorization(): array { - - // Set authorization header - // Make string of "API_KEY:SECRET" - $auth = $this->client_api_key; - // Base64 encode it - $credentials = base64_encode( $auth ); - // Create and set the Authorization header to use the encoded credentials - $headers = [ 'Authorization: Basic ' . $credentials, 'cache-control: no-cache' ]; + public function get_settings_link( $settings_tab = 'ctct_options_settings_general' ) { - return $headers; + return add_query_arg( + [ + 'post_type' => 'ctct_forms', + 'page' => sanitize_text_field( $settings_tab ), + ], + admin_url( 'edit.php' ) + ); } /** - * Execute our API request for token acquisition. - * - * @since 2.0.0 + * Maybe get the disclosure address from the API Organization Information. * - * @param string $url URL to make request to. - * @param array $options Request options. + * @since 1.0.0 * - * @return bool - * @throws Exception + * @param bool $as_parts If true return an array. + * @return mixed */ - private function exec( $url, $options ): bool { - $response = wp_safe_remote_post( $url, $options ); + public function get_disclosure_info( $as_parts = false ) { + /* + * [ + * [name] => Business Name + * [address] => 555 Business Place Ln., Beverly Hills, CA, 90210 + * ] + */ - $this->last_error = ''; - $this->status_code = 0; + static $address_fields = [ 'address_line1', 'address_line2', 'address_line3', 'city', 'state_code', 'postal_code' ]; - if ( ! is_wp_error( $response ) ) { + // Grab disclosure info from the API. + $account_info = $this->get_account_info(); - $data = json_decode( $response['body'], true ); - $json_last_error = json_last_error(); - if ( JSON_ERROR_NONE !== $json_last_error ) { - constant_contact_maybe_log_it( 'JSON Error: ', json_last_error_msg() ); - } + if ( empty( $account_info ) ) { + return $as_parts ? [] : ''; + } - // check if the body contains error - if ( isset( $data['error'] ) ) { - if ( 'invalid_grant' === $data['error'] ) { - $this->api_errors_admin_email(); + $disclosure = [ + 'name' => empty( $account_info->organization_name ) ? constant_contact_get_option( '_ctct_disclose_name', '' ) : $account_info->organization_name, + 'address' => constant_contact_get_option( '_ctct_disclose_address', '' ), + ]; + + if ( empty( $disclosure['name'] ) ) { + return $as_parts ? [] : ''; + } + + // Determine the address to use for disclosure from the API. + if ( + isset( $account_info['physical_address'] ) + && count( $account_info['physical_address'] ) + ) { + $organization_address = $account_info['physical_address']; + $disclosure_address = []; + + if ( is_array( $address_fields ) ) { + foreach ( $address_fields as $field ) { + if ( isset( $organization_address[ $field ] ) && strlen( $organization_address[ $field ] ) ) { + $disclosure_address[] = $organization_address[ $field ]; + } } - $this->last_error = $data['error'] . ': ' . ( $data['error_description'] ?? 'Undefined' ); - constant_contact_maybe_log_it( 'Error: ', $this->last_error ); - return false; } - if ( ! empty( $data['access_token'] ) ) { + $disclosure['address'] = implode( ', ', $disclosure_address ); + } elseif ( empty( $disclosure['address'] ) ) { + unset( $disclosure['address'] ); + } - constant_contact_maybe_log_it( 'Refresh Token: ', 'Old Refresh Token: ' . $this->obfuscate_api_data_item( $this->refresh_token ) ); - constant_contact_maybe_log_it( 'Access Token: ', 'Old Access Token: ' . $this->obfuscate_api_data_item( $this->access_token ) ); + if ( ! empty( $account_info['website'] ) ) { + $disclosure['website'] = $account_info['website']; + } - constant_contact()->get_connect()->e_set( '_ctct_access_token', $data['access_token'] ); - constant_contact()->get_connect()->e_set( '_ctct_refresh_token', $data['refresh_token'] ); - constant_contact()->get_connect()->e_set( '_ctct_expires_in', (string) $data['expires_in'] ); + return $as_parts ? $disclosure : implode( ', ', array_values( $disclosure ) ); + } - $this->access_token = $data['access_token'] ?? ''; - $this->refresh_token = $data['refresh_token'] ?? ''; - $this->expires_in = $data['expires_in'] ?? ''; + /** + * Generate code_verifier and code_challenge for rfc7636 PKCE. + * https://datatracker.ietf.org/doc/html/rfc7636#appendix-B + * + * @return array [code_verifier, code_challenge]. + */ + private function code_challenge( ?string $code_verifier = null ): array { + $gen = static function () { + $strings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~'; + $length = random_int( 43, 128 ); - delete_option( 'ctct_auth_url' ); - $dateObj = current_datetime(); - $expDateObj = $dateObj->modify( '+' . $data['expires_in'] . ' seconds' ); + for ( $i = 0; $i < $length; $i++ ) { + yield $strings[ random_int( 0, 65 ) ]; + } + }; - constant_contact_maybe_log_it( 'Refresh Token: ', 'Refresh token successfully received' ); - constant_contact_maybe_log_it( 'Refresh Token: ', 'New Refresh Token: ' . $this->obfuscate_api_data_item( $this->refresh_token ) ); - constant_contact_maybe_log_it( 'Access Token: ', 'New Access Token: ' . $this->obfuscate_api_data_item( $this->access_token ) ); - constant_contact_maybe_log_it( - 'Expiration time:', - 'Current time: ' . $dateObj->format( 'Y-n-d, H:i' ) . ' Estimated expiration time: ' . $expDateObj->format( 'Y-n-d, H:i' ) - ); + $code = $code_verifier ?? implode( '', iterator_to_array( $gen() ) ); - return isset( $data['access_token'], $data['refresh_token'] ); - } - } else { - $this->status_code = 0; - $this->last_error = $response->get_error_message(); - constant_contact_maybe_log_it( 'Error: ', $this->last_error ); + if ( ! \preg_match( '/[A-Za-z0-9-._~]{43,128}/', $code ) ) { + return [ '', '' ]; } - return false; + return [ $code, constant_contact()->get_api_utility()->base64url_encode( pack( 'H*', hash( 'sha256', $code ) ) ) ]; } /** - * Obfuscate a value in our debug logs. + * Handle user session details. * - * Helps keep things private and not put into a potentially publicly accessed file. + * Not used. * - * @since 2.1.0 + * @since 2.0.0 * - * @param string $data_item Item to obfuscate. + * @param string $key + * @param string|null $value * - * @return string + * @return mixed|string */ - private function obfuscate_api_data_item( string $data_item ): string { - $start = substr( $data_item, 0, 8 ); - return $start . '***'; + public function session( string $key, ?string $value ) { + if ( $this->session_callback ) { + return call_user_func( $this->session_callback, $key, $value ); + } + if ( null === $value ) { + $value = get_user_meta( $this->this_user_id, $key, true ); + delete_user_meta( $this->this_user_id, $key, $value ); + + return $value; + } + + update_user_meta( $this->this_user_id, $key, $value ); + + return $value; } /** @@ -1729,31 +1643,6 @@ private function get_note_content( $submission_data ) { return $note; } - /** - * Check if our current access token is expired. - * - * Based on access token issued timestamp + expires in timestamp and current time. - * - * @since 2.2.0 - * - * @return bool - */ - private function access_token_maybe_expired() { - - $issued_time = get_option( 'ctct_access_token_timestamp', '' ); - if ( empty( $issued_time ) ) { - // It's not expired because it doesn't exist. - // This should be filled in by now though. - return false; - } - - $current_time = time(); - $expiration_time = (int) $issued_time + (int) $this->expires_in; - - // If we're currently above the expiration time, we're expired. - return $current_time >= $expiration_time; - } - /** * Logs a missed API request to our overall log of missed requests. * @@ -1884,16 +1773,3 @@ public function set_email_type() { return 'text/html'; } } - -/** - * Helper function to get/return the ConstantContact_API object. - * - * @since 1.0.0 - * @deprecated 2.11.0 - * - * @return object ConstantContact_API - */ -function constantcontact_api() { - _deprecated_function( __FUNCTION__, '2.11.0', 'constant_contact()->get_api()' ); - return constant_contact()->get_api(); -} diff --git a/includes/class-builder.php b/includes/class-builder.php index 2788776e..c2324206 100644 --- a/includes/class-builder.php +++ b/includes/class-builder.php @@ -313,7 +313,7 @@ class="ctct-modal-flare"

-
+
diff --git a/includes/class-connect.php b/includes/class-connect.php index 247cf446..4ad06249 100644 --- a/includes/class-connect.php +++ b/includes/class-connect.php @@ -149,10 +149,7 @@ public function admin_page_display() { wp_localize_script( 'ctct_form', 'ctctTexts', [ 'disconnectconfirm' => esc_html__( 'Are you sure you want to disconnect?', 'constant-contact-forms' ) ] ); wp_enqueue_script( 'ctct_form' ); - ?> -
- - get_api()->get_api_token() ) : + if ( constant_contact()->get_api()->get_api_token() ) : $heading = esc_html__( 'Account connected!', 'constant-contact-forms' ); $description = esc_html__( 'You are connected to the Constant Contact account shown below.', 'constant-contact-forms' ); @@ -161,19 +158,20 @@ public function admin_page_display() { $description = esc_html__( 'Issues with reauthentication for tokens occurred and a manual disconnect and reconnect is needed.', 'constant-contact-forms' ); } ?> -
-
-

-

- -

-
-

- +

+
+
+

+

+

-

- +

+ +

+

+ get_api()->get_account_info(); @@ -190,67 +188,72 @@ public function admin_page_display() { } catch ( Exception $ex ) { esc_html_e( 'There was an issue with retrieving connected account information. Please try again.', 'constant-contact-forms' ); } - ?> -

-
-
-

- -

-

- contact_email ) . '">' . esc_html( $account->contact_email ) . ''; - } - ?> -

-
-
-

- -

-
- - - -
+ ?> +

+
+
+

+ +

+

+ contact_email ) . '">' . esc_html( $account->contact_email ) . ''; + } + ?> +

+
+
+

+ +

+
+ + + +
+
-
- -
- - -
-
-

-

-

- -
-
- -

-

- - +
+ + + +
+
+

+

+

+ +
+
+ +

+

+ + +
-
- + -

- -

- get_api()->get_authorization_url(); + $auth_link = add_query_arg( + [ + 'rmc' => 'wp_connect_connect' + ], + $auth_link + ); + + $code_link = add_query_arg( + [ + 'post_type' => 'ctct_forms', + 'page' => 'ctct_options_settings_auth', + ], + admin_url( 'edit.php' ) + ); $env_types = [ 'local', 'development', 'staging' ]; $duplicate_account_notification = ''; @@ -260,81 +263,80 @@ public function admin_page_display() { ); } ?> - -

-

- -

+
-

- - - -

- -
- +
+ get_api()->get_signup_link(); + $try_url = constant_contact()->get_api_utility()->get_signup_link(); if ( ! empty( $_GET['page'] ) && 'ctct_options_connect' === sanitize_text_field( $_GET['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return ''; diff --git a/includes/class-uninstall.php b/includes/class-uninstall.php index e1b23216..59b31e12 100644 --- a/includes/class-uninstall.php +++ b/includes/class-uninstall.php @@ -62,6 +62,7 @@ private function get_option_names() { 'ctct_api_v2_v3_migrated', 'ctct_missed_api_requests', 'ctct_log_suffix', + 'ctct_refresh_failures', Constant_Contact::$activated_date_option, ConstantContact_Notifications::$dismissed_notices_option, ConstantContact_Notifications::$review_dismissed_option, diff --git a/includes/deprecated.php b/includes/deprecated.php index fd3dec55..91e14c7c 100644 --- a/includes/deprecated.php +++ b/includes/deprecated.php @@ -58,3 +58,15 @@ function ctct_has_forms() { return constant_contact_has_forms(); } + +/** + * Helper function to get/return the ConstantContact_API object. + * @return object ConstantContact_API + * @since 1.0.0 + * @deprecated 2.11.0 + */ +function constantcontact_api() { + _deprecated_function( __FUNCTION__, '2.11.0', 'constant_contact()->get_api()' ); + + return constant_contact()->get_api(); +} diff --git a/includes/helper-functions.php b/includes/helper-functions.php index 6acf5907..48fe6d3d 100644 --- a/includes/helper-functions.php +++ b/includes/helper-functions.php @@ -633,7 +633,10 @@ function constant_contact_get_widgets_by_form( $form_id ) { get_option( "widget_{$widget_type}", [] ), function( $value ) use ( $data ) { if ( 'ctct_form' === $data['type'] ) { - return absint( $value['ctct_form_id'] ) === $data['form_id']; + // We get this primarily from classic widgets as opposed to widget blocks. + if ( is_array( $value ) && array_key_exists( 'ctct_form_id', $value ) ) { + return absint( $value['ctct_form_id'] ) === $data['form_id']; + } } elseif ( 'text' === $data['type'] ) { if ( ! isset( $value['text'] ) || false === strpos( $value['text'], '[ctct' ) ) { return false; diff --git a/includes/notification-logic.php b/includes/notification-logic.php index b0c79f27..62289e97 100644 --- a/includes/notification-logic.php +++ b/includes/notification-logic.php @@ -16,7 +16,7 @@ * @return bool * @since 1.2.2 */ -function constant_contact_maybe_display_review_notification() : bool { +function constant_contact_maybe_display_review_notification(): bool { if ( ! current_user_can( 'manage_options' ) ) { return false; @@ -95,7 +95,7 @@ function constant_contact_maybe_display_review_notification() : bool { * @return bool * @since 1.6.0 */ -function constant_contact_maybe_display_exceptions_notice() : bool { +function constant_contact_maybe_display_exceptions_notice(): bool { if ( ! current_user_can( 'manage_options' ) ) { return false; } @@ -110,7 +110,7 @@ function constant_contact_maybe_display_exceptions_notice() : bool { * @return bool Whether to display the deleted forms notice. * @since 1.8.0 */ -function constant_contact_maybe_display_deleted_forms_notice() : bool { +function constant_contact_maybe_display_deleted_forms_notice(): bool { if ( ! current_user_can( 'manage_options' ) ) { return false; } @@ -153,7 +153,7 @@ function constant_contact_forms_maybe_set_exception_notice( $e = '' ) { * @return bool * @since 1.14.0 */ -function constant_contact_maybe_display_api3_upgrade_notice() : bool { +function constant_contact_maybe_display_api3_upgrade_notice(): bool { if ( ! current_user_can( 'manage_options' ) ) { return false; } @@ -168,7 +168,7 @@ function constant_contact_maybe_display_api3_upgrade_notice() : bool { * @return bool|int * @since 2.0.0 */ -function constant_contact_maybe_display_api3_upgraded_notice() : bool { +function constant_contact_maybe_display_api3_upgraded_notice(): bool { if ( ! current_user_can( 'manage_options' ) ) { return false; } @@ -190,7 +190,7 @@ function constant_contact_maybe_display_api3_upgraded_notice() : bool { * @return bool * @since 2.2.0 */ -function constant_contact_maybe_display_disconnect_reconnect_notice() : bool { +function constant_contact_maybe_display_disconnect_reconnect_notice(): bool { if ( ! current_user_can( 'manage_options' ) ) { return false; } @@ -203,7 +203,7 @@ function constant_contact_maybe_display_disconnect_reconnect_notice() : bool { * @return bool * @since 2.2.0 */ -function constant_contact_maybe_show_cron_notification() : bool { +function constant_contact_maybe_show_cron_notification(): bool { if ( ! current_user_can( 'manage_options' ) ) { return false; } @@ -219,7 +219,7 @@ function constant_contact_maybe_show_cron_notification() : bool { return false; } -function constant_contact_maybe_show_update_available_notification() : bool { +function constant_contact_maybe_show_update_available_notification(): bool { if ( ! current_user_can( 'manage_options' ) ) { return false; } diff --git a/package-lock.json b/package-lock.json index 629bf4bb..c6a438d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "constant-contact-forms", - "version": "2.17.0", + "version": "2.18.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "constant-contact-forms", - "version": "2.16.0", + "version": "2.18.0", "license": "GPL-3.0-or-later", "dependencies": { "@babel/runtime": "^7.28.6" @@ -2926,9 +2926,9 @@ } }, "node_modules/@jest/environment-jsdom-abstract/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -6435,31 +6435,6 @@ } } }, - "node_modules/@wordpress/eslint-plugin/node_modules/@csstools/selector-specificity": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", - "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "optional": true, - "peer": true, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^7.0.0" - } - }, "node_modules/@wordpress/eslint-plugin/node_modules/@wordpress/theme": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@wordpress/theme/-/theme-0.8.0.tgz", @@ -6487,24 +6462,6 @@ } } }, - "node_modules/@wordpress/eslint-plugin/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0", - "optional": true, - "peer": true - }, - "node_modules/@wordpress/eslint-plugin/node_modules/balanced-match": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", - "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/@wordpress/eslint-plugin/node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -6522,369 +6479,6 @@ "node": ">=10" } }, - "node_modules/@wordpress/eslint-plugin/node_modules/css-tree": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", - "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "mdn-data": "2.27.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, - "node_modules/@wordpress/eslint-plugin/node_modules/file-entry-cache": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.2.tgz", - "integrity": "sha512-N2WFfK12gmrK1c1GXOqiAJ1tc5YE+R53zvQ+t5P8S5XhnmKYVB5eZEiLNZKDSmoG8wqqbF9EXYBBW/nef19log==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "flat-cache": "^6.1.20" - } - }, - "node_modules/@wordpress/eslint-plugin/node_modules/flat-cache": { - "version": "6.1.20", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.20.tgz", - "integrity": "sha512-AhHYqwvN62NVLp4lObVXGVluiABTHapoB57EyegZVmazN+hhGhLTn3uZbOofoTw4DSDvVCadzzyChXhOAvy8uQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "cacheable": "^2.3.2", - "flatted": "^3.3.3", - "hookified": "^1.15.0" - } - }, - "node_modules/@wordpress/eslint-plugin/node_modules/global-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", - "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "global-prefix": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@wordpress/eslint-plugin/node_modules/global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@wordpress/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/@wordpress/eslint-plugin/node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@wordpress/eslint-plugin/node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@wordpress/eslint-plugin/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@wordpress/eslint-plugin/node_modules/known-css-properties": { - "version": "0.37.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz", - "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/@wordpress/eslint-plugin/node_modules/mdn-data": { - "version": "2.27.1", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", - "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", - "dev": true, - "license": "CC0-1.0", - "optional": true, - "peer": true - }, - "node_modules/@wordpress/eslint-plugin/node_modules/meow": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", - "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wordpress/eslint-plugin/node_modules/postcss-safe-parser": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", - "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/@wordpress/eslint-plugin/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@wordpress/eslint-plugin/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@wordpress/eslint-plugin/node_modules/stylelint": { - "version": "16.26.1", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.26.1.tgz", - "integrity": "sha512-v20V59/crfc8sVTAtge0mdafI3AdnzQ2KsWe6v523L4OA1bJO02S7MO2oyXDCS6iWb9ckIPnqAFVItqSBQr7jw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/stylelint" - }, - { - "type": "github", - "url": "https://github.com/sponsors/stylelint" - } - ], - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-syntax-patches-for-csstree": "^1.0.19", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/media-query-list-parser": "^4.0.3", - "@csstools/selector-specificity": "^5.0.0", - "@dual-bundle/import-meta-resolve": "^4.2.1", - "balanced-match": "^2.0.0", - "colord": "^2.9.3", - "cosmiconfig": "^9.0.0", - "css-functions-list": "^3.2.3", - "css-tree": "^3.1.0", - "debug": "^4.4.3", - "fast-glob": "^3.3.3", - "fastest-levenshtein": "^1.0.16", - "file-entry-cache": "^11.1.1", - "global-modules": "^2.0.0", - "globby": "^11.1.0", - "globjoin": "^0.1.4", - "html-tags": "^3.3.1", - "ignore": "^7.0.5", - "imurmurhash": "^0.1.4", - "is-plain-object": "^5.0.0", - "known-css-properties": "^0.37.0", - "mathml-tag-names": "^2.1.3", - "meow": "^13.2.0", - "micromatch": "^4.0.8", - "normalize-path": "^3.0.0", - "picocolors": "^1.1.1", - "postcss": "^8.5.6", - "postcss-resolve-nested-selector": "^0.1.6", - "postcss-safe-parser": "^7.0.1", - "postcss-selector-parser": "^7.1.0", - "postcss-value-parser": "^4.2.0", - "resolve-from": "^5.0.0", - "string-width": "^4.2.3", - "supports-hyperlinks": "^3.2.0", - "svg-tags": "^1.0.0", - "table": "^6.9.0", - "write-file-atomic": "^5.0.1" - }, - "bin": { - "stylelint": "bin/stylelint.mjs" - }, - "engines": { - "node": ">=18.12.0" - } - }, - "node_modules/@wordpress/eslint-plugin/node_modules/stylelint/node_modules/cosmiconfig": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", - "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@wordpress/eslint-plugin/node_modules/supports-hyperlinks": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", - "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=14.18" - }, - "funding": { - "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" - } - }, - "node_modules/@wordpress/eslint-plugin/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/@wordpress/eslint-plugin/node_modules/write-file-atomic": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", - "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/@wordpress/jest-console": { "version": "8.41.0", "resolved": "https://registry.npmjs.org/@wordpress/jest-console/-/jest-console-8.41.0.tgz", @@ -8301,15 +7895,25 @@ } }, "node_modules/axios": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", - "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", + "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", "dev": true, "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/axios/node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" } }, "node_modules/axobject-query": { @@ -8619,9 +8223,9 @@ } }, "node_modules/basic-ftp": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.0.tgz", - "integrity": "sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.2.tgz", + "integrity": "sha512-1tDrzKsdCg70WGvbFss/ulVAxupNauGnOlgpyjKzeQxzyllBLS0CGLV7tjIXTK3ZQA9/FBEm9qyFFN1bciA6pw==", "dev": true, "license": "MIT", "engines": { @@ -12726,9 +12330,9 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, @@ -12793,9 +12397,9 @@ } }, "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "dev": true, "license": "MIT", "dependencies": { @@ -15177,9 +14781,9 @@ } }, "node_modules/jest-environment-jsdom/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -16235,16 +15839,16 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "dev": true, "license": "MIT" }, "node_modules/lodash-es": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", - "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", "dev": true, "license": "MIT" }, @@ -17113,9 +16717,9 @@ } }, "node_modules/node-forge": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.2.tgz", - "integrity": "sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", + "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", "dev": true, "license": "(BSD-3-Clause OR GPL-2.0)", "engines": { @@ -18131,9 +17735,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -22487,9 +22091,9 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index 0e5585eb..01e43085 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "constant-contact-forms", - "version": "2.17.0", + "version": "2.18.0", "description": "", "main": "build/index.js", "engines": { diff --git a/readme.txt b/readme.txt index 5e0e6c4d..4a714610 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: constantcontact, webdevstudios, tw2113, znowebdev, ggwicz, ra Tags: constant contact, constant contact official, marketing, newsletter, contacts Requires at least: 6.4.0 Tested up to: 6.9 -Stable tag: 2.17.0 +Stable tag: 2.18.0 License: GPLv3 License URI: http://www.gnu.org/licenses/gpl-3.0.html Requires PHP: 8.1 @@ -47,6 +47,16 @@ Development of Constant Contact Forms plugin occurs on [GitHub](https://github.c == Changelog == += 2.18.0 = +* Added: Revised refresh process to be more permissible of failures that are not expired refresh token related. Thanks JoeyYax. +* Updated: "Connect now" screen UI. +* Updated: details and visuals for the embed block. +* Updated: small visuals for WP 7.0. +* Updated: Extra error handling from empty API responses. +* Updated: internal code organization. +* Fixed: errors when deleting a form. +* Fixed: JS errors from CAPTCHA settings UI hiding, elsewhere in admin. + = 2.17.0 = * Added: Hide UI of non-selected Captcha services until selected for usage. * Added: Details regarding list status in Constant Contact account, to our forms list. diff --git a/src/block.json b/src/block.json index 74107c08..ac7543a5 100644 --- a/src/block.json +++ b/src/block.json @@ -2,11 +2,14 @@ "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 3, "name": "constant-contact/single-contact-form", - "version": "2.7.0", + "version": "2.18.0", "title": "Constant Contact: Single Form", "category": "widgets", "icon": "index-card", "description": "Display a single Constant Contact form.", + "keywords": [ + "form" + ], "example": {}, "supports": { "html": false diff --git a/src/edit.js b/src/edit.js index 5ed72a28..10c02e7e 100644 --- a/src/edit.js +++ b/src/edit.js @@ -4,6 +4,7 @@ import {useBlockProps, InspectorControls} from '@wordpress/block-editor'; import {SelectControl, Spinner, PanelBody, ExternalLink} from '@wordpress/components'; import ServerSideRender from '@wordpress/server-side-render'; import {addQueryArgs} from '@wordpress/url'; +import logo from './logo/ctct-logo-horizontal-color-reversed.svg'; import './editor.scss'; @@ -40,7 +41,7 @@ export default function Edit(props) { } ) } - let smMsg = (formEntryObjs && formEntryObjs.length > 1 ) ? __('Choose the form to display with the dropdown below.', 'constant-contact-forms' ) : __('Please create a Constant Contact Form.', 'constant-contact-forms'); + const smMsg = (formEntryObjs && formEntryObjs.length > 1 ) ? __('Choose the form to display with the dropdown below.', 'constant-contact-forms' ) : __('Please create a Constant Contact Form.', 'constant-contact-forms'); const getURL = (form) => { const adminRoot = ajaxurl.replace(/\/admin-ajax\.php$/, '/post.php'); @@ -61,15 +62,22 @@ export default function Edit(props) {
{__('Constant
- setAttributes({displayTitle})} /> + setAttributes({displayTitle})} + __next40pxDefaultSize + __nextHasNoMarginBottom + />
@@ -80,6 +88,8 @@ export default function Edit(props) { value={selectedForm ?? ''} options={formEntryObjs} onChange={(selectedForm) => setAttributes({selectedForm})} + __next40pxDefaultSize + __nextHasNoMarginBottom />
diff --git a/src/editor.scss b/src/editor.scss index 0d8473a0..6333ad3a 100644 --- a/src/editor.scss +++ b/src/editor.scss @@ -1,4 +1,29 @@ // sass-lint:disable class-name-format no-qualifying-elements id-name-format + +$brand-blue: #1756ec; + +// Primary palette +$color-green: rgb(46,204,64); +$color-red: rgb(255,65,54); + +// Grayscale +$color-gray: #aaa; +$color-white: #fff; +$color-white-semi: rgba(255, 255, 255, 0.1); + +// Theming +$color-error: $color-red; +$color-error-semi: rgb(255, 65, 54, 0.5); +$color-success: $color-green; + +@mixin clearfix { + &::after { + clear: both; + content: ''; + display: table; + } +} + //-------------------------------------------------------------- // EDIT VIEW //-------------------------------------------------------------- @@ -12,19 +37,17 @@ flex-direction: column; text-align: center; align-items: center; - border-radius: 5px; overflow: hidden; - background-color: #1756ec; + background-color: $brand-blue; padding: 40px; - color: #fff; + color: $color-white; label { - color: #fff; + color: $color-white; } } .ctct-block-container { - &--component { max-width: 400px; width: 100%; @@ -38,10 +61,10 @@ } &--header { - margin: 0 0 10px; + margin: 0; width: 100%; img { - max-width: 280px; + max-width: 320px; display: block; } } @@ -49,12 +72,10 @@ &--selection { width: 75%; padding: 20px; - background-color: rgba(255, 255, 255, 0.1); - border-radius: 4px; + background-color: $color-white-semi; margin: 20px 0 0; small { - opacity: 0.8; font-size: 14px; font-weight: 600; margin: 0 0 10px; @@ -66,26 +87,6 @@ // PREVIEW VIEW -- Approximately how it'll appear on the frontend. //-------------------------------------------------------------- -// Primary palette -$color-green: #2ecc40; -$color-red: #ff4136; - -// Grayscale -$color-gray: #aaa; -$color-white: #fff; - -// Theming -$color-error: $color-red; -$color-success: $color-green; - -@mixin clearfix { - &::after { - clear: both; - content: ''; - display: table; - } -} - .ctct-form-wrapper { input { &[type='text'], @@ -105,7 +106,7 @@ $color-success: $color-green; &:required.ctct-invalid, &.ctct-invalid { background: $color-white url(../assets/images/error.svg) no-repeat; - background-color: rgba($color-error, 0.50); + background-color: $color-error-semi; background-position: 8px 50%; background-size: 24px; border-color: $color-error; diff --git a/src/logo/ctct-logo-horizontal-color-reversed.svg b/src/logo/ctct-logo-horizontal-color-reversed.svg new file mode 100644 index 00000000..2e4b7b35 --- /dev/null +++ b/src/logo/ctct-logo-horizontal-color-reversed.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + +