From ab4f51ac94237567007a438c8d47715c712979aa Mon Sep 17 00:00:00 2001 From: sultann Date: Wed, 10 Jun 2026 22:22:19 +0600 Subject: [PATCH 01/18] Add PHPUnit baseline test suite --- composer.json | 12 ++- phpunit.xml | 52 +++++++++++ tests/.gitignore | 2 + tests/bin/install.sh | 77 +++++++++++++++ tests/bootstrap.php | 60 ++++++++++++ tests/phpunit/ContractTest.php | 82 ++++++++++++++++ tests/phpunit/EncryptionTest.php | 49 ++++++++++ tests/phpunit/KeyModelTest.php | 139 ++++++++++++++++++++++++++++ tests/phpunit/OrderDeliveryTest.php | 108 +++++++++++++++++++++ tests/phpunit/StocksTest.php | 54 +++++++++++ tests/phpunit/TestCase.php | 90 ++++++++++++++++++ 11 files changed, 724 insertions(+), 1 deletion(-) create mode 100644 phpunit.xml create mode 100644 tests/.gitignore create mode 100755 tests/bin/install.sh create mode 100644 tests/bootstrap.php create mode 100644 tests/phpunit/ContractTest.php create mode 100644 tests/phpunit/EncryptionTest.php create mode 100644 tests/phpunit/KeyModelTest.php create mode 100644 tests/phpunit/OrderDeliveryTest.php create mode 100644 tests/phpunit/StocksTest.php create mode 100644 tests/phpunit/TestCase.php diff --git a/composer.json b/composer.json index e2837650..ecd55799 100644 --- a/composer.json +++ b/composer.json @@ -23,13 +23,21 @@ "pluginever/framework-settings": "dev-trunk" }, "require-dev": { - "byteever/byteever-sniffs": "^1.1.3" + "byteever/byteever-sniffs": "^1.1.3", + "phpunit/phpunit": "^9.6", + "wp-phpunit/wp-phpunit": "^6.8", + "yoast/phpunit-polyfills": "^4.0" }, "autoload": { "psr-4": { "WooCommerceSerialNumbers\\": ["includes/","src/"] } }, + "autoload-dev": { + "psr-4": { + "WooCommerceSerialNumbers\\Tests\\": "tests/phpunit/" + } + }, "config": { "platform": { "php": "7.4" @@ -45,6 +53,8 @@ "makepot": "wp --allow-root i18n make-pot . --include=*.php,assets,includes,templates --domain=wc-serial-numbers", "phpcs": "php ./vendor/bin/phpcs --standard=phpcs.xml -s -v", "phpcbf": "php ./vendor/bin/phpcbf --standard=phpcs.xml -v", + "test:setup": "bash ./tests/bin/install.sh wordpress_test root root localhost latest", + "test": "phpunit", "strauss": [ "test -f /tmp/strauss.phar || curl -o /tmp/strauss.phar -L https://github.com/BrianHenryIE/strauss/releases/download/0.24.1/strauss.phar", "@php /tmp/strauss.phar", diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 00000000..7dffb7d7 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,52 @@ + + + + + + ./tests/phpunit + + + + + + + ./src + + + ./vendor + ./tests + + + + + + + + + + + + + + + + + + + + diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 00000000..9b2dce80 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,2 @@ +coverage/ +logs/ diff --git a/tests/bin/install.sh b/tests/bin/install.sh new file mode 100755 index 00000000..3d318bee --- /dev/null +++ b/tests/bin/install.sh @@ -0,0 +1,77 @@ +#!/bin/bash + +set -e + +DB_NAME=${1:-wordpress_test} +DB_USER=${2:-root} +DB_PASS=${3:-''} +DB_HOST=${4:-localhost} +WP_VERSION=${5:-latest} + +WP_TESTS_DIR=${WP_TESTS_DIR-/tmp/wordpress-tests-lib} +WP_CORE_DIR=${WP_CORE_DIR-/tmp/wordpress} +WC_DIR="$WP_CORE_DIR/wp-content/plugins/woocommerce" + +# prerequisites +for cmd in wp mysql curl unzip; do + if ! command -v "$cmd" >/dev/null 2>&1; then + echo "$cmd is required but not installed." + exit 1 + fi +done + +echo "Setting up WordPress test environment..." +# Download WordPress. +if [ ! -f "$WP_CORE_DIR/wp-load.php" ]; then + echo "Downloading WordPress..." + if [ "$WP_VERSION" = "latest" ]; then + wp core download --path="$WP_CORE_DIR" --force --allow-root || exit 1 + else + wp core download --path="$WP_CORE_DIR" --version="$WP_VERSION" --force --allow-root || exit 1 + fi +fi + +# Download test suite matching WordPress version. +if [ ! -d "$WP_TESTS_DIR/includes" ]; then + echo "Downloading test suite..." + BRANCH="trunk" + [ "$WP_VERSION" != "latest" ] && BRANCH="$WP_VERSION" + rm -rf /tmp/wordpress-develop + git clone --depth=1 --branch "$BRANCH" https://github.com/WordPress/wordpress-develop.git /tmp/wordpress-develop 2>/dev/null || exit 1 + mkdir -p "$WP_TESTS_DIR" + cp -r /tmp/wordpress-develop/tests/phpunit/{includes,data} "$WP_TESTS_DIR/" || exit 1 + rm -rf /tmp/wordpress-develop +fi + +# Download WooCommerce (the plugin under test requires it to boot). +if [ ! -f "$WC_DIR/woocommerce.php" ]; then + echo "Downloading WooCommerce..." + mkdir -p "$WP_CORE_DIR/wp-content/plugins" + curl -sL "https://downloads.wordpress.org/plugin/woocommerce.zip" -o /tmp/woocommerce.zip || exit 1 + unzip -q -o /tmp/woocommerce.zip -d "$WP_CORE_DIR/wp-content/plugins/" || exit 1 + rm -f /tmp/woocommerce.zip +fi + +# Create config file. +cat > "$WP_TESTS_DIR/wp-tests-config.php" << EOF +/dev/null || exit 1 + +echo "Setup complete." diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 00000000..6723204e --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,60 @@ +get_version() ); +} + +tests_add_filter( 'muplugins_loaded', '_manually_load_plugins' ); + +require_once "{$_tests_dir}/includes/bootstrap.php"; + +// Create the WooCommerce + plugin tables now that WordPress has loaded. +WC_Install::install(); + +// Serial Numbers reads orders via get_post(), so keep CPT order storage (HPOS off). +update_option( 'woocommerce_custom_orders_table_enabled', 'no' ); + +Installer::install(); + +// Non-core tables survive between runs (the WP bootstrap only reinstalls core +// tables), so clear rows left behind by previously committed runs — otherwise +// fresh post IDs collide with stale order items. +global $wpdb; +foreach ( array( 'serial_numbers', 'serial_numbers_activations', 'woocommerce_order_items', 'woocommerce_order_itemmeta' ) as $_table ) { + $wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}{$_table}" ); +} diff --git a/tests/phpunit/ContractTest.php b/tests/phpunit/ContractTest.php new file mode 100644 index 00000000..2634e47c --- /dev/null +++ b/tests/phpunit/ContractTest.php @@ -0,0 +1,82 @@ +assertTrue( function_exists( $function ), "Function {$function} should exist." ); + } + } + + /** + * The public classes exist. + */ + public function testPublicClassesExist(): void { + $this->assertTrue( class_exists( \WooCommerceSerialNumbers\Models\Key::class ) ); + + // The list table is admin-only and extends WP_List_Table. + if ( ! class_exists( '\WP_List_Table' ) ) { + require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php'; + } + $this->assertTrue( class_exists( \WooCommerceSerialNumbers\Admin\ListTables\ListTable::class ) ); + } + + /** + * The plugin boots its services on woocommerce_loaded and fired its loaded action. + */ + public function testBootHooks(): void { + $this->assertNotFalse( has_action( 'woocommerce_loaded', array( WCSN(), 'init' ) ) ); + $this->assertGreaterThanOrEqual( 1, did_action( 'wc_serial_numbers_loaded' ) ); + } + + /** + * Key statuses and sources expose the expected defaults. + */ + public function testStatusesAndSourcesDefaults(): void { + $statuses = wcsn_get_key_statuses(); + foreach ( array( 'available', 'pending', 'sold', 'expired', 'cancelled' ) as $status ) { + $this->assertArrayHasKey( $status, $statuses ); + } + + $this->assertArrayHasKey( 'custom_source', wcsn_get_key_sources() ); + } + + /** + * Default option-driven helpers report the expected values. + */ + public function testHelperDefaults(): void { + $this->assertSame( 'manage_woocommerce', wcsn_get_manager_role() ); + $this->assertTrue( wcsn_is_software_support_enabled() ); + } + + /** + * WCSN() returns the same plugin instance every call. + */ + public function testSingletonInstance(): void { + $this->assertSame( WCSN(), WCSN() ); + } +} diff --git a/tests/phpunit/EncryptionTest.php b/tests/phpunit/EncryptionTest.php new file mode 100644 index 00000000..af9b8d1c --- /dev/null +++ b/tests/phpunit/EncryptionTest.php @@ -0,0 +1,49 @@ +assertNotFalse( $encrypted ); + $this->assertNotSame( $plain, $encrypted ); + $this->assertSame( $plain, Encryption::decrypt( $encrypted ) ); + } + + /** + * The maybe-encrypt/maybe-decrypt helpers round-trip through the wcsn functions. + */ + public function testWcsnHelpersRoundtrip(): void { + $plain = 'HELPER-KEY-001'; + $encrypted = wcsn_encrypt_key( $plain ); + + $this->assertNotSame( $plain, $encrypted ); + $this->assertSame( $plain, wcsn_decrypt_key( $encrypted ) ); + + // Encrypting an already encrypted value leaves it unchanged. + $this->assertSame( $encrypted, wcsn_encrypt_key( $encrypted ) ); + } + + /** + * Decrypting garbage fails gracefully without raising an exception. + */ + public function testDecryptGarbageFailsGracefully(): void { + $this->assertFalse( Encryption::decrypt( 'this is not encrypted at all !!!' ) ); + $this->assertFalse( Encryption::isEncrypted( 'this is not encrypted at all !!!' ) ); + + // The maybe-decrypt helper returns the input untouched. + $this->assertSame( 'plain-value', wcsn_decrypt_key( 'plain-value' ) ); + } +} diff --git a/tests/phpunit/KeyModelTest.php b/tests/phpunit/KeyModelTest.php new file mode 100644 index 00000000..1fbd8190 --- /dev/null +++ b/tests/phpunit/KeyModelTest.php @@ -0,0 +1,139 @@ +create_product(); + $key = wcsn_insert_key( + array( + 'product_id' => $product->get_id(), + 'serial_key' => 'SERIAL-123-ABC', + ) + ); + $this->assertNotWPError( $key ); + $this->assertNotEmpty( $key->get_id() ); + + // Re-read from the database and confirm the plain key round-trips. + $reloaded = Key::get( $key->get_id() ); + $this->assertSame( 'SERIAL-123-ABC', $reloaded->get_serial_key() ); + $this->assertSame( 'SERIAL-123-ABC', $reloaded->get_key() ); + $this->assertSame( $product->get_id(), $reloaded->get_product_id() ); + $this->assertSame( 'available', $reloaded->get_status() ); + $this->assertSame( 'custom_source', $reloaded->get_source() ); + + // The stored value is encrypted at rest, not the plain key. + $raw = $wpdb->get_var( $wpdb->prepare( "SELECT serial_key FROM {$wpdb->prefix}serial_numbers WHERE id = %d", $key->get_id() ) ); + $this->assertNotSame( 'SERIAL-123-ABC', $raw ); + $this->assertSame( 'SERIAL-123-ABC', wcsn_decrypt_key( $raw ) ); + } + + /** + * Saving without a product id is rejected. + */ + public function testSaveRequiresProductId(): void { + $key = wcsn_insert_key( + array( + 'serial_key' => 'NO-PRODUCT-KEY', + ) + ); + $this->assertWPError( $key ); + $this->assertSame( 'missing-required', $key->get_error_code() ); + } + + /** + * Saving without a serial key is rejected. + */ + public function testSaveRequiresSerialKey(): void { + $key = wcsn_insert_key( + array( + 'product_id' => $this->create_product()->get_id(), + ) + ); + $this->assertWPError( $key ); + $this->assertSame( 'missing-required', $key->get_error_code() ); + } + + /** + * Saving with an invalid product id is rejected. + */ + public function testSaveRejectsInvalidProduct(): void { + $key = wcsn_insert_key( + array( + 'product_id' => 999999, + 'serial_key' => 'BAD-PRODUCT-KEY', + ) + ); + $this->assertWPError( $key ); + $this->assertSame( 'invalid-data', $key->get_error_code() ); + } + + /** + * Duplicate serial keys are rejected by default. + */ + public function testDuplicateKeyRejectedByDefault(): void { + $product = $this->create_product(); + $this->make_key( + array( + 'product_id' => $product->get_id(), + 'serial_key' => 'DUPLICATE-KEY', + ) + ); + + $duplicate = wcsn_insert_key( + array( + 'product_id' => $product->get_id(), + 'serial_key' => 'DUPLICATE-KEY', + ) + ); + $this->assertWPError( $duplicate ); + $this->assertSame( 'invalid-data', $duplicate->get_error_code() ); + } + + /** + * A key transitions from available to sold via setters and save. + */ + public function testStatusTransitionAvailableToSold(): void { + $key = $this->make_key(); + $this->assertSame( 'available', $key->get_status() ); + + $key->set_status( 'sold' ); + $this->assertNotWPError( $key->save() ); + + $reloaded = Key::get( $key->get_id() ); + $this->assertSame( 'sold', $reloaded->get_status() ); + } + + /** + * The status setter ignores values outside the registered statuses. + */ + public function testStatusSetterIgnoresUnknownStatus(): void { + $key = $this->make_key(); + $key->set_status( 'bogus-status' ); + $this->assertSame( 'available', $key->get_status() ); + } + + /** + * Deleting a key removes it. + */ + public function testDeleteRemovesKey(): void { + $key = $this->make_key(); + $id = $key->get_id(); + + $this->assertTrue( (bool) wcsn_delete_key( $id ) ); + $this->assertEmpty( Key::get( $id ) ); + $this->assertFalse( wcsn_get_key( $id ) ); + } +} diff --git a/tests/phpunit/OrderDeliveryTest.php b/tests/phpunit/OrderDeliveryTest.php new file mode 100644 index 00000000..7f5a68c8 --- /dev/null +++ b/tests/phpunit/OrderDeliveryTest.php @@ -0,0 +1,108 @@ +add_product( $product, 1 ); + $order->calculate_totals(); + $order->save(); + + return $order; + } + + /** + * Completing an order assigns an available key to it and marks it sold. + */ + public function testCompletedOrderAssignsKey(): void { + $product = $this->create_product(); + $key = $this->make_key( + array( + 'product_id' => $product->get_id(), + 'serial_key' => 'ORDER-KEY-001', + ) + ); + + $order = $this->create_order( $product ); + $order->update_status( 'completed' ); + + $reloaded = Key::get( $key->get_id() ); + $this->assertSame( $order->get_id(), $reloaded->get_order_id() ); + $this->assertSame( 'sold', $reloaded->get_status() ); + $this->assertNotEmpty( $reloaded->get_order_item_id() ); + $this->assertNotEmpty( $reloaded->get_order_date() ); + + $order_keys = wcsn_order_get_keys( $order->get_id() ); + $this->assertCount( 1, $order_keys ); + $this->assertSame( $key->get_id(), $order_keys[0]->get_id() ); + $this->assertTrue( wcsn_order_is_fullfilled( $order->get_id() ) ); + } + + /** + * A processing order also triggers key assignment. + */ + public function testProcessingOrderAssignsKey(): void { + $product = $this->create_product(); + $key = $this->make_key( + array( + 'product_id' => $product->get_id(), + 'serial_key' => 'ORDER-KEY-002', + ) + ); + + $order = $this->create_order( $product ); + $order->update_status( 'processing' ); + + $reloaded = Key::get( $key->get_id() ); + $this->assertSame( $order->get_id(), $reloaded->get_order_id() ); + $this->assertSame( 'sold', $reloaded->get_status() ); + } + + /** + * Cancelling a delivered order revokes the key. + */ + public function testCancelledOrderRevokesKey(): void { + $product = $this->create_product(); + $key = $this->make_key( + array( + 'product_id' => $product->get_id(), + 'serial_key' => 'ORDER-KEY-003', + ) + ); + + $order = $this->create_order( $product ); + $order->update_status( 'completed' ); + $order->update_status( 'cancelled' ); + + $reloaded = Key::get( $key->get_id() ); + $this->assertSame( 'cancelled', $reloaded->get_status() ); + $this->assertSame( 0, $reloaded->get_order_id() ); + } + + /** + * Without available keys the order stays unfulfilled. + */ + public function testOrderWithoutStockStaysUnfulfilled(): void { + $product = $this->create_product(); + + $order = $this->create_order( $product ); + $order->update_status( 'completed' ); + + $this->assertCount( 0, wcsn_order_get_keys( $order->get_id() ) ); + $this->assertFalse( wcsn_order_is_fullfilled( $order->get_id() ) ); + } +} diff --git a/tests/phpunit/StocksTest.php b/tests/phpunit/StocksTest.php new file mode 100644 index 00000000..071f8463 --- /dev/null +++ b/tests/phpunit/StocksTest.php @@ -0,0 +1,54 @@ +create_product(); + + $this->make_key( array( 'product_id' => $product->get_id(), 'serial_key' => 'STOCK-KEY-1' ) ); + $this->make_key( array( 'product_id' => $product->get_id(), 'serial_key' => 'STOCK-KEY-2' ) ); + $this->make_key( array( 'product_id' => $product->get_id(), 'serial_key' => 'STOCK-KEY-3' ) ); + $this->make_key( + array( + 'product_id' => $product->get_id(), + 'serial_key' => 'STOCK-KEY-SOLD', + 'status' => 'sold', + ) + ); + + $counts = wcsn_get_stocks_count(); + $this->assertArrayHasKey( $product->get_id(), $counts ); + $this->assertEquals( 3, $counts[ $product->get_id() ] ); + } + + /** + * Per-product stock helper returns the available count, zero when unknown. + */ + public function testProductStockHelper(): void { + $product = $this->create_product(); + $this->make_key( array( 'product_id' => $product->get_id(), 'serial_key' => 'STOCK-KEY-4' ) ); + + $this->assertEquals( 1, wcsn_get_product_stock( $product->get_id() ) ); + $this->assertSame( 0, wcsn_get_product_stock( 999999 ) ); + } + + /** + * The WooCommerce stock quantity of an enabled product is overridden by key stock. + */ + public function testStockQuantityFilterUsesKeyStock(): void { + $product = $this->create_product(); + $this->make_key( array( 'product_id' => $product->get_id(), 'serial_key' => 'STOCK-KEY-5' ) ); + $this->make_key( array( 'product_id' => $product->get_id(), 'serial_key' => 'STOCK-KEY-6' ) ); + + $this->assertEquals( 2, wc_get_product( $product->get_id() )->get_stock_quantity() ); + } +} diff --git a/tests/phpunit/TestCase.php b/tests/phpunit/TestCase.php new file mode 100644 index 00000000..bdbfa63d --- /dev/null +++ b/tests/phpunit/TestCase.php @@ -0,0 +1,90 @@ +reset_state(); + } + + /** + * Tear down test fixtures. + * + * @return void + */ + public function tearDown(): void { + $this->reset_state(); + parent::tearDown(); + } + + /** + * Empty the keys tables and clear the stock transient. + * + * Uses DELETE instead of TRUNCATE — TRUNCATE is DDL and would commit the + * test transaction, leaking fixtures into the shared test database. + * + * @return void + */ + protected function reset_state(): void { + global $wpdb; + $wpdb->query( "DELETE FROM {$wpdb->prefix}serial_numbers" ); + $wpdb->query( "DELETE FROM {$wpdb->prefix}serial_numbers_activations" ); + delete_transient( 'wcsn_products_stock_count' ); + } + + /** + * Create a simple product enabled for serial numbers. + * + * @param array $args Extra product args. + * @return \WC_Product_Simple + */ + protected function create_product( array $args = array() ): \WC_Product_Simple { + $product = new \WC_Product_Simple(); + $product->set_name( $args['name'] ?? 'Test Software' ); + $product->set_regular_price( $args['price'] ?? '10' ); + $product->set_status( 'publish' ); + $product->save(); + + update_post_meta( $product->get_id(), '_is_serial_number', 'yes' ); + update_post_meta( $product->get_id(), '_serial_key_source', 'custom_source' ); + + return $product; + } + + /** + * Create a saved available key for a product. + * + * @param array $args Key args (serial_key, product_id, status, ...). + * @return Key + */ + protected function make_key( array $args = array() ): Key { + if ( empty( $args['product_id'] ) ) { + $args['product_id'] = $this->create_product()->get_id(); + } + + $key = wcsn_insert_key( + wp_parse_args( + $args, + array( + 'serial_key' => 'KEY-' . wp_generate_password( 12, false ), + ) + ) + ); + $this->assertNotWPError( $key ); + + return $key; + } +} From c47248c1b88531303afb8d3904288d6c07eed4ab Mon Sep 17 00:00:00 2001 From: sultann Date: Wed, 10 Jun 2026 22:43:24 +0600 Subject: [PATCH 02/18] Replace framework packages with the B8 plugin dependency --- composer.json | 38 +++++++++++++------------------------- 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/composer.json b/composer.json index ecd55799..b2da782c 100644 --- a/composer.json +++ b/composer.json @@ -3,24 +3,13 @@ "type": "wordpress-plugin", "license": "GPL-2.0-or-later", "repositories": [ - { - "type": "vcs", - "url": "git@github.com:pluginever/framework-plugin.git" - }, - { - "type": "vcs", - "url": "git@github.com:pluginever/framework-settings.git" - }, - { - "type": "vcs", - "url": "git@github.com:pluginever/framework-model.git" - } + { "type": "vcs", "url": "git@github.com:byteever/plugin.git" }, + { "type": "vcs", "url": "git@github.com:pluginever/framework-model.git" } ], "require": { "php": ">=7.4", - "pluginever/framework-plugin": "dev-trunk", - "pluginever/framework-model": "dev-trunk", - "pluginever/framework-settings": "dev-trunk" + "byteever/plugin": "^1.0", + "pluginever/framework-model": "dev-trunk" }, "require-dev": { "byteever/byteever-sniffs": "^1.1.3", @@ -30,7 +19,7 @@ }, "autoload": { "psr-4": { - "WooCommerceSerialNumbers\\": ["includes/","src/"] + "WooCommerceSerialNumbers\\": "includes/" } }, "autoload-dev": { @@ -39,27 +28,27 @@ } }, "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + }, "platform": { "php": "7.4" }, - "allow-plugins": { - "dealerdirect/phpcodesniffer-composer-installer": true - } + "sort-packages": true }, "scripts": { "post-install-cmd": "@strauss", "post-update-cmd": "@strauss", "post-autoload-dump": "test -f /tmp/strauss.phar && php /tmp/strauss.phar include-autoloader || true", "makepot": "wp --allow-root i18n make-pot . --include=*.php,assets,includes,templates --domain=wc-serial-numbers", - "phpcs": "php ./vendor/bin/phpcs --standard=phpcs.xml -s -v", - "phpcbf": "php ./vendor/bin/phpcbf --standard=phpcs.xml -v", + "phpcs": "phpcs --standard=phpcs.xml -s", + "phpcbf": "phpcbf --standard=phpcs.xml", "test:setup": "bash ./tests/bin/install.sh wordpress_test root root localhost latest", "test": "phpunit", "strauss": [ "test -f /tmp/strauss.phar || curl -o /tmp/strauss.phar -L https://github.com/BrianHenryIE/strauss/releases/download/0.24.1/strauss.phar", "@php /tmp/strauss.phar", - "@composer dump-autoload --no-scripts", - "find ./vendor/pluginever -type f -name '*.php' -exec perl -i -pe \"s/'framework-text-domain'/'wc-serial-numbers'/g\" {} +" + "@composer dump-autoload --no-scripts" ] }, "extra": { @@ -68,8 +57,7 @@ "namespace_prefix": "WooCommerceSerialNumbers\\", "classmap_prefix": "WooCommerceSerialNumbers__", "packages": [ - "pluginever/framework-plugin", - "pluginever/framework-settings", + "byteever/plugin", "pluginever/framework-model" ] } From 2d227e4d1160f889d8261eeffad404477b749538 Mon Sep 17 00:00:00 2001 From: sultann Date: Wed, 10 Jun 2026 22:43:24 +0600 Subject: [PATCH 03/18] Port main plugin file, Plugin class and Installer to the B8 framework --- {src => includes}/Installer.php | 190 ++++++++++++++++-------------- includes/Plugin.php | 101 ++++++++++++++++ src/Plugin.php | 202 -------------------------------- wc-serial-numbers.php | 66 +++++++---- 4 files changed, 249 insertions(+), 310 deletions(-) rename {src => includes}/Installer.php (68%) create mode 100644 includes/Plugin.php delete mode 100644 src/Plugin.php diff --git a/src/Installer.php b/includes/Installer.php similarity index 68% rename from src/Installer.php rename to includes/Installer.php index 39c2cb4d..ad1d3520 100644 --- a/src/Installer.php +++ b/includes/Installer.php @@ -5,35 +5,45 @@ defined( 'ABSPATH' ) || exit; /** - * Class Installer. + * Handles plugin installation. * * @since 1.4.2 * @package WooCommerceSerialNumbers */ -class Installer { +class Installer extends B8\Component { /** - * Update callbacks. + * Update hook name. + * + * @since 2.4.0 + * @var string + */ + const UPDATE_HOOK = 'wc_serial_numbers_run_update'; + + /** + * Upgrade routines keyed by the target version. * * @since 1.4.2 - * @var array + * @var array */ - protected $updates = array( - '1.1.2' => 'update_112', - '1.2.0' => 'update_120', - '1.2.1' => 'update_121', - '1.4.6' => 'update_146', - '1.5.6' => 'update_156', + protected array $updates = array( + '1.1.2' => array( self::class, 'update_112' ), + '1.2.0' => array( self::class, 'update_120' ), + '1.2.1' => array( self::class, 'update_121' ), + '1.4.6' => array( self::class, 'update_146' ), + '1.5.6' => array( self::class, 'update_156' ), ); /** - * Installer constructor. + * Register hooks. * * @since 1.4.2 + * @return void */ - public function __construct() { - add_filter( 'cron_schedules', array( __CLASS__, 'custom_cron_schedules' ), 20 ); // phpcs:ignore WordPress.WP.CronInterval.CronSchedulesInterval - add_action( 'init', array( $this, 'check_update' ), 0 ); + public function register(): void { + add_filter( 'cron_schedules', array( $this, 'custom_cron_schedules' ), 20 ); // phpcs:ignore WordPress.WP.CronInterval.CronSchedulesInterval + add_action( 'init', array( $this, 'maybe_update' ) ); + add_action( self::UPDATE_HOOK, array( $this, 'run_update' ) ); } /** @@ -44,7 +54,7 @@ public function __construct() { * @since 1.0.0 * @return array */ - public static function custom_cron_schedules( $schedules ) { + public function custom_cron_schedules( $schedules ) { $schedules ['once_a_minute'] = array( 'interval' => 60, 'display' => esc_html__( 'Once a Minute', 'wc-serial-numbers' ), @@ -54,59 +64,43 @@ public static function custom_cron_schedules( $schedules ) { } /** - * Check the plugin version and run the updater if necessary. - * - * This check is done on all requests and runs if the versions do not match. + * Run a pending upgrade. * * @since 1.4.2 * @return void */ - public function check_update() { - $db_version = WCSN()->get_db_version(); - $current_version = WCSN()->get_version(); - $requires_update = version_compare( $db_version, $current_version, '<' ); - $can_install = ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) && ! defined( 'IFRAME_REQUEST' ); - if ( $can_install && $requires_update ) { - static::install(); - - $update_versions = array_keys( $this->updates ); - usort( $update_versions, 'version_compare' ); - if ( ! is_null( $db_version ) && version_compare( $db_version, end( $update_versions ), '<' ) ) { - $this->update(); - } else { - WCSN()->update_db_version( $current_version ); - } + public function maybe_update(): void { + $legacy_version = get_option( $this->app->option_prefix . '_version' ); + if ( $legacy_version && ! $this->app->options->has( 'db_version' ) ) { + $this->app->options->update_db_version( $legacy_version ); + delete_option( $this->app->option_prefix . '_version' ); + } + + if ( version_compare( $this->app->version, $this->app->options->get_db_version(), '>' ) ) { + $this->install(); + $this->app->queue->add( self::UPDATE_HOOK ); } } /** - * Update the plugin. + * Run the pending upgrades. * * @since 1.4.2 * @return void */ - public function update() { - $db_version = WCSN()->get_db_version(); - foreach ( $this->updates as $version => $callbacks ) { - $callbacks = (array) $callbacks; - if ( version_compare( $db_version, $version, '<' ) ) { - foreach ( $callbacks as $callback ) { - WCSN()->log( sprintf( 'Updating to %s from %s', $version, $db_version ) ); - // if the callback return false then we need to update the db version. - $continue = call_user_func( array( $this, $callback ) ); - if ( ! $continue ) { - WCSN()->update_db_version( $version ); - $notice = sprintf( - /* translators: 1: plugin name 2: version number */ - __( '%1$s updated to version %2$s successfully.', 'wc-serial-numbers' ), - 'Serial Numbers for WooCommerce', - '' . $version . '' - ); - WCSN()->add_notice( $notice, 'success' ); - } - } + public function run_update(): void { + $installed = $this->app->options->get_db_version(); + + uksort( $this->updates, 'version_compare' ); + + foreach ( $this->updates as $version => $callback ) { + if ( version_compare( $installed, $version, '<' ) ) { + call_user_func( $callback ); + $this->app->options->update_db_version( $version, true ); } } + + $this->app->options->update_db_version( $this->app->version, true ); } /** @@ -115,26 +109,36 @@ public function update() { * @since 1.4.2 * @return void */ - public static function install() { - if ( ! is_blog_installed() ) { - return; - } + public function install(): void { + $this->create_tables(); + $this->create_cron_jobs(); + $this->create_defaults(); + + $this->app->options->add( 'install_date', current_time( 'mysql' ) ); + $this->app->options->update_db_version( $this->app->version ); - self::create_tables(); - self::create_cron_jobs(); - Admin\Settings::instance()->save_defaults(); - WCSN()->update_db_version( WCSN()->get_version(), false ); - add_option( 'wc_serial_numbers_install_date', current_time( 'mysql' ) ); - set_transient( 'wc_serial_numbers_activated', true, 30 ); - set_transient( 'wc_serial_numbers_activation_redirect', true, 30 ); + flush_rewrite_rules(); } /** - * Create tables. + * Clean up the plugin's runtime state. * + * @since 2.4.0 * @return void */ - public static function create_tables() { + public function deactivate(): void { + $this->app->queue->clear(); + + flush_rewrite_rules(); + } + + /** + * Create the database tables. + * + * @since 1.4.2 + * @return void + */ + protected function create_tables(): void { global $wpdb; $wpdb->hide_errors(); // todo rename table names to wcsn_keys and wcsn_activations. @@ -187,7 +191,7 @@ public static function create_tables() { * @since 1.0.0 * @return void */ - public static function create_cron_jobs() { + protected function create_cron_jobs(): void { // setup transient actions. if ( false === wp_next_scheduled( 'wc_serial_numbers_hourly_event' ) ) { wp_schedule_event( time(), 'hourly', 'wc_serial_numbers_hourly_event' ); @@ -198,13 +202,23 @@ public static function create_cron_jobs() { } } + /** + * Seed the default options. + * + * @since 1.4.2 + * @return void + */ + protected function create_defaults(): void { + $this->app->make( Admin\Settings::class )->save_defaults(); + } + /** * Update to version 1.1.2 * * @since 1.1.2 * @return void */ - protected function update_112() { + public static function update_112() { global $wpdb; $wpdb->query( "ALTER TABLE {$wpdb->prefix}wcsn_serial_numbers ADD KEY product_id(`product_id`)" ); $wpdb->query( "ALTER TABLE {$wpdb->prefix}wcsn_serial_numbers ADD KEY order_id (`order_id`)" ); @@ -224,7 +238,7 @@ protected function update_112() { * @since 1.2.0 * @return void */ - protected function update_120() { + public static function update_120() { wp_clear_scheduled_hook( 'wcsn_per_minute_event' ); wp_clear_scheduled_hook( 'wcsn_daily_event' ); wp_clear_scheduled_hook( 'wcsn_hourly_event' ); @@ -268,25 +282,25 @@ protected function update_120() { $wpdb->query( "UPDATE {$wpdb->prefix}serial_numbers set status='available', order_date='0000-00-00 00:00:00', order_id='0' WHERE status !='available' AND order_id='0' AND expire_date='0000-00-00 00:00:00'" ); // settings update. - $heading_text = $this->update_1_2_0_get_option( 'heading_text', 'Serial Numbers', 'wsn_delivery_settings' ); - $serial_col_heading = $this->update_1_2_0_get_option( 'table_column_heading', 'Serial Number', 'wsn_delivery_settings' ); - $serial_key_label = $this->update_1_2_0_get_option( 'serial_key_label', 'Serial Number', 'wsn_delivery_settings' ); - $serial_email_label = $this->update_1_2_0_get_option( 'serial_email_label', 'Activation Email', 'wsn_delivery_settings' ); - $show_validity = 'yes' === $this->update_1_2_0_get_option( 'show_validity', 'yes', 'wsn_delivery_settings' ); - $show_activation_limit = 'yes' === $this->update_1_2_0_get_option( 'show_activation_limit', 'yes', 'wsn_delivery_settings' ); + $heading_text = self::update_1_2_0_get_option( 'heading_text', 'Serial Numbers', 'wsn_delivery_settings' ); + $serial_col_heading = self::update_1_2_0_get_option( 'table_column_heading', 'Serial Number', 'wsn_delivery_settings' ); + $serial_key_label = self::update_1_2_0_get_option( 'serial_key_label', 'Serial Number', 'wsn_delivery_settings' ); + $serial_email_label = self::update_1_2_0_get_option( 'serial_email_label', 'Activation Email', 'wsn_delivery_settings' ); + $show_validity = 'yes' === self::update_1_2_0_get_option( 'show_validity', 'yes', 'wsn_delivery_settings' ); + $show_activation_limit = 'yes' === self::update_1_2_0_get_option( 'show_activation_limit', 'yes', 'wsn_delivery_settings' ); $license = get_option( 'woocommerce_serial_numbers_pro_pluginever_license' ); $options = array( - 'wc_serial_numbers_autocomplete_order' => $this->update_1_2_0_get_option( 'wsn_auto_complete_order', 'yes', 'wsn_delivery_settings' ), - 'wc_serial_numbers_reuse_serial_number' => $this->update_1_2_0_get_option( 'wsn_re_use_serial', 'no', 'wsn_delivery_settings' ), + 'wc_serial_numbers_autocomplete_order' => self::update_1_2_0_get_option( 'wsn_auto_complete_order', 'yes', 'wsn_delivery_settings' ), + 'wc_serial_numbers_reuse_serial_number' => self::update_1_2_0_get_option( 'wsn_re_use_serial', 'no', 'wsn_delivery_settings' ), 'wc_serial_numbers_disable_software_support' => 'no', 'wc_serial_numbers_manual_delivery' => 'no', 'wc_serial_numbers_hide_serial_number' => 'yes', - 'wc_serial_numbers_revoke_status_cancelled' => in_array( 'cancelled', $this->update_1_2_0_get_option( 'wsn_revoke_serial_number', array(), 'wsn_delivery_settings' ), true ) ? 'yes' : 'no', - 'wc_serial_numbers_revoke_status_refunded' => in_array( 'refunded', $this->update_1_2_0_get_option( 'wsn_revoke_serial_number', array(), 'wsn_delivery_settings' ), true ) ? 'yes' : 'no', - 'wc_serial_numbers_revoke_status_failed' => in_array( 'failed', $this->update_1_2_0_get_option( 'wsn_revoke_serial_number', array(), 'wsn_delivery_settings' ), true ) ? 'yes' : 'no', - 'wc_serial_numbers_enable_stock_notification' => $this->update_1_2_0_get_option( 'wsn_admin_bar_notification_send_email', 'yes', 'wsn_notification_settings' ), - 'wc_serial_numbers_stock_threshold' => $this->update_1_2_0_get_option( 'wsn_admin_bar_notification_number', '5', 'wsn_notification_settings' ), - 'wc_serial_numbers_notification_recipient' => $this->update_1_2_0_get_option( 'wsn_admin_bar_notification_email', get_option( 'admin_email' ), 'wsn_notification_settings' ), + 'wc_serial_numbers_revoke_status_cancelled' => in_array( 'cancelled', self::update_1_2_0_get_option( 'wsn_revoke_serial_number', array(), 'wsn_delivery_settings' ), true ) ? 'yes' : 'no', + 'wc_serial_numbers_revoke_status_refunded' => in_array( 'refunded', self::update_1_2_0_get_option( 'wsn_revoke_serial_number', array(), 'wsn_delivery_settings' ), true ) ? 'yes' : 'no', + 'wc_serial_numbers_revoke_status_failed' => in_array( 'failed', self::update_1_2_0_get_option( 'wsn_revoke_serial_number', array(), 'wsn_delivery_settings' ), true ) ? 'yes' : 'no', + 'wc_serial_numbers_enable_stock_notification' => self::update_1_2_0_get_option( 'wsn_admin_bar_notification_send_email', 'yes', 'wsn_notification_settings' ), + 'wc_serial_numbers_stock_threshold' => self::update_1_2_0_get_option( 'wsn_admin_bar_notification_number', '5', 'wsn_notification_settings' ), + 'wc_serial_numbers_notification_recipient' => self::update_1_2_0_get_option( 'wsn_admin_bar_notification_email', get_option( 'admin_email' ), 'wsn_notification_settings' ), 'wc_serial_numbers_order_table_heading' => $heading_text, 'wc_serial_numbers_order_table_col_product_label' => 'Product', 'wc_serial_numbers_order_table_col_key_label' => $serial_key_label, @@ -316,7 +330,7 @@ protected function update_120() { * * @return string */ - protected function update_1_2_0_get_option( $option_name, $default_value, $section = 'serial_numbers_settings' ) { + protected static function update_1_2_0_get_option( $option_name, $default_value, $section = 'serial_numbers_settings' ) { $settings = get_option( $section, array() ); return ! empty( $settings[ $option_name ] ) ? $settings[ $option_name ] : $default_value; @@ -328,7 +342,7 @@ protected function update_1_2_0_get_option( $option_name, $default_value, $secti * @since 1.2.1 * @return void */ - protected function update_121() { + public static function update_121() { global $wpdb; $wpdb->query( "ALTER TABLE {$wpdb->prefix}serial_numbers CHANGE order_id order_id bigint(20) DEFAULT NULL" ); $wpdb->query( "ALTER TABLE {$wpdb->prefix}serial_numbers CHANGE vendor_id vendor_id bigint(20) DEFAULT NULL" ); @@ -340,7 +354,7 @@ protected function update_121() { * @since 1.4.6 * @return void */ - protected function update_146() { + public static function update_146() { global $wpdb; // Update key status default value to 'available'. // Change key status. @@ -368,7 +382,7 @@ protected function update_146() { * * @since 1.5.6 */ - protected function update_156() { + public static function update_156() { global $wpdb; // if order_item_id column not exist then add it. if ( ! $wpdb->get_var( "SHOW COLUMNS FROM {$wpdb->prefix}serial_numbers LIKE 'order_item_id'" ) ) { diff --git a/includes/Plugin.php b/includes/Plugin.php new file mode 100644 index 00000000..daec9a7b --- /dev/null +++ b/includes/Plugin.php @@ -0,0 +1,101 @@ + + */ + protected array $components = array( + Installer::class, + Cron::class, + Cache::class, + Encryption::class, + Cart::class, + Orders::class, + Stocks::class, + Actions::class, + RestAPI::class, + Frontend\Frontend::class, + Compat::class, + API::class, + Admin\Admin::class, + ); + + /** + * Register hooks. + * + * @since 1.0.0 + * @return void + */ + public function bootstrap(): void { + define( 'WC_SERIAL_NUMBERS_VERSION', $this->version ); + define( 'WC_SERIAL_NUMBERS_FILE', $this->file ); + + add_action( 'before_woocommerce_init', array( $this, 'declare_compatibility' ) ); + add_action( 'woocommerce_loaded', array( $this, 'plugins_loaded' ), 0 ); + } + + /** + * Declare WooCommerce compatibility. + * + * @since 1.0.0 + * @return void + */ + public function declare_compatibility(): void { + if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) { + \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', $this->file, true ); + \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'cart_checkout_blocks', $this->file, true ); + } + } + + /** + * Initialize the plugin. + * + * @since 1.0.0 + * @return void + */ + public function plugins_loaded(): void { + $this->boot( $this->components ); + + /** + * Fires after the plugin has booted its components. + * + * @since 1.0.0 + */ + $this->do_action( 'loaded' ); + } + + /** + * Whether the Pro add-on is active. + * + * @since 2.4.0 + * @return bool True when the Pro add-on is active. + */ + public function is_pro_active(): bool { + return ! empty( $this->pro_basename ) && $this->plugin_active( $this->pro_basename ); + } + + /** + * Determines if the premium version active. + * + * @since 1.0.0 + * @return bool + * @deprecated 2.4.0 + */ + public function is_premium_active() { + return $this->is_pro_active(); + } +} diff --git a/src/Plugin.php b/src/Plugin.php deleted file mode 100644 index 62900458..00000000 --- a/src/Plugin.php +++ /dev/null @@ -1,202 +0,0 @@ -includes(); - $this->init_hooks(); - } - - /** - * Include required files. - * - * @since 1.0.0 - * @return void - */ - public function includes() { - require_once __DIR__ . '/functions.php'; - require_once __DIR__ . '/Deprecated/Functions.php'; - } - - /** - * Hook into actions and filters. - * - * @since 1.0.0 - * @return void - */ - public function init_hooks() { - register_activation_hook( $this->get_file(), array( Installer::class, 'install' ) ); - add_action( 'admin_notices', array( $this, 'dependencies_notices' ) ); - add_action( 'before_woocommerce_init', array( $this, 'on_before_woocommerce_init' ) ); - add_action( 'woocommerce_loaded', array( $this, 'init' ), 0 ); - } - - /** - * Run on before WooCommerce init. - * - * @since 1.0.0 - * @return void - */ - public function on_before_woocommerce_init() { - if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) { - \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', $this->get_file(), true ); - \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'cart_checkout_blocks', $this->get_file(), true ); - } - } - - /** - * Missing dependencies notice. - * - * @since 1.0.0 - * @return void - */ - public function dependencies_notices() { - if ( $this->is_plugin_active( 'woocommerce' ) ) { - return; - } - $notice = sprintf( - /* translators: 1: plugin name 2: WooCommerce */ - __( '%1$s requires %2$s to be installed and active.', 'wc-serial-numbers' ), - '' . esc_html( $this->get_name() ) . '', - '' . esc_html__( 'WooCommerce', 'wc-serial-numbers' ) . '' - ); - - echo '

' . wp_kses_post( $notice ) . '

'; - } - - /** - * Init the plugin after plugins_loaded so environment variables are set. - * - * @since 1.0.0 - * @return void - */ - public function init() { - $this->services['installer'] = new Installer(); - $this->services['cron'] = new Cron(); - $this->services['cache'] = new Cache(); - $this->services['encryption'] = new Encryption(); - $this->services['orders'] = new Orders(); - $this->services['stocks'] = new Stocks(); - $this->services['actions'] = new Actions(); - $this->services['restapi'] = new RestAPI(); - $this->services['utilities'] = new Utilities\Utilities(); - $this->services['frontend'] = new Frontend\Frontend(); - - // Compatibility. - if ( 'yes' === get_option( 'wcsn_enable_pdf_invoices', 'no' ) ) { - $this->services['compat'] = new Compat(); - } - - if ( wcsn_is_software_support_enabled() ) { - $this->services['api'] = new API(); - } - - if ( self::is_request( 'admin' ) ) { - $this->services['admin'] = new Admin\Admin(); - } - - // Init action. - do_action( 'wc_serial_numbers_loaded' ); - } - - /** - * Determines if the pro version active. - * - * @since 1.0.0 - * @return bool - * @deprecated 1.4.0 - */ - public static function is_pro_active() { - _deprecated_function( __METHOD__, '1.4.0', 'Plugin::is_premium_active()' ); - - return self::$instance->is_premium_active(); - } - - /** - * Determines if the wc is active. - * - * @since 1.0.0 - * @return bool - * @deprecated 1.4.0 - */ - public function is_wc_active() { - return $this->is_plugin_active( 'woocommerce/woocommerce.php' ); - } - - /** - * Plugin URL getter. - * - * @since 1.2.0 - * @return string - * @deprecated 1.4.0 - */ - public function plugin_url() { - _deprecated_function( __METHOD__, '1.4.0', 'Plugin::get_url()' ); - - return $this->get_url(); - } - - /** - * Plugin path getter. - * - * @since 1.2.0 - * @return string - * @deprecated 1.4.0 - */ - public function plugin_path() { - _deprecated_function( __METHOD__, '1.4.0', 'Plugin::get_path()' ); - - return $this->get_path(); - } - - /** - * Plugin base path name getter. - * - * @since 1.2.0 - * @return string - * @deprecated 1.4.2 - */ - public function plugin_basename() { - _deprecated_function( __METHOD__, '1.4.2', 'Plugin::get_basename()' ); - - return $this->get_basename(); - } - - /** - * Get assets path. - * - * @since 1.0.0 - * @return string - */ - public function get_assets_path() { - return $this->get_dir_path( 'assets/build/' ); - } - - /** - * Get assets url. - * - * @since 1.0.0 - * @return string - */ - public function get_assets_url() { - return $this->get_dir_url( 'assets/build/' ); - } -} diff --git a/wc-serial-numbers.php b/wc-serial-numbers.php index eb861d41..462ef58c 100644 --- a/wc-serial-numbers.php +++ b/wc-serial-numbers.php @@ -3,8 +3,8 @@ * Plugin Name: Serial Numbers * Plugin URI: https://pluginever.com/plugins/woocommerce-serial-numbers-pro/ * Description: Sell and manage license keys, serial numbers, and secret keys easily within your WooCommerce store. - * Version: 2.3.4 - * Requires at least: 5.2 + * Version: 2.4.0 + * Requires at least: 6.4 * Tested up to: 7.0 * Requires PHP: 7.4 * Author: PluginEver @@ -38,31 +38,40 @@ * @package WooCommerceSerialNumbers */ +use WooCommerceSerialNumbers\Installer; use WooCommerceSerialNumbers\Plugin; defined( 'ABSPATH' ) || exit; -// Autoloader. +// Load the Composer autoloader. require_once __DIR__ . '/vendor/autoload.php'; +require_once __DIR__ . '/includes/functions.php'; +require_once __DIR__ . '/includes/Deprecated/Functions.php'; + +$data = array( + 'version' => '2.4.0', + 'short_name' => 'wc_serial_numbers', + 'name' => 'Serial Numbers', + 'plugin_uri' => 'https://pluginever.com/plugins/woocommerce-serial-numbers-pro/', + 'settings_url' => admin_url( 'admin.php?page=wc-serial-numbers-settings' ), + 'pro_basename' => 'wc-serial-numbers-pro/wc-serial-numbers-pro.php', + 'upgrade_url' => 'https://pluginever.com/plugins/woocommerce-serial-numbers-pro/', + 'store_url' => 'https://pluginever.com', + 'docs_url' => 'https://pluginever.com/docs/wocommerce-serial-numbers/', + 'support_url' => 'https://pluginever.com/support/', + 'review_url' => 'https://wordpress.org/support/plugin/wc-serial-numbers/reviews/#new-post', +); + +Plugin::create( __FILE__, $data ); /** - * Get the plugin instance. + * Get the main plugin instance. * * @since 1.0.0 - * @return Plugin + * @return Plugin Plugin instance. */ -function WCSN() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionNameInvalid - $data = array( - 'file' => __FILE__, - 'settings_url' => admin_url( 'admin.php?page=wc-serial-numbers-settings' ), - 'support_url' => 'https://pluginever.com/support/', - 'docs_url' => 'https://pluginever.com/docs/wocommerce-serial-numbers/', - 'premium_url' => 'https://pluginever.com/plugins/woocommerce-serial-numbers-pro/', - 'premium_basename' => 'wc-serial-numbers-pro', - 'review_url' => 'https://wordpress.org/support/plugin/wc-serial-numbers/reviews/#new-post', - ); - - return Plugin::create( $data ); +function WCSN(): Plugin { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionNameInvalid + return Plugin::instance(); } /** @@ -72,9 +81,26 @@ function WCSN() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName. * @return Plugin * @deprecated 1.5.6 */ -function wc_serial_numbers() { +function wc_serial_numbers(): Plugin { return WCSN(); } -// Initialize the plugin. -WCSN(); +// Register the plugin activation and deactivation hooks. +WCSN()->on_activation( array( Installer::class, 'install' ) ); +WCSN()->on_deactivation( array( Installer::class, 'deactivate' ) ); + +// Show a notice when WooCommerce is missing. +if ( is_admin() && ! WCSN()->plugin_active( 'woocommerce' ) ) { + WCSN()->notices->add( + array( + 'notice_id' => 'wc_serial_numbers_missing_dependency', + 'type' => 'error', + 'dismissible' => false, + 'capability' => 'activate_plugins', + 'message' => WCSN()->plugin_path( 'includes/Admin/views/html-notice-dependency.php' ), + ) + ); +} + +// Boot the plugin. +WCSN()->bootstrap(); From b85e497118567182e667fa744d410e27fc8b0fec Mon Sep 17 00:00:00 2001 From: sultann Date: Wed, 10 Jun 2026 22:43:24 +0600 Subject: [PATCH 04/18] Convert hook classes to B8 components and consolidate source under includes --- {src => includes}/API.php | 26 +- {src => includes}/Actions.php | 36 +- includes/Admin/Admin.php | 346 ++++++++++++++++++ .../Admin/ListTables/ActivationsTable.php | 0 .../Admin/ListTables/KeysTable.php | 0 .../Admin/ListTables/ListTable.php | 0 .../Admin/ListTables/StockTable.php | 0 {src => includes}/Admin/Menus.php | 24 +- includes/Admin/Orders.php | 36 +- includes/Admin/Products.php | 34 +- includes/Admin/Requests.php | 32 +- includes/Admin/Settings.php | 313 ++++++++++++++++ .../Admin/views/html-api-actions.php | 0 .../Admin/views/html-api-validation.php | 4 +- .../Admin/views/html-edit-key.php | 0 .../Admin/views/html-list-activations.php | 0 .../Admin/views/html-list-keys.php | 0 .../Admin/views/html-list-stock.php | 0 .../Admin/views/html-notice-dependency.php | 18 + .../Admin/views/html-reports.php | 0 {src => includes}/Admin/views/html-tools.php | 0 includes/Cache.php | 39 ++ includes/Cart.php | 73 ++++ includes/Compat.php | 30 +- {src => includes}/Cron.php | 16 +- {src => includes}/Deprecated/Functions.php | 0 {src => includes}/Encryption.php | 8 +- {src => includes}/Frontend/Frontend.php | 30 +- {src => includes}/Frontend/Shortcodes.php | 10 +- {src => includes}/Functions/Template.php | 2 +- {src => includes}/Models/Activation.php | 0 {src => includes}/Models/Key.php | 0 {src => includes}/Models/Model.php | 0 includes/Orders.php | 128 +++++++ {src => includes}/RestAPI.php | 10 +- {src => includes}/Stocks.php | 22 +- {src => includes}/functions.php | 0 phpunit.xml | 2 +- src/Admin/Admin.php | 206 ----------- src/Admin/Notices.php | 167 --------- src/Admin/Settings.php | 215 ----------- src/Cache.php | 37 -- src/Orders.php | 174 --------- src/Utilities/Utilities.php | 31 -- 44 files changed, 1100 insertions(+), 969 deletions(-) rename {src => includes}/API.php (78%) rename {src => includes}/Actions.php (62%) create mode 100644 includes/Admin/Admin.php rename {src => includes}/Admin/ListTables/ActivationsTable.php (100%) rename {src => includes}/Admin/ListTables/KeysTable.php (100%) rename {src => includes}/Admin/ListTables/ListTable.php (100%) rename {src => includes}/Admin/ListTables/StockTable.php (100%) rename {src => includes}/Admin/Menus.php (93%) create mode 100644 includes/Admin/Settings.php rename {src => includes}/Admin/views/html-api-actions.php (100%) rename {src => includes}/Admin/views/html-api-validation.php (96%) rename {src => includes}/Admin/views/html-edit-key.php (100%) rename {src => includes}/Admin/views/html-list-activations.php (100%) rename {src => includes}/Admin/views/html-list-keys.php (100%) rename {src => includes}/Admin/views/html-list-stock.php (100%) create mode 100644 includes/Admin/views/html-notice-dependency.php rename {src => includes}/Admin/views/html-reports.php (100%) rename {src => includes}/Admin/views/html-tools.php (100%) create mode 100644 includes/Cache.php create mode 100644 includes/Cart.php rename {src => includes}/Cron.php (78%) rename {src => includes}/Deprecated/Functions.php (100%) rename {src => includes}/Encryption.php (96%) rename {src => includes}/Frontend/Frontend.php (67%) rename {src => includes}/Frontend/Shortcodes.php (98%) rename {src => includes}/Functions/Template.php (99%) rename {src => includes}/Models/Activation.php (100%) rename {src => includes}/Models/Key.php (100%) rename {src => includes}/Models/Model.php (100%) create mode 100644 includes/Orders.php rename {src => includes}/RestAPI.php (98%) rename {src => includes}/Stocks.php (66%) rename {src => includes}/functions.php (100%) delete mode 100644 src/Admin/Admin.php delete mode 100644 src/Admin/Notices.php delete mode 100644 src/Admin/Settings.php delete mode 100644 src/Cache.php delete mode 100644 src/Orders.php delete mode 100644 src/Utilities/Utilities.php diff --git a/src/API.php b/includes/API.php similarity index 78% rename from src/API.php rename to includes/API.php index d76cf426..099d8039 100644 --- a/src/API.php +++ b/includes/API.php @@ -2,6 +2,7 @@ namespace WooCommerceSerialNumbers; +use WooCommerceSerialNumbers\B8\Component; use WooCommerceSerialNumbers\Models\Activation; use WooCommerceSerialNumbers\Models\Key; @@ -13,17 +14,28 @@ * @since 1.0.0 * @package WooCommerceSerialNumbers */ -class API { +class API extends Component { /** - * API constructor. + * Whether to load. + * + * @since 2.4.0 + * @return bool + */ + public function autoload(): bool { + return wcsn_is_software_support_enabled(); + } + + /** + * Register hooks. * * @since 1.0.0 + * @return void */ - public function __construct() { + public function register(): void { // add query vars. - add_filter( 'query_vars', array( __CLASS__, 'add_query_vars' ), 0 ); - add_action( 'woocommerce_api_serial-numbers-api', array( __CLASS__, 'process_request' ) ); + add_filter( 'query_vars', array( $this, 'add_query_vars' ), 0 ); + add_action( 'woocommerce_api_serial-numbers-api', array( $this, 'process_request' ) ); } /** @@ -35,7 +47,7 @@ public function __construct() { * * @return array */ - public static function add_query_vars( $vars ) { + public function add_query_vars( $vars ) { $vars[] = 'product_id'; $vars[] = 'serial_key'; $vars[] = 'request'; @@ -51,7 +63,7 @@ public static function add_query_vars( $vars ) { * * @since 1.0.0 */ - public static function process_request() { + public function process_request() { global $wp; $action = isset( $wp->query_vars['request'] ) ? sanitize_key( $wp->query_vars['request'] ) : ''; $product_id = isset( $wp->query_vars['product_id'] ) ? absint( $wp->query_vars['product_id'] ) : ''; diff --git a/src/Actions.php b/includes/Actions.php similarity index 62% rename from src/Actions.php rename to includes/Actions.php index b26fe1f2..66106c4d 100644 --- a/src/Actions.php +++ b/includes/Actions.php @@ -2,6 +2,7 @@ namespace WooCommerceSerialNumbers; +use WooCommerceSerialNumbers\B8\Component; use WooCommerceSerialNumbers\Models\Activation; use WooCommerceSerialNumbers\Models\Key; @@ -15,21 +16,22 @@ * @since 1.5.6 * @package WooCommerceSerialNumbers */ -class Actions { +class Actions extends Component { /** - * Actions constructor. + * Register hooks. * - * @since 1.5.6 + * @since 1.0.0 + * @return void */ - public function __construct() { - add_action( 'wc_serial_numbers_key_db_data', array( __CLASS__, 'decrypt_key' ) ); - add_action( 'wc_serial_numbers_key_insert_data', array( __CLASS__, 'encrypt_key' ) ); - add_action( 'wc_serial_numbers_key_update_data', array( __CLASS__, 'encrypt_key' ) ); - add_action( 'wc_serial_numbers_key_insert', array( __CLASS__, 'enable_product' ) ); - add_action( 'wc_serial_numbers_key_deleted', array( __CLASS__, 'delete_activations' ) ); - add_action( 'wc_serial_numbers_activation_inserted', array( __CLASS__, 'update_activation_count' ) ); - add_action( 'wc_serial_numbers_activation_deleted', array( __CLASS__, 'update_activation_count' ) ); + public function register(): void { + add_action( 'wc_serial_numbers_key_db_data', array( $this, 'decrypt_key' ) ); + add_action( 'wc_serial_numbers_key_insert_data', array( $this, 'encrypt_key' ) ); + add_action( 'wc_serial_numbers_key_update_data', array( $this, 'encrypt_key' ) ); + add_action( 'wc_serial_numbers_key_insert', array( $this, 'enable_product' ) ); + add_action( 'wc_serial_numbers_key_deleted', array( $this, 'delete_activations' ) ); + add_action( 'wc_serial_numbers_activation_inserted', array( $this, 'update_activation_count' ) ); + add_action( 'wc_serial_numbers_activation_deleted', array( $this, 'update_activation_count' ) ); } /** @@ -39,7 +41,7 @@ public function __construct() { * * @since 1.4.6 */ - public static function decrypt_key( $data ) { + public function decrypt_key( $data ) { if ( ! empty( $data['serial_key'] ) ) { $data['serial_key'] = wcsn_decrypt_key( $data['serial_key'] ); } @@ -54,7 +56,7 @@ public static function decrypt_key( $data ) { * * @since 1.4.6 */ - public static function encrypt_key( $data ) { + public function encrypt_key( $data ) { if ( ! empty( $data['serial_key'] ) ) { $data['serial_key'] = wcsn_encrypt_key( $data['serial_key'] ); } @@ -69,7 +71,7 @@ public static function encrypt_key( $data ) { * * @since 1.4.6 */ - public static function enable_product( $key_id ) { + public function enable_product( $key_id ) { $key = Key::get( $key_id ); if ( $key ) { @@ -88,7 +90,7 @@ public static function enable_product( $key_id ) { * * @since 1.4.6 */ - public static function delete_activations( $key ) { + public function delete_activations( $key ) { $activations = $key->get_activations(); if ( $activations ) { foreach ( $activations as $activation ) { @@ -104,7 +106,7 @@ public static function delete_activations( $key ) { * * @since 1.4.6 */ - public static function revoke_order_item_keys( $revoke ) { + public function revoke_order_item_keys( $revoke ) { if ( 'yes' !== get_option( 'wc_serial_numbers_revoke_keys', 'yes' ) ) { $revoke = false; } @@ -120,7 +122,7 @@ public static function revoke_order_item_keys( $revoke ) { * * @since 1.0.0 */ - public static function update_activation_count( $activation ) { + public function update_activation_count( $activation ) { $key = Key::get( $activation->get_serial_id() ); if ( $key ) { $key->recount_remaining_activation(); diff --git a/includes/Admin/Admin.php b/includes/Admin/Admin.php new file mode 100644 index 00000000..a394ed18 --- /dev/null +++ b/includes/Admin/Admin.php @@ -0,0 +1,346 @@ + + */ + public array $components = array( + Settings::class, + Menus::class, + Requests::class, + Orders::class, + Products::class, + ); + + /** + * Whether to load. + * + * @since 2.4.0 + * @return bool + */ + public function autoload(): bool { + return is_admin(); + } + + /** + * Register hooks. + * + * @since 1.0.0 + * @return void + */ + public function register(): void { + add_action( 'admin_init', array( $this, 'register_notices' ) ); + add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); + add_filter( 'woocommerce_screen_ids', array( $this, 'screen_ids' ) ); + add_filter( 'admin_footer_text', array( $this, 'admin_footer_text' ), PHP_INT_MAX ); + add_filter( 'update_footer', array( $this, 'update_footer' ), PHP_INT_MAX ); + add_filter( 'allowed_redirect_hosts', array( $this, 'allowed_redirect_hosts' ) ); + add_filter( 'plugin_action_links_' . $this->app->basename(), array( $this, 'plugin_action_links' ) ); + add_filter( 'plugin_row_meta', array( $this, 'plugin_row_meta' ), 10, 2 ); + } + + /** + * Register the admin notices. + * + * @since 1.0.0 + * @return void + */ + public function register_notices(): void { + $is_outdated_pro = defined( 'WC_SERIAL_NUMBER_PRO_PLUGIN_VERSION' ) && version_compare( WC_SERIAL_NUMBER_PRO_PLUGIN_VERSION, '1.4.0', '<' ); + if ( ! $is_outdated_pro ) { + $is_outdated_pro = function_exists( 'wc_serial_numbers_pro' ) && is_callable( array( wc_serial_numbers_pro(), 'get_version' ) ) && wc_serial_numbers_pro()->get_version() && version_compare( wc_serial_numbers_pro()->get_version(), '1.4.0', '<' ); + } + + if ( $is_outdated_pro ) { + $this->app->notices->add( + array( + 'notice_id' => 'wc_serial_numbers_outdated_pro', + 'type' => 'error', + 'dismissible' => false, + 'message' => sprintf( + /* translators: %s: link to the plugin page */ + __( '%s is not functional because you are using outdated version of the plugin, please update to the version 1.3.8 or higher.', 'wc-serial-numbers' ), + 'Serial Numbers Pro' + ), + ) + ); + } + + if ( ! $this->app->is_pro_active() ) { + $this->app->notices->add( + array( + 'notice_id' => 'wc_serial_numbers_upgrade_to_pro_wcsnpro10', + 'type' => 'info', + 'class' => 'notice-alt notice-large', + 'dismissible' => true, + 'message' => sprintf( + /* translators: 1: discount amount 2: promo code 3: link start 4: link end 5: plugin name 6: PRO label */ + __( 'Upgrade to %6$s to unlock the full potential of %5$s and avail a %1$s discount by using the promo code %2$s. %3$s Upgrade Now%4$s.', 'wc-serial-numbers' ), + '10%', + 'WCSNPRO10', + '', + '', + '' . esc_html( $this->app->get( 'name' ) ) . '', + 'PRO' + ), + ) + ); + } + } + + /** + * Add plugin action links. + * + * @since 1.0.0 + * @param array $links Plugin action links. + * @return array + */ + public function plugin_action_links( array $links ): array { + $actions = array( + 'settings' => sprintf( + '%s', + esc_url( (string) $this->app->get( 'settings_url' ) ), + esc_html__( 'Settings', 'wc-serial-numbers' ) + ), + ); + + if ( ! $this->app->is_pro_active() ) { + $pro_link = add_query_arg( + array( + 'utm_source' => 'plugins-page', + 'utm_medium' => 'plugin-action-link', + 'utm_campaign' => 'plugins-page', + 'utm_term' => 'go-pro', + 'utm_id' => $this->app->slug, + ), + (string) $this->app->get( 'upgrade_url' ) + ); + $actions['go_pro'] = sprintf( + '%2$s', + esc_url( $pro_link ), + esc_html__( 'Go Pro', 'wc-serial-numbers' ) + ); + } + + return array_merge( $actions, $links ); + } + + /** + * Add the plugin row meta links. + * + * @since 1.0.0 + * @param array $links Plugin row meta links. + * @param string $file Plugin file path relative to the plugins directory. + * @return array + */ + public function plugin_row_meta( array $links, string $file ): array { + if ( $file !== $this->app->basename() ) { + return $links; + } + + $links[] = sprintf( + '%s', + esc_url( (string) $this->app->get( 'docs_url' ) ), + esc_html__( 'Documentation', 'wc-serial-numbers' ) + ); + + $links[] = sprintf( + '%s', + esc_url( (string) $this->app->get( 'support_url' ) ), + esc_html__( 'Support', 'wc-serial-numbers' ) + ); + + $links[] = sprintf( + '%s', + esc_url( (string) $this->app->get( 'review_url' ) ), + esc_html__( 'Review', 'wc-serial-numbers' ) + ); + + $links[] = sprintf( + '%s', + esc_url( (string) $this->app->get( 'store_url' ) ), + esc_html__( 'More Plugins', 'wc-serial-numbers' ) + ); + + return $links; + } + + /** + * Enqueue admin scripts. + * + * @param string $hook Hook name. + * + * @since 1.0.0 + */ + public function enqueue_scripts( $hook ) { + if ( ! in_array( $hook, self::get_screen_ids(), true ) ) { + return; + } + + // Determine which select2 to use. WooCommerce 3.6+ uses its own select2 but the latest WooCommerce 10.3+ deprecated it and added wc-select2 style/js handle. + $which_select2_style = wp_style_is( 'wc-select2', 'registered' ) ? 'wc-select2' : 'select2'; + $which_select2_script = wp_script_is( 'wc-select2', 'registered' ) ? 'wc-select2' : 'select2'; + + wp_enqueue_style( 'jquery-ui-style' ); + wp_enqueue_style( $which_select2_style ); + wp_enqueue_script( 'jquery-ui-datepicker' ); + + $this->app->scripts->enqueue_style( 'wc-serial-numbers-admin', 'css/admin-style.css' ); + $this->app->scripts->enqueue_script( 'wc-serial-numbers-admin', 'js/admin-script.js', array( 'jquery', 'jquery-ui-datepicker', $which_select2_script, 'wp-util' ) ); + wp_localize_script( + 'wc-serial-numbers-admin', + 'wc_serial_numbers_vars', + array( + 'i18n' => array( + 'search_product' => __( 'Search by product', 'wc-serial-numbers' ), + 'search_order' => __( 'Search by order', 'wc-serial-numbers' ), + 'search_customer' => __( 'Search by customer', 'wc-serial-numbers' ), + 'show' => __( 'Show', 'wc-serial-numbers' ), + 'hide' => __( 'Hide', 'wc-serial-numbers' ), + 'copied' => __( 'Copied', 'wc-serial-numbers' ), + ), + 'search_nonce' => wp_create_nonce( 'wc_serial_numbers_search_nonce' ), + 'ajax_nonce' => wp_create_nonce( 'wcsn_ajax_search' ), + 'ajaxurl' => admin_url( 'admin-ajax.php' ), + 'apiurl' => site_url( '?wc-api=serial-numbers-api' ), + ) + ); + + // add inline style for select2 --wp-admin-theme-color. + wp_add_inline_style( 'common', ':root{--wp-admin-theme-color:#0073aa;}' ); + } + + /** + * Add the plugin screens to the WooCommerce screens. + * This will load the WooCommerce admin styles and scripts. + * + * @param array $ids Screen ids. + * + * @return array + */ + public function screen_ids( $ids ) { + return array_merge( $ids, self::get_screen_ids() ); + } + + /** + * Admin footer text. + * + * @param string $footer_text Footer text. + * + * @since 1.0.0 + * @return string + */ + public function admin_footer_text( $footer_text ) { + if ( $this->app->get( 'review_url' ) && in_array( get_current_screen()->id, self::get_screen_ids(), true ) ) { + $footer_text = sprintf( + /* translators: 1: Plugin name 2: WordPress */ + __( 'Thank you for using %1$s! Share your appreciation with a five-star review %2$s.', 'wc-serial-numbers' ), + '' . esc_html( $this->app->get( 'name' ) ) . '', + 'here' + ); + } + + return $footer_text; + } + + /** + * Update footer. + * + * @param string $footer_text Footer text. + * + * @since 1.0.0 + * @return string + */ + public function update_footer( $footer_text ) { + if ( in_array( get_current_screen()->id, self::get_screen_ids(), true ) ) { + /* translators: 1: Plugin version */ + $footer_text = sprintf( esc_html__( 'Version %s', 'wc-serial-numbers' ), $this->app->version ); + } + + return $footer_text; + } + + /** + * Get screen ids. + * + * @since 1.0.0 + * @return array + */ + public static function get_screen_ids() { + $screen_id = sanitize_title( __( 'Serial Numbers', 'wc-serial-numbers' ) ); + $screen_ids = array( + 'toplevel_page_' . $screen_id, + 'toplevel_page_wc-serial-numbers', + $screen_id . '_page_wc-serial-numbers-activations', + $screen_id . '_page_wc-serial-numbers-products', + $screen_id . '_page_wc-serial-numbers-tools', + $screen_id . '_page_wc-serial-numbers-reports', + $screen_id . '_page_wc-serial-numbers-settings', + ); + + return apply_filters( 'wc_serial_numbers_screen_ids', $screen_ids ); + } + + /** + * Render a view. + * + * @param string $view The name of the view to render. + * @param array $args The arguments to pass to the view. + * @param string $path The path to the view file. + * + * @since 1.0.0 + * @return void + */ + public static function view( $view, $args = array(), $path = '' ) { + if ( empty( $path ) ) { + $path = __DIR__ . '/views/'; + } + // replace .php extension if it was added. + $view = str_replace( '.php', '', $view ); + $view = ltrim( $view, '/' ); + $path = rtrim( $path, '/' ); + + $file = $path . '/' . $view . '.php'; + + if ( ! file_exists( $file ) ) { + return; + } + + if ( $args && is_array( $args ) ) { + extract( $args ); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract + } + + include $file; + } + + /** + * Allowed redirect hosts. + * + * @param array $hosts Allowed hosts. + * + * @since 1.0.0 + * @return array + */ + public function allowed_redirect_hosts( $hosts ) { + $hosts[] = 'pluginever.com'; + $hosts[] = 'www.pluginever.com'; + + return $hosts; + } +} diff --git a/src/Admin/ListTables/ActivationsTable.php b/includes/Admin/ListTables/ActivationsTable.php similarity index 100% rename from src/Admin/ListTables/ActivationsTable.php rename to includes/Admin/ListTables/ActivationsTable.php diff --git a/src/Admin/ListTables/KeysTable.php b/includes/Admin/ListTables/KeysTable.php similarity index 100% rename from src/Admin/ListTables/KeysTable.php rename to includes/Admin/ListTables/KeysTable.php diff --git a/src/Admin/ListTables/ListTable.php b/includes/Admin/ListTables/ListTable.php similarity index 100% rename from src/Admin/ListTables/ListTable.php rename to includes/Admin/ListTables/ListTable.php diff --git a/src/Admin/ListTables/StockTable.php b/includes/Admin/ListTables/StockTable.php similarity index 100% rename from src/Admin/ListTables/StockTable.php rename to includes/Admin/ListTables/StockTable.php diff --git a/src/Admin/Menus.php b/includes/Admin/Menus.php similarity index 93% rename from src/Admin/Menus.php rename to includes/Admin/Menus.php index f9d3455c..bdb83b6d 100644 --- a/src/Admin/Menus.php +++ b/includes/Admin/Menus.php @@ -2,6 +2,8 @@ namespace WooCommerceSerialNumbers\Admin; +use WooCommerceSerialNumbers\B8\Component; + use WooCommerceSerialNumbers\Models\Key; defined( 'ABSPATH' ) || exit; @@ -12,13 +14,13 @@ * @since 1.0.0 * @package WooCommerceSerialNumbers\Admin */ -class Menus { +class Menus extends Component { /** - * Menus constructor. + * Register hooks. * * @since 1.0.0 */ - public function __construct() { + public function register(): void { // Register the menus. add_action( 'admin_menu', array( $this, 'main_menu' ) ); add_action( 'admin_menu', array( $this, 'activations_menu' ), 40 ); @@ -171,7 +173,7 @@ public function settings_menu() { __( 'Settings', 'wc-serial-numbers' ), 'manage_woocommerce', // phpcs:ignore WordPress.WP.Capabilities.Unknown 'wc-serial-numbers-settings', - array( Settings::class, 'output' ) + $this->app->callback( array( Settings::class, 'render' ) ) ); } @@ -182,7 +184,7 @@ public function settings_menu() { * @return void */ public function promo_menu() { - if ( ! WCSN()->is_premium_active() ) { + if ( ! $this->app->is_pro_active() ) { add_submenu_page( 'wc-serial-numbers', '', @@ -335,14 +337,14 @@ public static function import_tab() {

- <?php esc_attr_e( 'Import Serial Numbers', 'wc-serial-numbers' ); ?> + <?php esc_attr_e( 'Import Serial Numbers', 'wc-serial-numbers' ); ?>

- <?php esc_attr_e( 'Import Serial Numbers', 'wc-serial-numbers' ); ?> + <?php esc_attr_e( 'Import Serial Numbers', 'wc-serial-numbers' ); ?>
- <?php esc_attr_e( 'Export Serial Numbers', 'wc-serial-numbers' ); ?> + <?php esc_attr_e( 'Export Serial Numbers', 'wc-serial-numbers' ); ?> - <?php esc_attr_e( 'Generators', 'wc-serial-numbers' ); ?> + <?php esc_attr_e( 'Generators', 'wc-serial-numbers' ); ?> WCSN()->get_version(), + 'Serial Numbers version' => WCSN()->version, ); - if ( WCSN()->is_premium_active() && function_exists( 'wc_serial_numbers_pro' ) ) { + if ( WCSN()->is_pro_active() && function_exists( 'wc_serial_numbers_pro' ) ) { $statuses['Serial Numbers Pro version'] = WCSN_PRO()->get_version(); } diff --git a/includes/Admin/Orders.php b/includes/Admin/Orders.php index dd135693..4451e610 100644 --- a/includes/Admin/Orders.php +++ b/includes/Admin/Orders.php @@ -2,6 +2,7 @@ namespace WooCommerceSerialNumbers\Admin; +use WooCommerceSerialNumbers\B8\Component; defined( 'ABSPATH' ) || exit; // Exit if accessed directly. /** @@ -10,25 +11,26 @@ * @since 1.0.0 * @package WooCommerceSerialNumbers\Admin */ -class Orders { +class Orders extends Component { /** - * Orders constructor. + * Register hooks. * * @since 1.0.0 + * @return void */ - public function __construct() { + public function register(): void { // add custom order action. add_filter( 'woocommerce_order_actions', array( $this, 'add_order_action' ) ); // handle custom order action. add_action( 'woocommerce_order_action_wcsn_add_keys', array( $this, 'handle_order_action' ) ); add_action( 'woocommerce_order_action_wcsn_remove_keys', array( $this, 'handle_order_action' ) ); - add_filter( 'manage_edit-shop_order_columns', array( __CLASS__, 'add_order_serial_column' ) ); - add_action( 'manage_shop_order_posts_custom_column', array( __CLASS__, 'add_order_serial_column_content' ), 20, 2 ); + add_filter( 'manage_edit-shop_order_columns', array( $this, 'add_order_serial_column' ) ); + add_action( 'manage_shop_order_posts_custom_column', array( $this, 'add_order_serial_column_content' ), 20, 2 ); // HPOS compatibility action. - add_filter( 'manage_woocommerce_page_wc-orders_columns', array( __CLASS__, 'add_order_serial_column' ), 20 ); - add_action( 'manage_woocommerce_page_wc-orders_custom_column', array( __CLASS__, 'hpos_add_order_serial_column_content' ), 20, 2 ); + add_filter( 'manage_woocommerce_page_wc-orders_columns', array( $this, 'add_order_serial_column' ), 20 ); + add_action( 'manage_woocommerce_page_wc-orders_custom_column', array( $this, 'hpos_add_order_serial_column_content' ), 20, 2 ); // Add order bulk action. add_filter( 'bulk_actions-edit-shop_order', array( $this, 'add_order_bulk_action' ) ); @@ -38,7 +40,7 @@ public function __construct() { add_filter( 'handle_bulk_actions-woocommerce_page_wc-orders', array( $this, 'handle_order_bulk_action' ), 10, 3 ); // Display order keys in order details. - add_action( 'woocommerce_after_order_itemmeta', array( __CLASS__, 'display_order_item_meta' ), 10, 3 ); + add_action( 'woocommerce_after_order_itemmeta', array( $this, 'display_order_item_meta' ), 10, 3 ); } /** @@ -66,7 +68,7 @@ public function add_order_action( $actions ) { public function handle_order_action( $order ) { // Must have manage woocommerce user capability role to access this endpoint. if ( ! current_user_can( 'manage_woocommerce' ) ) { // phpcs:ignore WordPress.WP.Capabilities.Unknown - WCSN()->add_notice( __( 'You do not have permission to perform this action.', 'wc-serial-numbers' ), 'error' ); + $this->app->flash->error( __( 'You do not have permission to perform this action.', 'wc-serial-numbers' ) ); exit; } @@ -76,11 +78,11 @@ public function handle_order_action( $order ) { if ( 'wcsn_add_keys' === $action ) { wcsn_order_update_keys( $order_id ); // add a notice. - WCSN()->add_notice( __( 'Serial keys added successfully to the order.', 'wc-serial-numbers' ) ); + $this->app->flash->success( __( 'Serial keys added successfully to the order.', 'wc-serial-numbers' ) ); } elseif ( 'wcsn_remove_keys' === $action ) { wcsn_order_remove_keys( $order_id ); // add a notice. - WCSN()->add_notice( __( 'Serial keys removed successfully from the order.', 'wc-serial-numbers' ) ); + $this->app->flash->success( __( 'Serial keys removed successfully from the order.', 'wc-serial-numbers' ) ); } } @@ -92,7 +94,7 @@ public function handle_order_action( $order ) { * @since 1.2.0 * @return array|string[] */ - public static function add_order_serial_column( $columns ) { + public function add_order_serial_column( $columns ) { $position = 3; $new = array_slice( $columns, 0, $position, true ) + array( 'order_serials' => '' ) + array_slice( $columns, $position, count( $columns ) - $position, true ); @@ -107,7 +109,7 @@ public static function add_order_serial_column( $columns ) { * * @since 1.2.0 */ - public static function add_order_serial_column_content( $column, $order_id ) { + public function add_order_serial_column_content( $column, $order_id ) { $order_status = wc_get_order( $order_id )->get_status(); @@ -145,7 +147,7 @@ public static function add_order_serial_column_content( $column, $order_id ) { * * @since 1.6.9 */ - public static function hpos_add_order_serial_column_content( $column, $order_id ) { + public function hpos_add_order_serial_column_content( $column, $order_id ) { $order = wc_get_order( $order_id ); $order_id = $order->get_id(); @@ -205,7 +207,7 @@ public function add_order_bulk_action( $actions ) { public function handle_order_bulk_action( $redirect_to, $action, $order_ids ) { // Must have manage woocommerce user capability role to access this endpoint. if ( ! current_user_can( 'manage_woocommerce' ) ) { // phpcs:ignore WordPress.WP.Capabilities.Unknown - WCSN()->add_notice( __( 'You do not have permission to perform this action.', 'wc-serial-numbers' ), 'error' ); + $this->app->flash->error( __( 'You do not have permission to perform this action.', 'wc-serial-numbers' ) ); return $redirect_to; } @@ -221,7 +223,7 @@ public function handle_order_bulk_action( $redirect_to, $action, $order_ids ) { } } // Translators: %d: number of orders. - WCSN()->add_notice( sprintf( __( '%d orders updated successfully.', 'wc-serial-numbers' ), count( $order_ids ) ) ); + $this->app->flash->success( sprintf( __( '%d orders updated successfully.', 'wc-serial-numbers' ), count( $order_ids ) ) ); $redirect_to = add_query_arg( 'bulk_action', $action, $redirect_to ); } @@ -237,7 +239,7 @@ public function handle_order_bulk_action( $redirect_to, $action, $order_ids ) { * * @since 1.0.0 */ - public static function display_order_item_meta( $item_id, $item, $product ) { + public function display_order_item_meta( $item_id, $item, $product ) { $order_id = wc_get_order_id_by_order_item_id( $item_id ); if ( ! $order_id || ! $product || ! wcsn_is_product_enabled( $product->get_id() ) ) { return; diff --git a/includes/Admin/Products.php b/includes/Admin/Products.php index 92956b03..cb9d20c2 100644 --- a/includes/Admin/Products.php +++ b/includes/Admin/Products.php @@ -2,6 +2,7 @@ namespace WooCommerceSerialNumbers\Admin; +use WooCommerceSerialNumbers\B8\Component; defined( 'ABSPATH' ) || exit; // Exit if accessed directly. /** @@ -10,19 +11,20 @@ * @since 1.0.0 * @package WooCommerceSerialNumbers\Admin */ -class Products { +class Products extends Component { /** - * Products constructor. + * Register hooks. * * @since 1.0.0 + * @return void */ - public function __construct() { - add_action( 'admin_head', array( __CLASS__, 'print_style' ) ); - add_filter( 'woocommerce_product_data_tabs', array( __CLASS__, 'product_data_tab' ) ); - add_action( 'woocommerce_product_data_panels', array( __CLASS__, 'product_write_panel' ) ); - add_filter( 'woocommerce_process_product_meta', array( __CLASS__, 'product_save_data' ) ); - add_action( 'woocommerce_product_after_variable_attributes', array( __CLASS__, 'variable_product_content' ), 10, 3 ); + public function register(): void { + add_action( 'admin_head', array( $this, 'print_style' ) ); + add_filter( 'woocommerce_product_data_tabs', array( $this, 'product_data_tab' ) ); + add_action( 'woocommerce_product_data_panels', array( $this, 'product_write_panel' ) ); + add_filter( 'woocommerce_process_product_meta', array( $this, 'product_save_data' ) ); + add_action( 'woocommerce_product_after_variable_attributes', array( $this, 'variable_product_content' ), 10, 3 ); } /** @@ -30,7 +32,7 @@ public function __construct() { * * @since 1.0.0 */ - public static function print_style() { + public function print_style() { ?>