From ba1f449081b3077a1418cfb50e45b89038745ea3 Mon Sep 17 00:00:00 2001 From: Yakir Sitbon Date: Wed, 15 Jan 2025 14:20:53 +0200 Subject: [PATCH] Internal: Create a Notifications package for plugins [ED-16360] (#1) --- .github/workflows/php-coding-standards.yml | 48 +++ .gitignore | 3 + composer.json | 23 ++ composer.lock | 409 +++++++++++++++++++++ phpcs.xml | 53 +++ plugin-example.php | 115 ++++++ src/v100/notifications.php | 221 +++++++++++ 7 files changed, 872 insertions(+) create mode 100644 .github/workflows/php-coding-standards.yml create mode 100644 .gitignore create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 phpcs.xml create mode 100644 plugin-example.php create mode 100644 src/v100/notifications.php diff --git a/.github/workflows/php-coding-standards.yml b/.github/workflows/php-coding-standards.yml new file mode 100644 index 0000000..47bc2e4 --- /dev/null +++ b/.github/workflows/php-coding-standards.yml @@ -0,0 +1,48 @@ +name: PHP Lint + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +concurrency: + cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.ref }} + +permissions: + contents: read + +jobs: + PHP-Code-Standards: + name: Lint PHP files + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, '[skip PHPCS]') || !contains(github.event.head_commit.message, '[skip CI]')" + steps: + - name: Check out source code + uses: actions/checkout@v4 + + - name: Set up PHP + uses: shivammathur/setup-php@a36e1e52ff4a1c9e9c9be31551ee4712a6cb6bd0 # 2.27.1 + with: + php-version: '7.4' + coverage: none + tools: composer, cs2pr, phpcs + env: + fail-fast: 'true' + + - name: Log debug information + run: | + export PATH=$HOME/.composer/vendor/bin:$PATH + php --version + phpcs -i + composer --version + + - name: Install dependencies + uses: ramsey/composer-install@83af392bf5f031813d25e6fe4cd626cdba9a2df6 # 2.2.0 + + - name: Run style check + run: | + export PATH=$HOME/.composer/vendor/bin:$PATH + composer run lint diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e6c2b3b --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +vendor/ +node_modules/ +.idea/ diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..22cd3ce --- /dev/null +++ b/composer.json @@ -0,0 +1,23 @@ +{ + "name": "elementor/wp-notifications-package", + "version": "1.0.0", + "require-dev": { + "squizlabs/php_codesniffer": "^3.10.2", + "dealerdirect/phpcodesniffer-composer-installer": "^v1.0.0", + "wp-coding-standards/wpcs": "^3.1.0" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "scripts": { + "lint": "vendor/bin/phpcs --standard=./phpcs.xml .", + "lint:fix": "vendor/bin/phpcbf ." + }, + "autoload": { + "psr-4": { + "Elementor\\WPNotificationsPackage\\": "src/" + } + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..8cc3708 --- /dev/null +++ b/composer.lock @@ -0,0 +1,409 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "284616c4c768b873fd9efc8b16c18d73", + "packages": [], + "packages-dev": [ + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/composer-installer.git", + "reference": "4be43904336affa5c2f70744a348312336afd0da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/4be43904336affa5c2f70744a348312336afd0da", + "reference": "4be43904336affa5c2f70744a348312336afd0da", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "ext-json": "*", + "ext-zip": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0", + "yoast/phpunit-polyfills": "^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/composer-installer/issues", + "source": "https://github.com/PHPCSStandards/composer-installer" + }, + "time": "2023-01-05T11:28:13+00:00" + }, + { + "name": "phpcsstandards/phpcsextra", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHPCSExtra.git", + "reference": "11d387c6642b6e4acaf0bd9bf5203b8cca1ec489" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/11d387c6642b6e4acaf0bd9bf5203b8cca1ec489", + "reference": "11d387c6642b6e4acaf0bd9bf5203b8cca1ec489", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "phpcsstandards/phpcsutils": "^1.0.9", + "squizlabs/php_codesniffer": "^3.8.0" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcsstandards/phpcsdevcs": "^1.1.6", + "phpcsstandards/phpcsdevtools": "^1.2.1", + "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-stable": "1.x-dev", + "dev-develop": "1.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHPCSExtra/graphs/contributors" + } + ], + "description": "A collection of sniffs and standards for use with PHP_CodeSniffer.", + "keywords": [ + "PHP_CodeSniffer", + "phpcbf", + "phpcodesniffer-standard", + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHPCSExtra/issues", + "security": "https://github.com/PHPCSStandards/PHPCSExtra/security/policy", + "source": "https://github.com/PHPCSStandards/PHPCSExtra" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2023-12-08T16:49:07+00:00" + }, + { + "name": "phpcsstandards/phpcsutils", + "version": "1.0.12", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", + "reference": "87b233b00daf83fb70f40c9a28692be017ea7c6c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/87b233b00daf83fb70f40c9a28692be017ea7c6c", + "reference": "87b233b00daf83fb70f40c9a28692be017ea7c6c", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^3.10.0 || 4.0.x-dev@dev" + }, + "require-dev": { + "ext-filter": "*", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcsstandards/phpcsdevcs": "^1.1.6", + "yoast/phpunit-polyfills": "^1.1.0 || ^2.0.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-stable": "1.x-dev", + "dev-develop": "1.x-dev" + } + }, + "autoload": { + "classmap": [ + "PHPCSUtils/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHPCSUtils/graphs/contributors" + } + ], + "description": "A suite of utility functions for use with PHP_CodeSniffer", + "homepage": "https://phpcsutils.com/", + "keywords": [ + "PHP_CodeSniffer", + "phpcbf", + "phpcodesniffer-standard", + "phpcs", + "phpcs3", + "standards", + "static analysis", + "tokens", + "utility" + ], + "support": { + "docs": "https://phpcsutils.com/", + "issues": "https://github.com/PHPCSStandards/PHPCSUtils/issues", + "security": "https://github.com/PHPCSStandards/PHPCSUtils/security/policy", + "source": "https://github.com/PHPCSStandards/PHPCSUtils" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2024-05-20T13:34:27+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.11.2", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "1368f4a58c3c52114b86b1abe8f4098869cb0079" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/1368f4a58c3c52114b86b1abe8f4098869cb0079", + "reference": "1368f4a58c3c52114b86b1abe8f4098869cb0079", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + }, + "bin": [ + "bin/phpcbf", + "bin/phpcs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2024-12-11T16:04:26+00:00" + }, + { + "name": "wp-coding-standards/wpcs", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/WordPress/WordPress-Coding-Standards.git", + "reference": "9333efcbff231f10dfd9c56bb7b65818b4733ca7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/9333efcbff231f10dfd9c56bb7b65818b4733ca7", + "reference": "9333efcbff231f10dfd9c56bb7b65818b4733ca7", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "ext-libxml": "*", + "ext-tokenizer": "*", + "ext-xmlreader": "*", + "php": ">=5.4", + "phpcsstandards/phpcsextra": "^1.2.1", + "phpcsstandards/phpcsutils": "^1.0.10", + "squizlabs/php_codesniffer": "^3.9.0" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^9.0", + "phpcsstandards/phpcsdevtools": "^1.2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + }, + "suggest": { + "ext-iconv": "For improved results", + "ext-mbstring": "For improved results" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Contributors", + "homepage": "https://github.com/WordPress/WordPress-Coding-Standards/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions", + "keywords": [ + "phpcs", + "standards", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/WordPress/WordPress-Coding-Standards/issues", + "source": "https://github.com/WordPress/WordPress-Coding-Standards", + "wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki" + }, + "funding": [ + { + "url": "https://opencollective.com/php_codesniffer", + "type": "custom" + } + ], + "time": "2024-03-25T16:39:00+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": {}, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..b9b8209 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,53 @@ + + + Elementor Coding Standards + + + + + + vendor/ + build/ + node_modules/ + tests/*.php + *.js + *.css + *.scss + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugin-example.php b/plugin-example.php new file mode 100644 index 0000000..0659cc3 --- /dev/null +++ b/plugin-example.php @@ -0,0 +1,115 @@ +init(); + } + + private function init() { + require __DIR__ . '/vendor/autoload.php'; + + $this->notifications = new Notifications( + 'wp-notifications-package', + '1.0.0', + 'wppe' + ); + + add_action( 'admin_notices', [ $this, 'display_notifications' ] ); + add_action( 'admin_footer', [ $this, 'display_dialog' ] ); + } + + public function display_notifications() { + $notifications = $this->notifications->get_notifications_by_conditions( true ); + + if ( empty( $notifications ) ) { + return; + } + + ?> +
+

+ +
+ +
+ +
+ notifications->get_notifications_by_conditions( true ); + + if ( empty( $notifications ) ) { + return; + } + + ?> + + +

+ + +
+ + + app_name = sanitize_title( $app_name ); + $this->app_version = $app_version; + $this->short_app_name = $short_app_name; + + $this->transient_key = "_{$this->app_name}_notifications"; + + add_action( 'admin_init', [ $this, 'refresh_notifications' ] ); + add_filter( 'body_class', [ $this, 'add_body_class' ] ); + } + + public function refresh_notifications(): void { + $this->get_notifications(); + } + + public function add_body_class( array $classes ): array { + $classes[] = $this->short_app_name . '-default'; + + return $classes; + } + + public function get_notifications_by_conditions( $force_request = false ) { + $notifications = $this->get_notifications( $force_request ); + + $filtered_notifications = []; + + foreach ( $notifications as $notification ) { + if ( empty( $notification['conditions'] ) ) { + $filtered_notifications = $this->add_to_array( $filtered_notifications, $notification ); + + continue; + } + + if ( ! $this->check_conditions( $notification['conditions'] ) ) { + continue; + } + + $filtered_notifications = $this->add_to_array( $filtered_notifications, $notification ); + } + + return $filtered_notifications; + } + + private function get_notifications( $force_update = false ): array { + $notifications = static::get_transient( $this->transient_key ); + + if ( false === $notifications || $force_update ) { + $notifications = $this->fetch_data(); + static::set_transient( $this->transient_key, $notifications ); + } + + return $notifications; + } + + private function add_to_array( $filtered_notifications, $notification ) { + foreach ( $filtered_notifications as $filtered_notification ) { + if ( $filtered_notification['id'] === $notification['id'] ) { + return $filtered_notifications; + } + } + + $filtered_notifications[] = $notification; + + return $filtered_notifications; + } + + private function check_conditions( $groups ): bool { + foreach ( $groups as $group ) { + if ( $this->check_group( $group ) ) { + return true; + } + } + + return false; + } + + private function check_group( $group ) { + $is_or_relation = ! empty( $group['relation'] ) && 'OR' === $group['relation']; + unset( $group['relation'] ); + $result = false; + + foreach ( $group as $condition ) { + // Reset results for each condition. + $result = false; + switch ( $condition['type'] ) { + case 'wordpress': // phpcs:ignore WordPress.WP.CapitalPDangit + // include an unmodified $wp_version + include ABSPATH . WPINC . '/version.php'; + $result = version_compare( $wp_version, $condition['version'], $condition['operator'] ); + break; + case 'multisite': + $result = is_multisite() === $condition['multisite']; + break; + case 'language': + $in_array = in_array( get_locale(), $condition['languages'], true ); + $result = 'in' === $condition['operator'] ? $in_array : ! $in_array; + break; + case 'plugin': + if ( ! function_exists( 'is_plugin_active' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + + $is_plugin_active = is_plugin_active( $condition['plugin'] ); + + if ( empty( $condition['operator'] ) ) { + $condition['operator'] = '=='; + } + + $result = '==' === $condition['operator'] ? $is_plugin_active : ! $is_plugin_active; + break; + case 'theme': + $theme = wp_get_theme(); + if ( wp_get_theme()->parent() ) { + $theme = wp_get_theme()->parent(); + } + + if ( $theme->get_template() === $condition['theme'] ) { + $version = $theme->version; + } else { + $version = ''; + } + + $result = version_compare( $version, $condition['version'], $condition['operator'] ); + break; + + default: + $result = apply_filters( "$this->app_name/notifications/condition/{$condition['type']}", $result, $condition ); + break; + } + + if ( ( $is_or_relation && $result ) || ( ! $is_or_relation && ! $result ) ) { + return $result; + } + } + + return $result; + } + + private function fetch_data(): array { + $response = wp_remote_get( + $this->api_endpoint, + [ + 'timeout' => 10, + 'body' => [ + 'api_version' => self::PACKAGE_VERSION, + 'app_name' => $this->app_name, + 'app_version' => $this->app_version, + 'site_lang' => get_bloginfo( 'language' ), + 'site_key' => $this->get_site_key(), + ], + ] + ); + + if ( is_wp_error( $response ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) { + return []; + } + + $data = \json_decode( wp_remote_retrieve_body( $response ), true ); + + if ( empty( $data['notifications'] ) || ! is_array( $data['notifications'] ) ) { + return []; + } + + return $data['notifications']; + } + + private function get_site_key() { + $site_key = get_option( 'elementor_connect_site_key' ); + + if ( ! $site_key ) { + $site_key = md5( uniqid( wp_generate_password() ) ); + update_option( 'elementor_connect_site_key', $site_key ); + } + + return $site_key; + } + + private static function get_transient( $cache_key ) { + $cache = get_option( $cache_key ); + + if ( empty( $cache['timeout'] ) ) { + return false; + } + + if ( time() > $cache['timeout'] ) { + return false; + } + + return json_decode( $cache['value'], true ); + } + + private static function set_transient( $cache_key, $value, $expiration = '+12 hours' ) { + $data = [ + 'timeout' => strtotime( $expiration, time() ), + 'value' => wp_json_encode( $value ), + ]; + + return update_option( $cache_key, $data, false ); + } +}