diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 94d6e0cf..00000000 --- a/.eslintrc.js +++ /dev/null @@ -1,29 +0,0 @@ -module.exports = { - root: true, - extends: ['plugin:@wordpress/eslint-plugin/recommended-with-formatting'], - plugins: ['import'], - env: { - browser: true, - es6: true, - }, - globals: { - wp: 'readonly', - ajaxurl: 'readonly', - FormData: 'readonly', - window: true, - document: true, - jQuery: true, - }, - rules: { - 'import/no-unresolved': 'off', - 'import/no-extraneous-dependencies': 'off', - 'no-console': 'off', - '@wordpress/dependency-group': 'warn', - '@wordpress/i18n-text-domain': [ - 'error', - { - allowedTextDomain: 'wc-serial-numbers', - }, - ], - }, -}; diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 29ae1a3e..0715004b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -38,7 +38,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f305ef02..97e62bce 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout trunk - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: trunk diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..03ce9740 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,43 @@ +name: Tests + +on: + workflow_dispatch: + +jobs: + phpunit: + name: PHP ${{ matrix.php }} / WP ${{ matrix.wordpress }} + runs-on: ubuntu-latest + + services: + mysql: + image: mysql:8.0 + env: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: wordpress_test + ports: + - 3306:3306 + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + + strategy: + matrix: + php: ['7.4', '8.0', '8.1', '8.2', '8.3'] + wordpress: ['6.4', '6.5', 'latest'] + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + tools: composer, wp-cli + + - name: Setup tests + run: | + composer config -g github-oauth.github.com "${{ secrets.ACCESS_TOKEN }}" + composer install --no-interaction + bash tests/bin/install.sh wordpress_test root root 127.0.0.1 ${{ matrix.wordpress }} + + - name: Run tests + run: composer test diff --git a/.github/workflows/trunk.yml b/.github/workflows/trunk.yml index 00565dcb..48bd1118 100644 --- a/.github/workflows/trunk.yml +++ b/.github/workflows/trunk.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/.prettierrc.js b/.prettierrc.js deleted file mode 100644 index 51b8aeb4..00000000 --- a/.prettierrc.js +++ /dev/null @@ -1,3 +0,0 @@ -// Import the default config file and expose it in the project root. -// Useful for editor integrations. -module.exports = require( '@wordpress/prettier-config' ); diff --git a/assets/src/images/add-generator.png b/assets/images/add-generator.png similarity index 100% rename from assets/src/images/add-generator.png rename to assets/images/add-generator.png diff --git a/assets/src/images/csv-export.png b/assets/images/csv-export.png similarity index 100% rename from assets/src/images/csv-export.png rename to assets/images/csv-export.png diff --git a/assets/src/images/csv-import.png b/assets/images/csv-import.png similarity index 100% rename from assets/src/images/csv-import.png rename to assets/images/csv-import.png diff --git a/assets/src/images/txt-import.png b/assets/images/txt-import.png similarity index 100% rename from assets/src/images/txt-import.png rename to assets/images/txt-import.png diff --git a/assets/src/js/admin-script.js b/assets/src/admin.js similarity index 100% rename from assets/src/js/admin-script.js rename to assets/src/admin.js diff --git a/assets/src/css/admin-style.scss b/assets/src/admin.scss similarity index 93% rename from assets/src/css/admin-style.scss rename to assets/src/admin.scss index f971b770..57c65a99 100644 --- a/assets/src/css/admin-style.scss +++ b/assets/src/admin.scss @@ -1,6 +1,6 @@ -@forward "../../../vendor/pluginever/framework-plugin/src/assets/css/layout"; -@forward "../../../vendor/pluginever/framework-plugin/src/assets/css/card"; -@forward "../../../vendor/pluginever/framework-plugin/src/assets/css/form"; +@forward "common/layout"; +@forward "common/card"; +@forward "common/form"; // Card component. .wcsn-card { diff --git a/assets/src/common/_card.scss b/assets/src/common/_card.scss new file mode 100644 index 00000000..1bf7136b --- /dev/null +++ b/assets/src/common/_card.scss @@ -0,0 +1,240 @@ +%card { + display: block; + position: relative; + margin: 0 auto 1.5em; + box-sizing: border-box; + width: 100%; + max-width: none; + min-width: auto; + min-height: 1px; + background: #fff; + border: 1px solid #e1e2e2; + font-size: 13px; + line-height: 1.5; + + [class*=pev] { + box-sizing: inherit; + } + + &:before, &:after { + content: ""; + display: table; + box-sizing: inherit; + } + + h2, h3, h4, h5, h6 { + margin-top: 0; + padding-top: 0; + } + + ul,ol{ + li{ + line-height: 1.7; + margin-bottom: 0.5em; + } + } +} + +%card-body { + position: relative; + color: #555; + + h2, h3, h4 { + color: inherit; + } + + & > *:first-child { + margin-top: 0; + } + + table:not(.widefat) { + width: 100%; + + td, th { + vertical-align: top; + text-align: left + } + + th { + padding: 0 6px 6px 0 + } + + td { + padding: 0 0 6px + } + } + + a:focus, a:active { + box-shadow: none; + outline: none; + } +} + +%card-group { + display: flex; + align-items: center; + justify-content: space-between; + min-height: 25px; + box-sizing: border-box; + flex-wrap: nowrap; + gap: 10px; + margin-bottom: 1em; + + & > img { + width: 54px; + height: 54px; + border-radius: 6px; + -o-object-fit: cover; + object-fit: cover; + } + + & > .icon { + width: 54px; + height: 54px; + font-size: 50px; + line-height: 54px; + border-radius: 6px; + -o-object-fit: cover; + object-fit: cover; + background-color: #f5f5f5; + display: flex; + align-items: center; + justify-content: center; + color: #777; + } + + & > div { + display: flex; + align-items: center; + gap: 10px; + } +} + +.pev-card { + @extend %card; + + &__title, h2, h3 { + padding: 0; + margin: 0; + color: inherit; + font-size: 14px; + font-weight: 600; + } + + &__subtitle { + font-size: 13px; + font-weight: 400; + color: #777; + } + + & > &__header, & > &__footer { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + + h2, h3, p { + margin: 0; + padding: 0; + } + + input, select, .select2, button { + margin-right: 5px; + + &:last-child { + margin-right: 0; + } + } + } + + & > &__body { + @extend %card-body; + } + + &__group { + @extend %card-group; + } + + & > [class*=pev-card]:not([class*=wide]) { + padding: 8px 10px; + } + + & > [class*=pev-card__] { + // child expect last item. + &:not(:last-child) { + border-bottom: 1px solid #e1e2e2; + } + } +} + +.pev-panel { + @extend %card; + @extend %card-body; + padding: 8px 10px; + + &__title { + font-family: HelveticaNeue-Light, "Helvetica Neue Light", "Helvetica Neue", sans-serif; + font-size: 18px; + font-weight: 400; + line-height: 1.2; + padding: 0; + margin: 0; + } + + &__subtitle { + font-family: HelveticaNeue-Light, "Helvetica Neue Light", "Helvetica Neue", sans-serif; + font-weight: 400; + line-height: 1.6em; + font-size: 13px; + margin: 0; + color: #777; + } + + &__group { + @extend %card-group; + } + + &__legend { + position: absolute; + right: -1px; + top: -1px; + color: #aaa; + font-size: 11px; + line-height: 1em; + background-color: transparent; + border-color: transparent transparent #e5e5e5 #e5e5e5; + border-style: solid; + border-width: 1px; + border-bottom-left-radius: 8px; + padding: 3px 2px 4px 7px; + z-index: 11; + + .promo-panel & { + color: #ffffff; + border-color: transparent transparent currentColor currentColor; + } + } + + &:has(&__legend) { + padding-top: 20px; + } + + &.promo-panel { + border-color: #482ac9; + background-color: #482ac9; + color: #ffffff; + + a:not(.button) { + color: #ffffff; + } + + .button { + display: block; + text-align: center; + margin-top: 20px; + text-transform: uppercase; + font-weight: bold; + color: #FFFFFF; + } + } +} diff --git a/assets/src/common/_form.scss b/assets/src/common/_form.scss new file mode 100644 index 00000000..6eb1d6eb --- /dev/null +++ b/assets/src/common/_form.scss @@ -0,0 +1,138 @@ +.pev-form-field { + width: 100%; + display: flex; + flex-direction: column; + position: relative; + min-width: 0; + vertical-align: top; + box-sizing: border-box; + min-height: 32px; + margin: 1em 0; + + label { + gap: 5px; + display: flex; + align-self: self-start; + justify-content: flex-start; + align-items: center; + margin: 0 0 3px; + + abbr[title="required"] { + color: #ff0000; + text-decoration: none; + + &:after { + content: "*"; + } + } + } + + p { + margin-bottom: 0; + } + + textarea { + resize: none; + } + + input:not([type="checkbox"]):not([type="radio"]):not(.addon), + select:not(.addon), + textarea:not(.addon){ + position: relative; + flex: 1 1 auto; + min-width: 0; + margin: 0; + max-width: unset; + + &:focus { + z-index: 3; + } + } + + &__group { + position: relative; + display: flex; + flex-wrap: nowrap; + align-items: stretch; + align-self: center; + width: 100%; + + .button, button { + position: relative; + z-index: 2; + display: flex; + align-items: center; + border-width: 1px; + margin: 0; + + &:focus { + z-index: 3; + } + } + + .addon { + display: flex; + align-items: center; + padding: 0 8px; + text-align: center; + white-space: nowrap; + background-color: #eee; + border: 1px solid #8c8f94; + border-radius: 4px; + user-select: none; + } + + & > :not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + + .select2-selection { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + } + + & > :nth-last-child(n+3) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + + & > :not(:first-child) { + margin-left: -1px !important; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + + .select2-selection { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + } + + // if the 2nd child is select2. + & > :nth-child(2) { + .select2-selection { + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + } + } + } + + @media screen and (min-width: 850px) { + .form-inline & { + display: grid; + grid-template-rows: 1fr; + grid-template-columns: 150px 1fr; + + & > label:first-child, .label { + width: 150px; + min-width: 150px; + grid-column: 1; + padding-right: 10px; + } + + & > *:not(:first-child) { + grid-column: 2; + } + } + } +} diff --git a/assets/src/common/_layout.scss b/assets/src/common/_layout.scss new file mode 100644 index 00000000..76b37610 --- /dev/null +++ b/assets/src/common/_layout.scss @@ -0,0 +1,34 @@ +.pev-navbar { + margin: 0 0 1em; + + + .subsubsub { + margin: -8px 0 0; + } +} + +.pev-poststuff{ + margin-top: 10px; + margin-right: 340px; + + .column-1{ + width: 100%; + min-width: 463px; + float: left; + } + + .column-2{ + float: right; + margin-right: -340px; + width: 320px; + } + + @media only screen and (max-width: 850px) { + margin-right: 0; + .column-1, .column-2{ + width: 100%; + min-width: 0; + float: none; + margin-right: 0; + } + } +} diff --git a/assets/src/css/common/_utilities.scss b/assets/src/css/common/_utilities.scss deleted file mode 100644 index 87bef53b..00000000 --- a/assets/src/css/common/_utilities.scss +++ /dev/null @@ -1,111 +0,0 @@ -$utilities: ( - // margin and padding - "margin": ( - responsive: true, - property: margin, - class: m, - values: map-merge($spacers, (auto: auto)) - ), - "margin-x": ( - responsive: true, - property: margin-right margin-left, - class: mx, - values: map-merge($spacers, (auto: auto)) - ), - "margin-y": ( - responsive: true, - property: margin-top margin-bottom, - class: my, - values: map-merge($spacers, (auto: auto)) - ), - "margin-top": ( - responsive: true, - property: margin-top, - class: mt, - values: map-merge($spacers, (auto: auto)) - ), - "margin-bottom": ( - responsive: true, - property: margin-bottom, - class: mb, - values: map-merge($spacers, (auto: auto)) - ), - "margin-start": ( - property: margin-left, - class: ms, - values: map-merge($spacers, (auto: auto)) - ), - // Padding utilities - "padding": ( - responsive: true, - property: padding, - class: p, - values: $spacers - ), - "padding-x": ( - responsive: true, - property: padding-right padding-left, - class: px, - values: $spacers - ), - "padding-y": ( - responsive: true, - property: padding-top padding-bottom, - class: py, - values: $spacers - ), - "padding-top": ( - responsive: true, - property: padding-top, - class: pt, - values: $spacers - ), - "padding-bottom": ( - responsive: true, - property: padding-bottom, - class: pb, - values: $spacers - ), - "border" : ( - property: border, - class: b, - values: ( - null: 1px solid #e1e2e2, - 0: 0, - ) - ), - "border-top" : ( - property: border-top, - class: bt, - values: ( - null: 1px solid #e1e2e2, - 0: 0, - ) - ), - "border-bottom" : ( - property: border-bottom, - class: bb, - values: ( - null: 1px solid #e1e2e2, - 0: 0, - ) - ), - "border-start" : ( - property: border-left, - class: bs, - values: ( - null: 1px solid #e1e2e2, - 0: 0, - ) - ), - "border-end" : ( - property: border-right, - class: be, - values: ( - null: 1px solid #e1e2e2, - 0: 0, - ) - ), -); - -@include generate-utilities($utilities); diff --git a/assets/src/js/frontend-script.js b/assets/src/frontend.js similarity index 100% rename from assets/src/js/frontend-script.js rename to assets/src/frontend.js diff --git a/assets/src/css/frontend-style.scss b/assets/src/frontend.scss similarity index 100% rename from assets/src/css/frontend-style.scss rename to assets/src/frontend.scss diff --git a/composer.json b/composer.json index e2837650..b2da782c 100644 --- a/composer.json +++ b/composer.json @@ -3,53 +3,52 @@ "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" + "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/"] + "WooCommerceSerialNumbers\\": "includes/" + } + }, + "autoload-dev": { + "psr-4": { + "WooCommerceSerialNumbers\\Tests\\": "tests/phpunit/" } }, "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": { @@ -58,8 +57,7 @@ "namespace_prefix": "WooCommerceSerialNumbers\\", "classmap_prefix": "WooCommerceSerialNumbers__", "packages": [ - "pluginever/framework-plugin", - "pluginever/framework-settings", + "byteever/plugin", "pluginever/framework-model" ] } 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/src/Admin/Admin.php b/includes/Admin/Admin.php similarity index 81% rename from src/Admin/Admin.php rename to includes/Admin/Admin.php index 880c90a5..32c25d92 100644 --- a/src/Admin/Admin.php +++ b/includes/Admin/Admin.php @@ -2,6 +2,8 @@ namespace WooCommerceSerialNumbers\Admin; +use WooCommerceSerialNumbers\B8\Component; + defined( 'ABSPATH' ) || exit; /** @@ -10,34 +12,46 @@ * @since 1.0.0 * @package WooCommerceSerialNumbers\Admin */ -class Admin { +class Admin extends Component { /** - * Admin constructor. + * Child components. * * @since 1.0.0 + * @var array */ - public function __construct() { - add_action( 'init', array( $this, 'init' ), 1 ); - 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' ) ); + public array $components = array( + Settings::class, + Menus::class, + Requests::class, + Orders::class, + Products::class, + Notices::class, + Premium::class, + ); + + /** + * Whether to load. + * + * @since 2.4.0 + * @return bool + */ + public function autoload(): bool { + return is_admin(); } /** - * Init. + * Register hooks. * * @since 1.0.0 + * @return void */ - public function init() { - WCSN()->services['admin/settings'] = Settings::instance(); - WCSN()->services['admin/menus'] = new Menus(); - WCSN()->services['admin/notices'] = new Notices(); - WCSN()->services['admin/requests'] = new Requests(); - WCSN()->services['admin/orders'] = new Orders(); - WCSN()->services['admin/products'] = new Products(); + public function register(): void { + 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' ) ); } /** @@ -60,8 +74,8 @@ public function enqueue_scripts( $hook ) { wp_enqueue_style( $which_select2_style ); wp_enqueue_script( 'jquery-ui-datepicker' ); - WCSN()->enqueue_style( 'wc-serial-numbers-admin', 'css/admin-style.css' ); - WCSN()->enqueue_script( 'wc-serial-numbers-admin', 'js/admin-script.js', array( 'jquery', 'jquery-ui-datepicker', $which_select2_script, 'wp-util' ) ); + $this->app->scripts->enqueue_style( 'wc-serial-numbers-admin', 'admin.css' ); + $this->app->scripts->enqueue_script( 'wc-serial-numbers-admin', 'admin.js', array( 'jquery', 'jquery-ui-datepicker', $which_select2_script, 'wp-util' ) ); wp_localize_script( 'wc-serial-numbers-admin', 'wc_serial_numbers_vars', @@ -85,7 +99,6 @@ public function enqueue_scripts( $hook ) { 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. @@ -107,12 +120,12 @@ public function screen_ids( $ids ) { * @return string */ public function admin_footer_text( $footer_text ) { - if ( WCSN()->get_review_url() && in_array( get_current_screen()->id, self::get_screen_ids(), true ) ) { + 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( WCSN()->get_name() ) . '', - 'here' + '' . esc_html( $this->app->get( 'name' ) ) . '', + 'here' ); } @@ -130,7 +143,7 @@ public function admin_footer_text( $footer_text ) { 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' ), WCSN()->get_version() ); + $footer_text = sprintf( esc_html__( 'Version %s', 'wc-serial-numbers' ), $this->app->version ); } return $footer_text; 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 87% rename from src/Admin/Menus.php rename to includes/Admin/Menus.php index f9d3455c..1cf8bd99 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,20 +14,19 @@ * @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 ); add_action( 'admin_menu', array( $this, 'tools_menu' ), 50 ); add_action( 'admin_menu', array( $this, 'reports_menu' ), 60 ); add_action( 'admin_menu', array( $this, 'settings_menu' ), 100 ); - add_action( 'admin_menu', array( $this, 'promo_menu' ), PHP_INT_MAX ); // Add tabs content. add_filter( 'wc_serial_numbers_tools_tabs', array( __CLASS__, 'add_tools_status_tab' ), PHP_INT_MAX ); @@ -171,29 +172,10 @@ 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' ) ) ); } - /** - * Add promo Menu. - * - * @since 1.0.0 - * @return void - */ - public function promo_menu() { - if ( ! WCSN()->is_premium_active() ) { - add_submenu_page( - 'wc-serial-numbers', - '', - ' ' . __( 'Upgrade to Pro', 'wc-serial-numbers' ) . '', - 'manage_woocommerce', // phpcs:ignore WordPress.WP.Capabilities.Unknown - 'go_wcsn_pro', - array( $this, 'go_pro_redirect' ) - ); - } - } - /** * Output keys page. * @@ -295,20 +277,6 @@ public function output_reports_page() { ); } - /** - * Redirect to pro page. - * - * @since 1.0.0 - * @return void - */ - public function go_pro_redirect() { - wp_verify_nonce( '_nonce' ); - if ( isset( $_GET['page'] ) && 'go_wcsn_pro' === $_GET['page'] ) { - wp_safe_redirect( 'https://pluginever.com/plugins/woocommerce-serial-numbers-pro/?utm_source=admin-menu&utm_medium=link&utm_campaign=upgrade&utm_id=wc-serial-numbers' ); - exit; - } - } - /** * Add status tab. * @@ -335,14 +303,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/Notices.php b/includes/Admin/Notices.php new file mode 100644 index 00000000..e7c5f19b --- /dev/null +++ b/includes/Admin/Notices.php @@ -0,0 +1,54 @@ +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' + ), + ) + ); + } + } +} 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/Premium.php b/includes/Admin/Premium.php new file mode 100644 index 00000000..6ba13dea --- /dev/null +++ b/includes/Admin/Premium.php @@ -0,0 +1,162 @@ +app->is_pro_active(); + } + + /** + * Register hooks. + * + * @since 2.4.0 + * @return void + */ + public function register(): void { + add_action( 'admin_init', array( $this, 'register_notices' ) ); + add_action( 'admin_menu', array( $this, 'promo_menu' ), PHP_INT_MAX ); + add_filter( 'plugin_action_links_' . $this->app->basename(), array( $this, 'go_pro_link' ) ); + $this->app->on_action( 'settings_sidebar', array( $this, 'render_sidebar' ) ); + } + + /** + * Register the premium notices. + * + * @since 2.4.0 + * @return void + */ + public function register_notices(): void { + $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' + ), + ) + ); + } + + /** + * Register the promotional menu. + * + * @since 2.4.0 + * @return void + */ + public function promo_menu(): void { + add_submenu_page( + 'wc-serial-numbers', + '', + ' ' . __( 'Upgrade to Pro', 'wc-serial-numbers' ) . '', + 'manage_woocommerce', // phpcs:ignore WordPress.WP.Capabilities.Unknown + 'go_wcsn_pro', + array( $this, 'go_pro_redirect' ) + ); + } + + /** + * Redirect to the pro version page. + * + * @since 2.4.0 + * @return void + */ + public function go_pro_redirect(): void { + wp_verify_nonce( '_nonce' ); + if ( isset( $_GET['page'] ) && 'go_wcsn_pro' === $_GET['page'] ) { + wp_safe_redirect( 'https://pluginever.com/plugins/woocommerce-serial-numbers-pro/?utm_source=admin-menu&utm_medium=link&utm_campaign=upgrade&utm_id=wc-serial-numbers' ); + exit; + } + } + + /** + * Add the Go Pro action link. + * + * @since 2.4.0 + * @param array $links Plugin action links. + * @return array + */ + public function go_pro_link( array $links ): array { + $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' ) + ); + + $go_pro = sprintf( + '%2$s', + esc_url( $pro_link ), + esc_html__( 'Go Pro', 'wc-serial-numbers' ) + ); + + return array_merge( array( 'go_pro' => $go_pro ), $links ); + } + + /** + * Render the settings sidebar panel. + * + * @since 2.4.0 + * @return void + */ + public function render_sidebar(): void { + $features = array( + __( 'Create and assign keys for WooCommerce variable products.', 'wc-serial-numbers' ), + __( 'Generate bulk keys with your custom key generator rule.', 'wc-serial-numbers' ), + __( 'Random & sequential key order for the generator rules.', 'wc-serial-numbers' ), + __( 'Automatic key generator to auto-create & assign keys with orders.', 'wc-serial-numbers' ), + __( 'License key management option from the order page with required actions.', 'wc-serial-numbers' ), + __( 'Support for bulk import/export of keys from/to CSV.', 'wc-serial-numbers' ), + __( 'Send keys via SMS with Twilio.', 'wc-serial-numbers' ), + __( 'Option to sell keys even if there are no available keys in the stock.', 'wc-serial-numbers' ), + __( 'Custom deliverable quantity to deliver multiple keys with a single product.', 'wc-serial-numbers' ), + __( 'Manual delivery option to manually deliver license keys instead of automatic.', 'wc-serial-numbers' ), + __( 'Email template to easily and quickly customize the order confirmation & low stock alert email.', 'wc-serial-numbers' ), + __( 'Many more ...', 'wc-serial-numbers' ), + ); + ?> +
+

+

+
    + +
  • -
  • + +
+ +
+