From 5297e16a3e82cf1566afb3a53042a02b01faf695 Mon Sep 17 00:00:00 2001 From: rockwellll Date: Fri, 6 Nov 2026 23:04:10 -0300 Subject: [PATCH 1/2] fix: run on PHP 7.4 and pass the Woo Marketplace checks (2.0.2) The Woo Marketplace tests the upload on PHP 7.4, where 2.0.1 failed to load: `match`, union types and `mixed` are PHP 8 only. They are now array lookups and docblock types. The header declares the supported WordPress, WooCommerce and PHP versions, and HPOS compatibility (orders only go through the WooCommerce order API). Failed requests log to the WooCommerce logger instead of error_log, and the checkout link's products and the Webchat card's source are sanitized before they are read. Bumps the version to 2.0.2. Co-Authored-By: Claude Opus 5.5 --- changelog.txt | 7 +++++++ hellotext.php | 20 +++++++++++++++++++- readme.txt | 5 ++++- src/Adapters/OrderAdapter.php | 4 ++-- src/Adapters/PriceAdapter.php | 4 ++-- src/Adapters/ProductAdapter.php | 4 ++-- src/Api/Client.php | 12 ++++++------ src/Api/Event.php | 6 +++--- src/Events/AppInstalled.php | 4 ++-- src/Events/CartUpdates.php | 2 +- src/Events/CheckoutLink.php | 4 ++-- src/Events/CustomerProfile.php | 2 +- src/Events/OrderPlaced.php | 2 +- src/Events/OrderStatus.php | 6 +++--- src/Services/CreateProfile.php | 6 +++--- src/Services/Session.php | 2 +- 16 files changed, 59 insertions(+), 31 deletions(-) diff --git a/changelog.txt b/changelog.txt index 5628def..eccc6d8 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,12 @@ ** Hellotext for WooCommerce Changelog ** +2026-11-07 - version 2.0.2 + +* Run on PHP 7.4 again: 2.0.0 failed to load there. +* Declare the supported WordPress, WooCommerce and PHP versions, and compatibility with High-Performance Order Storage. +* Log failed Hellotext requests to the WooCommerce logs (source "hellotext") when WP_DEBUG is on. +* Sanitize the checkout link's products and the Webchat card's source before reading them. + 2026-11-06 - version 2.0.1 * Declare WooCommerce as a required plugin: WordPress 6.5 and later only activate Hellotext while WooCommerce is active. diff --git a/hellotext.php b/hellotext.php index 2cabed5..2070c6e 100644 --- a/hellotext.php +++ b/hellotext.php @@ -8,8 +8,13 @@ * Plugin Name: Hellotext * Plugin URI: https://github.com/hellotext/hellotext-wordpress * Description: Integrates Hellotext tracking to WooCommerce. - * Version: 2.0.1 + * Version: 2.0.2 + * Requires at least: 6.5 + * Tested up to: 7.1 + * Requires PHP: 7.4 * Requires Plugins: woocommerce + * WC requires at least: 8.0 + * WC tested up to: 11.2 * Author: Hellotext * Author URI: https://www.hellotext.com * License: GPL v2 @@ -39,6 +44,19 @@ register_activation_hook(__FILE__, 'hellotext_activate'); register_deactivation_hook(__FILE__, 'hellotext_deactivate'); +/** + * Declare compatibility with High-Performance Order Storage: orders are only + * read and written through the WooCommerce order API. + * + * @return void + */ +function hellotext_declare_compatibility() { + if (class_exists(\Automattic\WooCommerce\Utilities\FeaturesUtil::class)) { + \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility('custom_order_tables', __FILE__, true); + } +} +add_action('before_woocommerce_init', 'hellotext_declare_compatibility'); + /** * Check for plugin updates from GitHub. * Caches result for 24 hours to avoid rate limiting. diff --git a/readme.txt b/readme.txt index 219cdb5..7d18ccb 100644 --- a/readme.txt +++ b/readme.txt @@ -1,6 +1,9 @@ === Hellotext === Contributors: ricardohellotext -Stable tag: 2.0.1 +Requires at least: 6.5 +Tested up to: 7.1 +Requires PHP: 7.4 +Stable tag: 2.0.2 License: GPLv2 License URI: https://www.gnu.org/licenses/gpl-2.0.html diff --git a/src/Adapters/OrderAdapter.php b/src/Adapters/OrderAdapter.php index 8a1fad0..55f5995 100644 --- a/src/Adapters/OrderAdapter.php +++ b/src/Adapters/OrderAdapter.php @@ -15,7 +15,7 @@ class OrderAdapter { * * @var \WC_Order|false */ - public \WC_Order|false $order; + public $order; /** * Adapted order items. @@ -30,7 +30,7 @@ class OrderAdapter { * @param int|\WC_Order $order Order ID or order instance. * @param array $products Pre-adapted products list. */ - public function __construct(int|\WC_Order $order, array $products = []) { + public function __construct($order, array $products = []) { $this->order = is_numeric($order) ? wc_get_order($order) : $order; $this->products = $products; } diff --git a/src/Adapters/PriceAdapter.php b/src/Adapters/PriceAdapter.php index 30b2bfb..5eedfaf 100644 --- a/src/Adapters/PriceAdapter.php +++ b/src/Adapters/PriceAdapter.php @@ -15,7 +15,7 @@ class PriceAdapter { * * @var float|string */ - public float|string $price; + public $price; /** * Currency code. @@ -30,7 +30,7 @@ class PriceAdapter { * @param float|string $price Raw price amount. * @param string|null $currency Currency code or null to use store default. */ - public function __construct(float|string $price, ?string $currency = null) { + public function __construct($price, ?string $currency = null) { $this->price = $price; $this->currency = is_null($currency) ? get_woocommerce_currency() : $currency; } diff --git a/src/Adapters/ProductAdapter.php b/src/Adapters/ProductAdapter.php index 6bcbf4f..56d167c 100644 --- a/src/Adapters/ProductAdapter.php +++ b/src/Adapters/ProductAdapter.php @@ -15,14 +15,14 @@ class ProductAdapter { * * @var \WC_Product|false */ - public \WC_Product|false $product; + public $product; /** * Create a new adapter instance. * * @param int|\WC_Product $product Product ID or product instance. */ - public function __construct(int|\WC_Product $product) { + public function __construct($product) { $this->product = is_numeric($product) ? wc_get_product($product) : $product; } diff --git a/src/Api/Client.php b/src/Api/Client.php index d73005b..7554199 100644 --- a/src/Api/Client.php +++ b/src/Api/Client.php @@ -45,12 +45,12 @@ public static function request(string $method = 'GET', string $path = '/', array // Handle errors if (is_wp_error($response)) { if (defined('WP_DEBUG') && WP_DEBUG) { - error_log(sprintf( - '[Hellotext] API request failed: %s %s - Error: %s', + wc_get_logger()->error(sprintf( + 'API request failed: %s %s - Error: %s', $method, $request_url, $response->get_error_message() - )); + ), ['source' => 'hellotext']); } return [ @@ -70,12 +70,12 @@ public static function request(string $method = 'GET', string $path = '/', array // Log non-2xx responses if (defined('WP_DEBUG') && WP_DEBUG && ($status_code < 200 || $status_code >= 300)) { - error_log(sprintf( - '[Hellotext] API request returned %d: %s %s', + wc_get_logger()->error(sprintf( + 'API request returned %d: %s %s', $status_code, $method, $request_url - )); + ), ['source' => 'hellotext']); } return [ diff --git a/src/Api/Event.php b/src/Api/Event.php index 578b478..440533e 100644 --- a/src/Api/Event.php +++ b/src/Api/Event.php @@ -76,11 +76,11 @@ public function track(string $action, array $payload): void { // Only log if there's an error and debug is enabled if (is_wp_error($response) && defined('WP_DEBUG') && WP_DEBUG) { - error_log(sprintf( - '[Hellotext] Event tracking failed for action "%s": %s', + wc_get_logger()->error(sprintf( + 'Event tracking failed for action "%s": %s', $action, $response->get_error_message() - )); + ), ['source' => 'hellotext']); } } } diff --git a/src/Events/AppInstalled.php b/src/Events/AppInstalled.php index 87ececc..3a417e9 100644 --- a/src/Events/AppInstalled.php +++ b/src/Events/AppInstalled.php @@ -86,7 +86,7 @@ function hellotext_connect(): void { * @param mixed $value Access token being saved. * @return mixed */ -function hellotext_retry_failed_connection(mixed $value): mixed { +function hellotext_retry_failed_connection($value) { if (get_option(Constants::OPTION_CONNECTION_FAILED)) { hellotext_schedule_connect(); } @@ -139,7 +139,7 @@ function hellotext_schedule_connect(): void { * @param mixed $new_value New value. * @return void */ -function hellotext_business_id_updated(mixed $old_value, mixed $new_value): void { +function hellotext_business_id_updated($old_value, $new_value): void { if ($old_value && $old_value !== $new_value) { hellotext_disconnect((string) $old_value); } diff --git a/src/Events/CartUpdates.php b/src/Events/CartUpdates.php index 31dcf79..7228cac 100644 --- a/src/Events/CartUpdates.php +++ b/src/Events/CartUpdates.php @@ -181,7 +181,7 @@ function hellotext_cart_event_source(): ?array { return null; } - $source = json_decode(wp_unslash($_POST['hellotext_source']), true); + $source = json_decode(sanitize_text_field(wp_unslash($_POST['hellotext_source'])), true); if (!is_array($source)) { return null; diff --git a/src/Events/CheckoutLink.php b/src/Events/CheckoutLink.php index 2d84e71..822002c 100644 --- a/src/Events/CheckoutLink.php +++ b/src/Events/CheckoutLink.php @@ -32,8 +32,8 @@ function hellotext_checkout_link(): void { * cart, with WooCommerce's notices, when nothing could be added. */ function hellotext_fill_checkout_link_cart(): string { - $products = wp_unslash($_GET['products'] ?? ''); - $lines = is_string($products) ? hellotext_checkout_link_lines($products) : []; + $products = sanitize_text_field(wp_unslash($_GET['products'] ?? '')); + $lines = hellotext_checkout_link_lines($products); if (empty($lines)) { return wc_get_cart_url(); diff --git a/src/Events/CustomerProfile.php b/src/Events/CustomerProfile.php index db4f958..ba12781 100644 --- a/src/Events/CustomerProfile.php +++ b/src/Events/CustomerProfile.php @@ -11,7 +11,7 @@ * @param mixed $customer Guest billing details, a user ID, or null for the current user. * @return void */ -add_action('hellotext_create_profile', function (mixed $customer = null): void { +add_action('hellotext_create_profile', function ($customer = null): void { if (is_array($customer)) { (new CreateProfile(null, $customer))->process(); return; diff --git a/src/Events/OrderPlaced.php b/src/Events/OrderPlaced.php index f5a62a7..98c5963 100644 --- a/src/Events/OrderPlaced.php +++ b/src/Events/OrderPlaced.php @@ -56,7 +56,7 @@ function hellotext_capture_checkout(\WC_Order $order): void { * @param mixed $order_id Order ID. * @return void */ -function hellotext_thankyou(mixed $order_id): void { +function hellotext_thankyou($order_id): void { $order = wc_get_order($order_id); if (!$order) { diff --git a/src/Events/OrderStatus.php b/src/Events/OrderStatus.php index 50297aa..ffde887 100644 --- a/src/Events/OrderStatus.php +++ b/src/Events/OrderStatus.php @@ -24,12 +24,12 @@ function hellotext_order_status_changed(int $order_id, string $old_status, strin hellotext_order_placed($order); } - $action = match ($new_status) { + $actions = [ 'processing' => Constants::EVENT_ORDER_CONFIRMED, 'cancelled' => Constants::EVENT_ORDER_CANCELLED, 'completed' => Constants::EVENT_ORDER_DELIVERED, - default => null, - }; + ]; + $action = $actions[$new_status] ?? null; if (!$action) { return; diff --git a/src/Services/CreateProfile.php b/src/Services/CreateProfile.php index 4693162..68da15d 100644 --- a/src/Services/CreateProfile.php +++ b/src/Services/CreateProfile.php @@ -119,12 +119,12 @@ public function create_hellotext_profile(): ?string { private function attribute(string $name): ?string { if ($this->user_id) { $user = get_user_by('id', $this->user_id); - $value = match ($name) { + $values = [ 'first_name' => $user->first_name ?? '', 'last_name' => $user->last_name ?? '', 'email' => $user->user_email ?? '', - default => '', - }; + ]; + $value = $values[$name] ?? ''; if ('' === $value) { $value = (string) get_user_meta($this->user_id, "billing_{$name}", true); diff --git a/src/Services/Session.php b/src/Services/Session.php index 8c61b5a..c8e808c 100644 --- a/src/Services/Session.php +++ b/src/Services/Session.php @@ -67,7 +67,7 @@ public static function encrypt(?string $session = null): string { * @param mixed $encrypted_data Encrypted session data. * @return string|false */ - public static function decrypt(mixed $encrypted_data = null): string|false { + public static function decrypt($encrypted_data = null) { if (!is_string($encrypted_data) || '' === $encrypted_data) { return false; } From e80940ce10bb70f4eb3e06f4d71e12a951535c42 Mon Sep 17 00:00:00 2001 From: rockwellll Date: Fri, 6 Nov 2026 23:05:03 -0300 Subject: [PATCH 2/2] ci: build the release zip with a hellotext root folder The Woo Marketplace rejects a zip whose root folder is not the plugin slug, hellotext. The release workflow now packages release/hellotext/ as hellotext.zip, and the CI package check verifies that layout. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/php.yml | 30 +++++++++++++++--------------- .github/workflows/release.yml | 6 +++--- changelog.txt | 1 + 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index f01522f..4c853b5 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -90,24 +90,24 @@ jobs: - name: Verify release package layout run: | mkdir -p release - rsync -av --exclude-from=.distignore . release/hellotext-wordpress/ + rsync -av --exclude-from=.distignore . release/hellotext/ - test -f release/hellotext-wordpress/hellotext.php - test -d release/hellotext-wordpress/src - test -f release/hellotext-wordpress/vendor/autoload.php - test -f release/hellotext-wordpress/assets/hellotext-service-worker.js - test -f release/hellotext-wordpress/assets/storefront.js + test -f release/hellotext/hellotext.php + test -d release/hellotext/src + test -f release/hellotext/vendor/autoload.php + test -f release/hellotext/assets/hellotext-service-worker.js + test -f release/hellotext/assets/storefront.js for path in \ - release/hellotext-wordpress/tests \ - release/hellotext-wordpress/composer.json \ - release/hellotext-wordpress/composer.lock \ - release/hellotext-wordpress/.github \ - release/hellotext-wordpress/.php-cs-fixer.cache \ - release/hellotext-wordpress/.php-cs-fixer.php \ - release/hellotext-wordpress/DEVELOPMENT.md \ - release/hellotext-wordpress/API.md \ - release/hellotext-wordpress/release; do + release/hellotext/tests \ + release/hellotext/composer.json \ + release/hellotext/composer.lock \ + release/hellotext/.github \ + release/hellotext/.php-cs-fixer.cache \ + release/hellotext/.php-cs-fixer.php \ + release/hellotext/DEVELOPMENT.md \ + release/hellotext/API.md \ + release/hellotext/release; do if [ -e "$path" ]; then echo "Unexpected development file in release package: $path" exit 1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 31af36d..f1fdcfd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,12 +24,12 @@ jobs: - name: Create release zip run: | mkdir -p release - rsync -av --exclude-from=.distignore . release/hellotext-wordpress/ + rsync -av --exclude-from=.distignore . release/hellotext/ cd release - zip -r hellotext-wordpress.zip hellotext-wordpress + zip -r hellotext.zip hellotext - name: Create GitHub Release uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3 with: - files: release/hellotext-wordpress.zip + files: release/hellotext.zip generate_release_notes: true diff --git a/changelog.txt b/changelog.txt index eccc6d8..7be4d93 100644 --- a/changelog.txt +++ b/changelog.txt @@ -6,6 +6,7 @@ * Declare the supported WordPress, WooCommerce and PHP versions, and compatibility with High-Performance Order Storage. * Log failed Hellotext requests to the WooCommerce logs (source "hellotext") when WP_DEBUG is on. * Sanitize the checkout link's products and the Webchat card's source before reading them. +* The release zip is hellotext.zip and installs to the hellotext folder, as the Woo Marketplace requires. 2026-11-06 - version 2.0.1