From 1eacdf5bf38fcb89c63f18d4541b5ac0c062350d Mon Sep 17 00:00:00 2001 From: "Md. Asif Hossain Nadim" <90011088+MdAsifHossainNadim@users.noreply.github.com> Date: Thu, 29 Feb 2024 12:58:24 +0600 Subject: [PATCH] fix/deprecated error for appsero updater (#194) * fix: deprecated-error-for-appsero-updater * enhance: load-appsero-from-wedocs * release: bump-version-&-updated-changelog --- composer.json | 1 - composer.lock | 110 +-- includes/Admin.php | 2 +- includes/Appsero/Client.php | 250 +++++++ includes/Appsero/Insights.php | 1299 +++++++++++++++++++++++++++++++++ includes/Appsero/License.php | 809 ++++++++++++++++++++ languages/wedocs.pot | 2 +- package.json | 2 +- readme.md | 2 +- readme.txt | 6 +- tests/bootstrap.php | 2 +- wedocs.php | 4 +- 12 files changed, 2398 insertions(+), 91 deletions(-) create mode 100644 includes/Appsero/Client.php create mode 100644 includes/Appsero/Insights.php create mode 100644 includes/Appsero/License.php diff --git a/composer.json b/composer.json index 500a1fe..8d2f035 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,6 @@ ], "minimum-stability": "dev", "require": { - "appsero/client": "^2.0.2", "woocommerce/action-scheduler": "dev-trunk" }, "autoload": { diff --git a/composer.lock b/composer.lock index 1386d06..1937c00 100644 --- a/composer.lock +++ b/composer.lock @@ -4,74 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8424029e90e55c86bf1bed76bb2931dd", + "content-hash": "116350e1527c41e68b0c663fd18e52db", "packages": [ - { - "name": "appsero/client", - "version": "v2.0.2", - "source": { - "type": "git", - "url": "https://github.com/Appsero/client.git", - "reference": "b61c3ab21df4d44f805ee9476f9d880f8370a36b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Appsero/client/zipball/b61c3ab21df4d44f805ee9476f9d880f8370a36b", - "reference": "b61c3ab21df4d44f805ee9476f9d880f8370a36b", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.2", - "phpcompatibility/phpcompatibility-wp": "dev-master", - "phpunit/phpunit": "^8.5.31", - "squizlabs/php_codesniffer": "^3.7", - "tareq1988/wp-php-cs-fixer": "dev-master", - "wp-coding-standards/wpcs": "dev-develop" - }, - "type": "library", - "autoload": { - "psr-4": { - "Appsero\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Tareq Hasan", - "email": "tareq@appsero.com" - } - ], - "description": "Appsero Client", - "keywords": [ - "analytics", - "plugin", - "theme", - "wordpress" - ], - "support": { - "issues": "https://github.com/Appsero/client/issues", - "source": "https://github.com/Appsero/client/tree/v2.0.2" - }, - "time": "2024-01-30T08:15:01+00:00" - }, { "name": "woocommerce/action-scheduler", "version": "dev-trunk", "source": { "type": "git", "url": "https://github.com/woocommerce/action-scheduler.git", - "reference": "2410f037681195150e08072827fefd846354fe05" + "reference": "65b0d7392accd4978c495e4eed4c0281a46eed4a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/woocommerce/action-scheduler/zipball/2410f037681195150e08072827fefd846354fe05", - "reference": "2410f037681195150e08072827fefd846354fe05", + "url": "https://api.github.com/repos/woocommerce/action-scheduler/zipball/65b0d7392accd4978c495e4eed4c0281a46eed4a", + "reference": "65b0d7392accd4978c495e4eed4c0281a46eed4a", "shasum": "" }, "require": { @@ -102,7 +48,7 @@ "issues": "https://github.com/woocommerce/action-scheduler/issues", "source": "https://github.com/woocommerce/action-scheduler/tree/trunk" }, - "time": "2023-12-14T14:01:47+00:00" + "time": "2024-02-22T13:47:42+00:00" } ], "packages-dev": [ @@ -230,12 +176,12 @@ "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "202aaf6b7c2e1e0a622b0298e9f3f537e4d84018" + "reference": "2f5294676c802a62b0549f6bc8983f14294ce369" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/202aaf6b7c2e1e0a622b0298e9f3f537e4d84018", - "reference": "202aaf6b7c2e1e0a622b0298e9f3f537e4d84018", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/2f5294676c802a62b0549f6bc8983f14294ce369", + "reference": "2f5294676c802a62b0549f6bc8983f14294ce369", "shasum": "" }, "require": { @@ -283,7 +229,7 @@ "type": "tidelift" } ], - "time": "2023-11-01T08:01:43+00:00" + "time": "2024-02-10T11:10:03+00:00" }, { "name": "phar-io/manifest", @@ -518,17 +464,17 @@ "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "bc3dc91a5e9b14aa06d1d9e90647c5c5a2cc5353" + "reference": "153ae662783729388a584b4361f2545e4d841e3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/bc3dc91a5e9b14aa06d1d9e90647c5c5a2cc5353", - "reference": "bc3dc91a5e9b14aa06d1d9e90647c5c5a2cc5353", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c", + "reference": "153ae662783729388a584b4361f2545e4d841e3c", "shasum": "" }, "require": { "doctrine/deprecations": "^1.0", - "php": "^7.4 || ^8.0", + "php": "^7.3 || ^8.0", "phpdocumentor/reflection-common": "^2.0", "phpstan/phpdoc-parser": "^1.13" }, @@ -567,9 +513,9 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.x" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.2" }, - "time": "2024-01-18T19:15:27+00:00" + "time": "2024-02-23T11:10:43+00:00" }, { "name": "phpspec/prophecy", @@ -577,20 +523,20 @@ "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "d4f454f7e1193933f04e6500de3e79191648ed0c" + "reference": "016d7770cf4ca93ab8fcc30511f31003b1f8bdcd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/d4f454f7e1193933f04e6500de3e79191648ed0c", - "reference": "d4f454f7e1193933f04e6500de3e79191648ed0c", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/016d7770cf4ca93ab8fcc30511f31003b1f8bdcd", + "reference": "016d7770cf4ca93ab8fcc30511f31003b1f8bdcd", "shasum": "" }, "require": { "doctrine/instantiator": "^1.2 || ^2.0", "php": "^7.2 || 8.0.* || 8.1.* || 8.2.* || 8.3.*", "phpdocumentor/reflection-docblock": "^5.2", - "sebastian/comparator": "^3.0 || ^4.0 || ^5.0", - "sebastian/recursion-context": "^3.0 || ^4.0 || ^5.0" + "sebastian/comparator": "^3.0 || ^4.0 || ^5.0 || ^6.0", + "sebastian/recursion-context": "^3.0 || ^4.0 || ^5.0 || ^6.0" }, "require-dev": { "phpspec/phpspec": "^6.0 || ^7.0", @@ -637,22 +583,22 @@ ], "support": { "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/v1.18.0" + "source": "https://github.com/phpspec/prophecy/tree/master" }, - "time": "2023-12-07T16:22:33+00:00" + "time": "2024-02-06T10:23:13+00:00" }, { "name": "phpstan/phpdoc-parser", - "version": "1.25.0", + "version": "1.26.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "bd84b629c8de41aa2ae82c067c955e06f1b00240" + "reference": "231e3186624c03d7e7c890ec662b81e6b0405227" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/bd84b629c8de41aa2ae82c067c955e06f1b00240", - "reference": "bd84b629c8de41aa2ae82c067c955e06f1b00240", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/231e3186624c03d7e7c890ec662b81e6b0405227", + "reference": "231e3186624c03d7e7c890ec662b81e6b0405227", "shasum": "" }, "require": { @@ -684,9 +630,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.25.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.26.0" }, - "time": "2024-01-04T17:06:16+00:00" + "time": "2024-02-23T16:05:55+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/includes/Admin.php b/includes/Admin.php index e110dfe..8dd0d37 100644 --- a/includes/Admin.php +++ b/includes/Admin.php @@ -2,7 +2,7 @@ namespace WeDevs\WeDocs; -use Appsero\Client as Appsero_Client; +use \WeDevs\WeDocs\Appsero\Client as Appsero_Client; /** * Frontend Handler Class diff --git a/includes/Appsero/Client.php b/includes/Appsero/Client.php new file mode 100644 index 0000000..a020e8f --- /dev/null +++ b/includes/Appsero/Client.php @@ -0,0 +1,250 @@ +hash = $hash; + $this->name = $name; + $this->file = $file; + + $this->set_basename_and_slug(); + } + + /** + * Initialize insights class + * + * @return Appsero\Insights + */ + public function insights() { + // if already instantiated, return the cached one + if ( $this->insights ) { + return $this->insights; + } + + $this->insights = new Insights( $this ); + + return $this->insights; + } + + /** + * Initialize license checker + * + * @return Appsero\License + */ + public function license() { + // if already instantiated, return the cached one + if ( $this->license ) { + return $this->license; + } + + $this->license = new License( $this ); + + return $this->license; + } + + /** + * API Endpoint + * + * @return string + */ + public function endpoint() { + $endpoint = apply_filters( 'appsero_endpoint', 'https://api.appsero.com' ); + + return trailingslashit( $endpoint ); + } + + /** + * Set project basename, slug and version + * + * @return void + */ + protected function set_basename_and_slug() { + if ( strpos( $this->file, WP_CONTENT_DIR . '/themes/' ) === false ) { + $this->basename = plugin_basename( $this->file ); + + list( $this->slug, $mainfile ) = explode( '/', $this->basename ); + + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + + $plugin_data = get_plugin_data( $this->file ); + + $this->project_version = $plugin_data['Version']; + $this->type = 'plugin'; + } else { + $this->basename = str_replace( WP_CONTENT_DIR . '/themes/', '', $this->file ); + + list( $this->slug, $mainfile ) = explode( '/', $this->basename ); + + $theme = wp_get_theme( $this->slug ); + + $this->project_version = $theme->version; + $this->type = 'theme'; + } + + $this->textdomain = $this->slug; + } + + /** + * Send request to remote endpoint + * + * @param array $params + * @param string $route + * + * @return array|WP_Error array of results including HTTP headers or WP_Error if the request failed + */ + public function send_request( $params, $route, $blocking = false ) { + $url = $this->endpoint() . $route; + + $headers = [ + 'user-agent' => 'Appsero/' . md5( esc_url( home_url() ) ) . ';', + 'Accept' => 'application/json', + ]; + + $response = wp_remote_post( + $url, + [ + 'method' => 'POST', + 'timeout' => 30, + 'redirection' => 5, + 'httpversion' => '1.0', + 'blocking' => $blocking, + 'headers' => $headers, + 'body' => array_merge( $params, [ 'client' => $this->version ] ), + 'cookies' => [], + ] + ); + + return $response; + } + + /** + * Check if the current server is localhost + * + * @return bool + */ + public function is_local_server() { + $is_local = isset( $_SERVER['REMOTE_ADDR'] ) && in_array( $_SERVER['REMOTE_ADDR'], [ '127.0.0.1', '::1' ], true ); + + return apply_filters( 'appsero_is_local', $is_local ); + } + + /** + * Translate function _e() + */ + // phpcs:ignore + public function _etrans( $text ) { + call_user_func( '_e', $text, $this->textdomain ); + } + + /** + * Translate function __() + */ + // phpcs:ignore + public function __trans( $text ) { + return call_user_func( '__', $text, $this->textdomain ); + } + + /** + * Set project textdomain + */ + public function set_textdomain( $textdomain ) { + $this->textdomain = $textdomain; + } +} diff --git a/includes/Appsero/Insights.php b/includes/Appsero/Insights.php new file mode 100644 index 0000000..a3ecec3 --- /dev/null +++ b/includes/Appsero/Insights.php @@ -0,0 +1,1299 @@ +client = $client; + } + } + + /** + * Don't show the notice + * + * @return \self + */ + public function hide_notice() + { + $this->show_notice = false; + + return $this; + } + + /** + * Add plugin data if needed + * + * @return \self + */ + public function add_plugin_data() + { + $this->plugin_data = true; + + return $this; + } + + /** + * Add extra data if needed + * + * @param array $data + * + * @return \self + */ + public function add_extra($data = []) + { + $this->extra_data = $data; + + return $this; + } + + /** + * Set custom notice text + * + * @param string $text + * + * @return \self + */ + public function notice($text = '') + { + $this->notice = $text; + + return $this; + } + + /** + * Initialize insights + * + * @return void + */ + public function init() + { + if ($this->client->type === 'plugin') { + $this->init_plugin(); + } elseif ($this->client->type === 'theme') { + $this->init_theme(); + } + } + + /** + * Initialize theme hooks + * + * @return void + */ + public function init_theme() + { + $this->init_common(); + + add_action('switch_theme', [$this, 'deactivation_cleanup']); + add_action('switch_theme', [$this, 'theme_deactivated'], 12, 3); + } + + /** + * Initialize plugin hooks + * + * @return void + */ + public function init_plugin() + { + // plugin deactivate popup + // if ( ! $this->is_local_server() ) { + // add_filter( 'plugin_action_links_' . $this->client->basename, [ $this, 'plugin_action_links' ] ); + // add_action( 'admin_footer', [ $this, 'deactivate_scripts' ] ); + // } + + add_filter('plugin_action_links_' . $this->client->basename, [$this, 'plugin_action_links']); + add_action('admin_footer', [$this, 'deactivate_scripts']); + + $this->init_common(); + + register_activation_hook($this->client->file, [$this, 'activate_plugin']); + register_deactivation_hook($this->client->file, [$this, 'deactivation_cleanup']); + } + + /** + * Initialize common hooks + * + * @return void + */ + protected function init_common() + { + if ($this->show_notice) { + // tracking notice + add_action('admin_notices', [$this, 'admin_notice']); + } + + add_action('admin_init', [$this, 'handle_optin_optout']); + + // uninstall reason + add_action('wp_ajax_' . $this->client->slug . '_submit-uninstall-reason', [$this, 'uninstall_reason_submission']); + + // cron events + add_filter('cron_schedules', [$this, 'add_weekly_schedule']); + add_action($this->client->slug . '_tracker_send_event', [$this, 'send_tracking_data']); + // add_action( 'admin_init', array( $this, 'send_tracking_data' ) ); // test + } + + /** + * Send tracking data to AppSero server + * + * @param bool $override + * + * @return void + */ + public function send_tracking_data($override = false) + { + if (!$this->tracking_allowed() && !$override) { + return; + } + + // Send a maximum of once per week + $last_send = $this->get_last_send(); + + if ($last_send && $last_send > strtotime('-1 week')) { + return; + } + + $tracking_data = $this->get_tracking_data(); + + $response = $this->client->send_request($tracking_data, 'track'); + + update_option($this->client->slug . '_tracking_last_send', time()); + } + + /** + * Get the tracking data points + * + * @return array + */ + protected function get_tracking_data() + { + $all_plugins = $this->get_all_plugins(); + + $users = get_users( + [ + 'role' => 'administrator', + 'orderby' => 'ID', + 'order' => 'ASC', + 'number' => 1, + 'paged' => 1, + ] + ); + + $admin_user = (is_array($users) && !empty($users)) ? $users[0] : false; + $first_name = ''; + $last_name = ''; + + if ($admin_user) { + $first_name = $admin_user->first_name ? $admin_user->first_name : $admin_user->display_name; + $last_name = $admin_user->last_name; + } + + $data = [ + 'url' => esc_url(home_url()), + 'site' => $this->get_site_name(), + 'admin_email' => get_option('admin_email'), + 'first_name' => $first_name, + 'last_name' => $last_name, + 'hash' => $this->client->hash, + 'server' => $this->get_server_info(), + 'wp' => $this->get_wp_info(), + 'users' => $this->get_user_counts(), + 'active_plugins' => count($all_plugins['active_plugins']), + 'inactive_plugins' => count($all_plugins['inactive_plugins']), + 'ip_address' => $this->get_user_ip_address(), + 'project_version' => $this->client->project_version, + 'tracking_skipped' => false, + 'is_local' => $this->is_local_server(), + ]; + + // Add Plugins + if ($this->plugin_data) { + $plugins_data = []; + + foreach ($all_plugins['active_plugins'] as $slug => $plugin) { + $slug = strstr($slug, '/', true); + + if (!$slug) { + continue; + } + + $plugins_data[$slug] = [ + 'name' => isset($plugin['name']) ? $plugin['name'] : '', + 'version' => isset($plugin['version']) ? $plugin['version'] : '', + ]; + } + + if (array_key_exists($this->client->slug, $plugins_data)) { + unset($plugins_data[$this->client->slug]); + } + + $data['plugins'] = $plugins_data; + } + + // Add Metadata + $extra = $this->get_extra_data(); + + if ($extra) { + $data['extra'] = $extra; + } + + // Check this has previously skipped tracking + $skipped = get_option($this->client->slug . '_tracking_skipped'); + + if ($skipped === 'yes') { + delete_option($this->client->slug . '_tracking_skipped'); + + $data['tracking_skipped'] = true; + } + + return apply_filters($this->client->slug . '_tracker_data', $data); + } + + /** + * If a child class wants to send extra data + * + * @return mixed + */ + protected function get_extra_data() + { + if (is_callable($this->extra_data)) { + return call_user_func($this->extra_data); + } + + if (is_array($this->extra_data)) { + return $this->extra_data; + } + + return []; + } + + /** + * Explain the user which data we collect + * + * @return array + */ + protected function data_we_collect() + { + $data = [ + 'Server environment details (php, mysql, server, WordPress versions)', + 'Number of users in your site', + 'Site language', + 'Number of active and inactive plugins', + 'Site name and URL', + 'Your name and email address', + ]; + + if ($this->plugin_data) { + array_splice($data, 4, 0, ["active plugins' name"]); + } + + return $data; + } + + /** + * Check if the user has opted into tracking + * + * @return bool + */ + public function tracking_allowed() + { + $allow_tracking = get_option($this->client->slug . '_allow_tracking', 'no'); + + return $allow_tracking === 'yes'; + } + + /** + * Get the last time a tracking was sent + * + * @return false|string + */ + private function get_last_send() + { + return get_option($this->client->slug . '_tracking_last_send', false); + } + + /** + * Check if the notice has been dismissed or enabled + * + * @return bool + */ + public function notice_dismissed() + { + $hide_notice = get_option($this->client->slug . '_tracking_notice', null); + + if ('hide' === $hide_notice) { + return true; + } + + return false; + } + + /** + * Check if the current server is localhost + * + * @return bool + */ + private function is_local_server() + { + $host = isset($_SERVER['HTTP_HOST']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST'])) : 'localhost'; + $ip = isset($_SERVER['SERVER_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['SERVER_ADDR'])) : '127.0.0.1'; + $is_local = false; + + if ( + in_array($ip, ['127.0.0.1', '::1'], true) + || !strpos($host, '.') + || in_array(strrchr($host, '.'), ['.test', '.testing', '.local', '.localhost', '.localdomain'], true) + ) { + $is_local = true; + } + + return apply_filters('appsero_is_local', $is_local); + } + + /** + * Schedule the event weekly + * + * @return void + */ + private function schedule_event() + { + $hook_name = wp_unslash($this->client->slug . '_tracker_send_event'); + + if (!wp_next_scheduled($hook_name)) { + wp_schedule_event(time(), 'weekly', $hook_name); + } + } + + /** + * Clear any scheduled hook + * + * @return void + */ + private function clear_schedule_event() + { + wp_clear_scheduled_hook($this->client->slug . '_tracker_send_event'); + } + + /** + * Display the admin notice to users that have not opted-in or out + * + * @return void + */ + public function admin_notice() + { + if ($this->notice_dismissed()) { + return; + } + + if ($this->tracking_allowed()) { + return; + } + + if (!current_user_can('manage_options')) { + return; + } + + // don't show tracking if a local server + // if ( $this->is_local_server() ) { + // return; + // } + + $optin_url = wp_nonce_url(add_query_arg($this->client->slug . '_tracker_optin', 'true'), '_wpnonce'); + $optout_url = wp_nonce_url(add_query_arg($this->client->slug . '_tracker_optout', 'true'), '_wpnonce'); + + if (empty($this->notice)) { + $notice = sprintf($this->client->__trans('Want to help make %1$s even more awesome? Allow %1$s to collect diagnostic data and usage information.'), $this->client->name); + } else { + $notice = $this->notice; + } + + $policy_url = 'https://appsero.com/privacy-policy/'; + + $notice .= ' (' . $this->client->__trans('what we collect') . ')'; + $notice .= ''; + + echo '

'; + echo $notice; + echo '

'; + echo ' ' . $this->client->__trans('Allow') . ''; + echo ' ' . $this->client->__trans('No thanks') . ''; + echo '

'; + + echo " + "; + } + + /** + * Handle the optin/optout + * + * @return void + */ + public function handle_optin_optout() + { + if (!isset($_GET['_wpnonce'])) { + return; + } + + if (!wp_verify_nonce(sanitize_key($_GET['_wpnonce']), '_wpnonce')) { + return; + } + + if (!current_user_can('manage_options')) { + return; + } + + if (isset($_GET[$this->client->slug . '_tracker_optin']) && $_GET[$this->client->slug . '_tracker_optin'] === 'true') { + $this->optin(); + + wp_safe_redirect(remove_query_arg($this->client->slug . '_tracker_optin')); + exit; + } + + if (isset($_GET[$this->client->slug . '_tracker_optout']) && isset($_GET[$this->client->slug . '_tracker_optout']) && $_GET[$this->client->slug . '_tracker_optout'] === 'true') { + $this->optout(); + + wp_safe_redirect(remove_query_arg($this->client->slug . '_tracker_optout')); + exit; + } + } + + /** + * Tracking optin + * + * @return void + */ + public function optin() + { + update_option($this->client->slug . '_allow_tracking', 'yes'); + update_option($this->client->slug . '_tracking_notice', 'hide'); + + $this->clear_schedule_event(); + $this->schedule_event(); + $this->send_tracking_data(); + + /* + * Fires when the user has opted in tracking. + */ + do_action($this->client->slug . '_tracker_optin', $this->get_tracking_data()); + } + + /** + * Optout from tracking + * + * @return void + */ + public function optout() + { + update_option($this->client->slug . '_allow_tracking', 'no'); + update_option($this->client->slug . '_tracking_notice', 'hide'); + + $this->send_tracking_skipped_request(); + + $this->clear_schedule_event(); + + /* + * Fires when the user has opted out tracking. + */ + do_action($this->client->slug . '_tracker_optout'); + } + + /** + * Get the number of post counts + * + * @param string $post_type + * + * @return int + */ + public function get_post_count($post_type) + { + global $wpdb; + + return (int) $wpdb->get_var( + $wpdb->prepare( + "SELECT count(ID) FROM $wpdb->posts WHERE post_type = %s and post_status = %s", + [$post_type, 'publish'] + ) + ); + } + + /** + * Get server related info. + * + * @return array + */ + private static function get_server_info() + { + global $wpdb; + + $server_data = []; + + if (isset($_SERVER['SERVER_SOFTWARE']) && !empty($_SERVER['SERVER_SOFTWARE'])) { + // phpcs:ignore + $server_data['software'] = $_SERVER['SERVER_SOFTWARE']; + } + + if (function_exists('phpversion')) { + $server_data['php_version'] = phpversion(); + } + + $server_data['mysql_version'] = $wpdb->db_version(); + + $server_data['php_max_upload_size'] = size_format(wp_max_upload_size()); + $server_data['php_default_timezone'] = date_default_timezone_get(); + $server_data['php_soap'] = class_exists('SoapClient') ? 'Yes' : 'No'; + $server_data['php_fsockopen'] = function_exists('fsockopen') ? 'Yes' : 'No'; + $server_data['php_curl'] = function_exists('curl_init') ? 'Yes' : 'No'; + + return $server_data; + } + + /** + * Get WordPress related data. + * + * @return array + */ + private function get_wp_info() + { + $wp_data = []; + + $wp_data['memory_limit'] = WP_MEMORY_LIMIT; + $wp_data['debug_mode'] = (defined('WP_DEBUG') && WP_DEBUG) ? 'Yes' : 'No'; + $wp_data['locale'] = get_locale(); + $wp_data['version'] = get_bloginfo('version'); + $wp_data['multisite'] = is_multisite() ? 'Yes' : 'No'; + $wp_data['theme_slug'] = get_stylesheet(); + + $theme = wp_get_theme($wp_data['theme_slug']); + + $wp_data['theme_name'] = $theme->get('Name'); + $wp_data['theme_version'] = $theme->get('Version'); + $wp_data['theme_uri'] = $theme->get('ThemeURI'); + $wp_data['theme_author'] = $theme->get('Author'); + + return $wp_data; + } + + /** + * Get the list of active and inactive plugins + * + * @return array + */ + private function get_all_plugins() + { + // Ensure get_plugins function is loaded + if (!function_exists('get_plugins')) { + include ABSPATH . '/wp-admin/includes/plugin.php'; + } + + $plugins = get_plugins(); + $active_plugins_keys = get_option('active_plugins', []); + $active_plugins = []; + + foreach ($plugins as $k => $v) { + // Take care of formatting the data how we want it. + $formatted = []; + $formatted['name'] = wp_strip_all_tags($v['Name']); + + if (isset($v['Version'])) { + $formatted['version'] = wp_strip_all_tags($v['Version']); + } + + if (isset($v['Author'])) { + $formatted['author'] = wp_strip_all_tags($v['Author']); + } + + if (isset($v['Network'])) { + $formatted['network'] = wp_strip_all_tags($v['Network']); + } + + if (isset($v['PluginURI'])) { + $formatted['plugin_uri'] = wp_strip_all_tags($v['PluginURI']); + } + + if (in_array($k, $active_plugins_keys, true)) { + // Remove active plugins from list so we can show active and inactive separately + unset($plugins[$k]); + $active_plugins[$k] = $formatted; + } else { + $plugins[$k] = $formatted; + } + } + + return [ + 'active_plugins' => $active_plugins, + 'inactive_plugins' => $plugins, + ]; + } + + /** + * Get user totals based on user role. + * + * @return array + */ + public function get_user_counts() + { + $user_count = []; + $user_count_data = count_users(); + $user_count['total'] = $user_count_data['total_users']; + + // Get user count based on user role + foreach ($user_count_data['avail_roles'] as $role => $count) { + if (!$count) { + continue; + } + + $user_count[$role] = $count; + } + + return $user_count; + } + + /** + * Add weekly cron schedule + * + * @param array $schedules + * + * @return array + */ + public function add_weekly_schedule($schedules) + { + $schedules['weekly'] = [ + 'interval' => DAY_IN_SECONDS * 7, + 'display' => 'Once Weekly', + ]; + + return $schedules; + } + + /** + * Plugin activation hook + * + * @return void + */ + public function activate_plugin() + { + $allowed = get_option($this->client->slug . '_allow_tracking', 'no'); + + // if it wasn't allowed before, do nothing + if ('yes' !== $allowed) { + return; + } + + // re-schedule and delete the last sent time so we could force send again + $hook_name = $this->client->slug . '_tracker_send_event'; + + if (!wp_next_scheduled($hook_name)) { + wp_schedule_event(time(), 'weekly', $hook_name); + } + + delete_option($this->client->slug . '_tracking_last_send'); + + $this->send_tracking_data(true); + } + + /** + * Clear our options upon deactivation + * + * @return void + */ + public function deactivation_cleanup() + { + $this->clear_schedule_event(); + + if ('theme' === $this->client->type) { + delete_option($this->client->slug . '_tracking_last_send'); + delete_option($this->client->slug . '_allow_tracking'); + } + + delete_option($this->client->slug . '_tracking_notice'); + } + + /** + * Hook into action links and modify the deactivate link + * + * @param array $links + * + * @return array + */ + public function plugin_action_links($links) + { + if (array_key_exists('deactivate', $links)) { + $links['deactivate'] = str_replace(' 'could-not-understand', + 'text' => $this->client->__trans("Couldn't understand"), + 'placeholder' => $this->client->__trans('Would you like us to assist you?'), + 'icon' => '', + ], + [ + 'id' => 'found-better-plugin', + 'text' => $this->client->__trans('Found a better plugin'), + 'placeholder' => $this->client->__trans('Which plugin?'), + 'icon' => '', + ], + [ + 'id' => 'not-have-that-feature', + 'text' => $this->client->__trans('Missing a specific feature'), + 'placeholder' => $this->client->__trans('Could you tell us more about that feature?'), + 'icon' => '', + ], + [ + 'id' => 'is-not-working', + 'text' => $this->client->__trans('Not working'), + 'placeholder' => $this->client->__trans('Could you tell us a bit more whats not working?'), + 'icon' => '', + ], + [ + 'id' => 'looking-for-other', + 'text' => $this->client->__trans('Not what I was looking'), + 'placeholder' => $this->client->__trans('Could you tell us a bit more?'), + 'icon' => '', + ], + [ + 'id' => 'did-not-work-as-expected', + 'text' => $this->client->__trans("Didn't work as expected"), + 'placeholder' => $this->client->__trans('What did you expect?'), + 'icon' => '', + ], + [ + 'id' => 'other', + 'text' => $this->client->__trans('Others'), + 'placeholder' => $this->client->__trans('Could you tell us a bit more?'), + 'icon' => '', + ], + ]; + + return $reasons; + } + + /** + * Plugin deactivation uninstall reason submission + * + * @return void + */ + public function uninstall_reason_submission() + { + if (!isset($_POST['nonce'])) { + return; + } + + if (!isset($_POST['reason_id'])) { + wp_send_json_error(); + } + + if (!wp_verify_nonce(sanitize_key(wp_unslash($_POST['nonce'])), 'appsero-security-nonce')) { + wp_send_json_error('Nonce verification failed'); + } + + if (!current_user_can('manage_options')) { + wp_send_json_error('You are not allowed for this task'); + } + + $data = $this->get_tracking_data(); + $data['reason_id'] = sanitize_text_field(wp_unslash($_POST['reason_id'])); + $data['reason_info'] = isset($_REQUEST['reason_info']) ? trim(sanitize_text_field(wp_unslash($_REQUEST['reason_info']))) : ''; + + $this->client->send_request($data, 'deactivate'); + + /* + * Fire after the plugin _uninstall_reason_submitted + */ + do_action($this->client->slug . '_uninstall_reason_submitted', $data); + + wp_send_json_success(); + } + + /** + * Handle the plugin deactivation feedback + * + * @return void + */ + public function deactivate_scripts() + { + global $pagenow; + + if ('plugins.php' !== $pagenow) { + return; + } + + $this->deactivation_modal_styles(); + $reasons = $this->get_uninstall_reasons(); + $custom_reasons = apply_filters('appsero_custom_deactivation_reasons', [], $this->client); +?> + +
+
+
+

client->_etrans('Goodbyes are always hard. If you have a moment, please let us know how we can improve.'); ?>

+
+ +
+
    + +
  • + +
  • + +
+ +
    + +
  • + +
  • + +
+ +
+

+ client->__trans('We share your data with Appsero to troubleshoot problems & make product improvements. Learn more about how Appsero handles your data.'), + esc_url('https://appsero.com/'), + esc_url('https://appsero.com/privacy-policy') + ); + ?> +

+
+ + +
+
+ + + + get_template() === $this->client->slug) { + $this->client->send_request($this->get_tracking_data(), 'deactivate'); + } + } + + /** + * Get user IP Address + */ + private function get_user_ip_address() + { + $response = wp_remote_get('https://icanhazip.com/'); + + if (is_wp_error($response)) { + return ''; + } + + $ip = trim(wp_remote_retrieve_body($response)); + + if (!filter_var($ip, FILTER_VALIDATE_IP)) { + return ''; + } + + return $ip; + } + + /** + * Get site name + */ + private function get_site_name() + { + $site_name = get_bloginfo('name'); + + if (empty($site_name)) { + $site_name = get_bloginfo('description'); + $site_name = wp_trim_words($site_name, 3, ''); + } + + if (empty($site_name)) { + $site_name = esc_url(home_url()); + } + + return $site_name; + } + + /** + * Send request to appsero if user skip to send tracking data + */ + private function send_tracking_skipped_request() + { + $skipped = get_option($this->client->slug . '_tracking_skipped'); + + $data = [ + 'hash' => $this->client->hash, + 'previously_skipped' => false, + ]; + + if ($skipped === 'yes') { + $data['previously_skipped'] = true; + } else { + update_option($this->client->slug . '_tracking_skipped', 'yes'); + } + + $this->client->send_request($data, 'tracking-skipped'); + } + + /** + * Deactivation modal styles + */ + private function deactivation_modal_styles() + { + ?> + +client = $client; + + $this->option_key = 'appsero_' . md5( $this->client->slug ) . '_manage_license'; + + $this->schedule_hook = $this->client->slug . '_license_check_event'; + + // Creating WP Ajax Endpoint to refresh license remotely + add_action( 'wp_ajax_appsero_refresh_license_' . $this->client->hash, [ $this, 'refresh_license_api' ] ); + + // Run hook to check license status daily + add_action( $this->schedule_hook, [ $this, 'check_license_status' ] ); + + // Active/Deactive corn schedule + $this->run_schedule(); + } + + /** + * Set the license option key. + * + * If someone wants to override the default generated key. + * + * @param string $key + * + * @since 1.3.0 + * + * @return License + */ + public function set_option_key( $key ) { + $this->option_key = $key; + + return $this; + } + + /** + * Get the license key + * + * @since 1.3.0 + * + * @return string|null + */ + public function get_license() { + return get_option( $this->option_key, null ); + } + + /** + * Check license + * + * @return array + */ + public function check( $license_key ) { + $route = 'public/license/' . $this->client->hash . '/check'; + + return $this->send_request( $license_key, $route ); + } + + /** + * Active a license + * + * @return array + */ + public function activate( $license_key ) { + $route = 'public/license/' . $this->client->hash . '/activate'; + + return $this->send_request( $license_key, $route ); + } + + /** + * Deactivate a license + * + * @return array + */ + public function deactivate( $license_key ) { + $route = 'public/license/' . $this->client->hash . '/deactivate'; + + return $this->send_request( $license_key, $route ); + } + + /** + * Send common request + * + * @return array + */ + protected function send_request( $license_key, $route ) { + $params = [ + 'license_key' => $license_key, + 'url' => esc_url( home_url() ), + 'is_local' => $this->client->is_local_server(), + ]; + + $response = $this->client->send_request( $params, $route, true ); + + if ( is_wp_error( $response ) ) { + return [ + 'success' => false, + 'error' => $response->get_error_message(), + ]; + } + + $response = json_decode( wp_remote_retrieve_body( $response ), true ); + + if ( empty( $response ) || isset( $response['exception'] ) ) { + return [ + 'success' => false, + 'error' => $this->client->__trans( 'Unknown error occurred, Please try again.' ), + ]; + } + + if ( isset( $response['errors'] ) && isset( $response['errors']['license_key'] ) ) { + $response = [ + 'success' => false, + 'error' => $response['errors']['license_key'][0], + ]; + } + + return $response; + } + + /** + * License Refresh Endpoint + */ + public function refresh_license_api() { + $this->check_license_status(); + + wp_send_json_success( + [ + 'message' => 'License refreshed successfully.', + ], + 200 + ); + } + + /** + * Add settings page for license + * + * @param array $args + * + * @return void + */ + public function add_settings_page( $args = [] ) { + $defaults = [ + 'type' => 'menu', // Can be: menu, options, submenu + 'page_title' => 'Manage License', + 'menu_title' => 'Manage License', + 'capability' => 'manage_options', + 'menu_slug' => $this->client->slug . '-manage-license', + 'icon_url' => '', + 'position' => null, + 'parent_slug' => '', + ]; + + $this->menu_args = wp_parse_args( $args, $defaults ); + + add_action( 'admin_menu', [ $this, 'admin_menu' ], 99 ); + } + + /** + * Admin Menu hook + * + * @return void + */ + public function admin_menu() { + switch ( $this->menu_args['type'] ) { + case 'menu': + $this->create_menu_page(); + break; + + case 'submenu': + $this->create_submenu_page(); + break; + + case 'options': + $this->create_options_page(); + break; + } + } + + /** + * License menu output + */ + public function menu_output() { + // process form data if submitted + if ( isset( $_POST['_nonce'] ) && wp_verify_nonce( sanitize_key( wp_unslash( $_POST['_nonce'] ) ), $this->client->name ) ) { + $form_data = [ + '_nonce' => sanitize_key( wp_unslash( $_POST['_nonce'] ) ), + '_action' => isset( $_POST['_action'] ) ? sanitize_text_field( wp_unslash( $_POST['_action'] ) ) : '', + 'license_key' => isset( $_POST['license_key'] ) ? sanitize_text_field( wp_unslash( $_POST['license_key'] ) ) : '', + ]; + $this->license_form_submit( $form_data ); + } + + $license = $this->get_license(); + $action = ( $license && isset( $license['status'] ) && 'activate' === $license['status'] ) ? 'deactive' : 'active'; + $this->licenses_style(); + ?> + +
+

License Settings

+ + show_license_page_notices(); + do_action( 'before_appsero_license_section' ); + ?> + +
+ show_license_page_card_header( $license ); ?> + +
+

+ client->__trans( 'Activate %s by your license key to get professional support and automatic update from your WordPress dashboard.' ), $this->client->name ); ?> +

+
+ + +
+
+ + + + + /> +
+ +
+
+ + show_active_license_info( $license ); + } + ?> +
+
+ + +
+ client->name ) ) { + $this->error = $this->client->__trans( 'Nonce vefification failed.' ); + + return; + } + + if ( ! current_user_can( 'manage_options' ) ) { + $this->error = $this->client->__trans( 'You don\'t have permission to manage license.' ); + + return; + } + + $license_key = ! empty( $form_data['license_key'] ) ? sanitize_text_field( wp_unslash( $form_data['license_key'] ) ) : ''; + $action = ! empty( $form_data['_action'] ) ? sanitize_text_field( wp_unslash( $form_data['_action'] ) ) : ''; + + switch ( $action ) { + case 'active': + $this->active_client_license( $license_key ); + break; + + case 'deactive': + $this->deactive_client_license(); + break; + + case 'refresh': + $this->refresh_client_license(); + break; + } + } + + /** + * Check license status on schedule + */ + public function check_license_status() { + $license = $this->get_license(); + + if ( isset( $license['key'] ) && ! empty( $license['key'] ) ) { + $response = $this->check( $license['key'] ); + + if ( isset( $response['success'] ) && $response['success'] ) { + $license['status'] = 'activate'; + $license['remaining'] = $response['remaining']; + $license['activation_limit'] = $response['activation_limit']; + $license['expiry_days'] = $response['expiry_days']; + $license['title'] = $response['title']; + $license['source_id'] = $response['source_identifier']; + $license['recurring'] = $response['recurring']; + } else { + $license['status'] = 'deactivate'; + $license['expiry_days'] = 0; + } + + update_option( $this->option_key, $license, false ); + } + } + + /** + * Check this is a valid license + */ + public function is_valid() { + if ( null !== $this->is_valid_license ) { + return $this->is_valid_license; + } + + $license = $this->get_license(); + + if ( ! empty( $license['key'] ) && isset( $license['status'] ) && $license['status'] === 'activate' ) { + $this->is_valid_license = true; + } else { + $this->is_valid_license = false; + } + + return $this->is_valid_license; + } + + /** + * Check this is a valid license + */ + public function is_valid_by( $option, $value ) { + $license = $this->get_license(); + + if ( ! empty( $license['key'] ) && isset( $license['status'] ) && $license['status'] === 'activate' ) { + if ( isset( $license[ $option ] ) && $license[ $option ] === $value ) { + return true; + } + } + + return false; + } + + /** + * Styles for licenses page + */ + private function licenses_style() { + ?> + + +
+
+

client->_etrans( 'Activations Remaining' ); ?>

+ +

client->_etrans( 'Unlimited' ); ?>

+ +

+ client->__trans( '%1$d out of %2$d' ), $license['remaining'], $license['activation_limit'] ); ?> +

+ +
+
+

client->_etrans( 'Expires in' ); ?>

+ 21 ? '' : 'occupied'; + echo '

' . $license['expiry_days'] . ' days

'; + } else { + echo '

' . $this->client->__trans( 'Never' ) . '

'; + } + ?> +
+
+ error ) ) { + ?> +
+

error; ?>

+
+ success ) ) { + ?> +
+

success; ?>

+
+ '; + } + + /** + * Card header + */ + private function show_license_page_card_header( $license ) { + ?> +
+ + + + + + client->__trans( 'Activate License' ); ?> + + +
+ + + +
+ + +
+ error = $this->client->__trans( 'The license key field is required.' ); + + return; + } + + $response = $this->activate( $license_key ); + + if ( ! $response['success'] ) { + $this->error = $response['error'] ? $response['error'] : $this->client->__trans( 'Unknown error occurred.' ); + + return; + } + + $data = [ + 'key' => $license_key, + 'status' => 'activate', + 'remaining' => $response['remaining'], + 'activation_limit' => $response['activation_limit'], + 'expiry_days' => $response['expiry_days'], + 'title' => $response['title'], + 'source_id' => $response['source_identifier'], + 'recurring' => $response['recurring'], + ]; + + update_option( $this->option_key, $data, false ); + + $this->success = $this->client->__trans( 'License activated successfully.' ); + } + + /** + * Deactive client license + */ + private function deactive_client_license() { + $license = $this->get_license(); + + if ( empty( $license['key'] ) ) { + $this->error = $this->client->__trans( 'License key not found.' ); + + return; + } + + $response = $this->deactivate( $license['key'] ); + + $data = [ + 'key' => '', + 'status' => 'deactivate', + ]; + + update_option( $this->option_key, $data, false ); + + if ( ! $response['success'] ) { + $this->error = $response['error'] ? $response['error'] : $this->client->__trans( 'Unknown error occurred.' ); + + return; + } + + $this->success = $this->client->__trans( 'License deactivated successfully.' ); + } + + /** + * Refresh Client License + */ + private function refresh_client_license() { + $license = $this->get_license(); + + if ( ! $license || ! isset( $license['key'] ) || empty( $license['key'] ) ) { + $this->error = $this->client->__trans( 'License key not found' ); + + return; + } + + $this->check_license_status(); + + $this->success = $this->client->__trans( 'License refreshed successfully.' ); + } + + /** + * Add license menu page + */ + private function create_menu_page() { + call_user_func( + 'add_menu_page', + $this->menu_args['page_title'], + $this->menu_args['menu_title'], + $this->menu_args['capability'], + $this->menu_args['menu_slug'], + [ $this, 'menu_output' ], + $this->menu_args['icon_url'], + $this->menu_args['position'] + ); + } + + /** + * Add submenu page + */ + private function create_submenu_page() { + call_user_func( + 'add_submenu_page', + $this->menu_args['parent_slug'], + $this->menu_args['page_title'], + $this->menu_args['menu_title'], + $this->menu_args['capability'], + $this->menu_args['menu_slug'], + [ $this, 'menu_output' ], + $this->menu_args['position'] + ); + } + + /** + * Add submenu page + */ + private function create_options_page() { + call_user_func( + 'add_options_page', + $this->menu_args['page_title'], + $this->menu_args['menu_title'], + $this->menu_args['capability'], + $this->menu_args['menu_slug'], + [ $this, 'menu_output' ], + $this->menu_args['position'] + ); + } + + /** + * Schedule daily sicense checker event + */ + public function schedule_cron_event() { + if ( ! wp_next_scheduled( $this->schedule_hook ) ) { + wp_schedule_event( time(), 'daily', $this->schedule_hook ); + + wp_schedule_single_event( time() + 20, $this->schedule_hook ); + } + } + + /** + * Clear any scheduled hook + */ + public function clear_scheduler() { + wp_clear_scheduled_hook( $this->schedule_hook ); + } + + /** + * Enable/Disable schedule + */ + private function run_schedule() { + switch ( $this->client->type ) { + case 'plugin': + register_activation_hook( $this->client->file, [ $this, 'schedule_cron_event' ] ); + register_deactivation_hook( $this->client->file, [ $this, 'clear_scheduler' ] ); + break; + + case 'theme': + add_action( 'after_switch_theme', [ $this, 'schedule_cron_event' ] ); + add_action( 'switch_theme', [ $this, 'clear_scheduler' ] ); + break; + } + } + + /** + * Get input license key + * + * @return $license + */ + private function get_input_license_value( $action, $license ) { + if ( 'active' === $action ) { + return isset( $license['key'] ) ? $license['key'] : ''; + } + + if ( 'deactive' === $action ) { + $key_length = strlen( $license['key'] ); + + return str_pad( + substr( $license['key'], 0, $key_length / 2 ), + $key_length, + '*' + ); + } + + return ''; + } +} diff --git a/languages/wedocs.pot b/languages/wedocs.pot index 8b6a2da..efcc382 100644 --- a/languages/wedocs.pot +++ b/languages/wedocs.pot @@ -9,7 +9,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"POT-Creation-Date: 2024-02-06T05:14:35+00:00\n" +"POT-Creation-Date: 2024-02-27T14:13:40+00:00\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "X-Generator: WP-CLI 2.8.1\n" "X-Domain: wedocs\n" diff --git a/package.json b/package.json index 97ef363..e4bb943 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "weDocs", - "version": "2.1.3", + "version": "2.1.4", "description": "A documentation plugin for WordPress", "author": "Tareq Hasan", "license": "GPL", diff --git a/readme.md b/readme.md index 1ddd3e7..7b2a43e 100644 --- a/readme.md +++ b/readme.md @@ -2,7 +2,7 @@ The documentation plugin for WordPress. Create great looking documentation for your products. -![WordPress Plugin Version](https://img.shields.io/badge/PLUGIN_VERSION-V2.1.3-blue) ![WordPress Plugin Active Installs](https://img.shields.io/badge/ACTIVE_INSTALLS-6K-green) ![WordPress Plugin Rating](https://img.shields.io/badge/RATING-4.8/5_(61)-green) ![WordPress Plugin: Tested WP Version](https://img.shields.io/badge/WORDPRESS-V6.4.2_TESTED-orange) ![WordPress Plugin Last Updated](https://img.shields.io/badge/LAST_UPDATED-FEBRUARY_2024-orange) +![WordPress Plugin Version](https://img.shields.io/badge/PLUGIN_VERSION-V2.1.4-blue) ![WordPress Plugin Active Installs](https://img.shields.io/badge/ACTIVE_INSTALLS-6K-green) ![WordPress Plugin Rating](https://img.shields.io/badge/RATING-4.8/5_(61)-green) ![WordPress Plugin: Tested WP Version](https://img.shields.io/badge/WORDPRESS-V6.4.2_TESTED-orange) ![WordPress Plugin Last Updated](https://img.shields.io/badge/LAST_UPDATED-FEBRUARY_2024-orange) ## Description ## diff --git a/readme.txt b/readme.txt index c515bbb..a2627dc 100644 --- a/readme.txt +++ b/readme.txt @@ -5,7 +5,7 @@ Tags: WordPress documentation plugin, WordPress docs plugin, Documentation, Know Requires at least: 5.6 Tested up to: 6.4.2 Requires PHP: 7.4 -Stable tag: 2.1.3 +Stable tag: 2.1.4 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html @@ -343,6 +343,10 @@ Can’t wait to join us? == Changelog == += v2.1.4 (29th February, 2024) = + + * **Fix:** Update appsero sdk, manage from wedocs & handle deprecation error. + = v2.1.3 (6th February, 2024) = * **Fix:** Updated Appsero Client SDK library to version 2.0.2 which will fix a security issue with the previous version of the library and a fatal error caused by the library. diff --git a/tests/bootstrap.php b/tests/bootstrap.php index a6567c7..cdac40a 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -12,7 +12,7 @@ $_tests_dir = getenv( 'WP_TESTS_DIR' ); if ( !$_tests_dir ) { - $_tests_dir = rtrim( sys_get_temp_dir(), '/\\' ) . '/wordpress-tests-lib'; + $_tests_dir = rtrim( sys_get_temp_dir(), '/\\' ) . '/wordpress-tests-Appsero'; } if ( !file_exists( $_tests_dir . '/includes/functions.php' ) ) { diff --git a/wedocs.php b/wedocs.php index b169ad2..8361d7c 100644 --- a/wedocs.php +++ b/wedocs.php @@ -3,7 +3,7 @@ Plugin Name: weDocs Plugin URI: https://wedocs.co/ Description: A documentation plugin for WordPress -Version: 2.1.3 +Version: 2.1.4 Author: weDevs Author URI: https://wedocs.co/?utm_source=wporg&utm_medium=banner&utm_campaign=author-uri License: GPL2 @@ -58,7 +58,7 @@ final class WeDocs { * * @var string */ - const VERSION = '2.1.3'; + const VERSION = '2.1.4'; /** * The plugin url.