diff --git a/packages/nextgenthemes/wp-shared/composer.json b/packages/nextgenthemes/wp-shared/composer.json new file mode 100644 index 00000000..11c7a1c5 --- /dev/null +++ b/packages/nextgenthemes/wp-shared/composer.json @@ -0,0 +1,33 @@ +{ + "name": "nextgenthemes/wp-shared", + "type": "wp-package", + "license": "GPL-3.0", + "autoload": { + "psr-4": { + "WP\\": "includes/WP" + }, + "files": [ + "includes/WP/fn-asset-helpers.php", + "includes/WP/fn-compat.php", + "includes/WP/fn-deprecated.php", + "includes/WP/fn-array.php", + "includes/WP/fn-string.php", + "includes/WP/fn-misc.php", + "includes/WP/fn-settings.php", + "includes/WP/fn-license.php", + "includes/WP/fn-remote-get.php", + "includes/WP/Admin/load-admin-files.php" + ] + }, + "authors": [ + { + "name": "Nicolas Jonas" + } + ], + "require": { + "php": ">=7.4" + }, + "scripts": { + "update": "wget https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js -O ./includes/WP/Admin/alpine.js" + } +} diff --git a/packages/nextgenthemes/wp-shared/includes/WP/Admin/EDD/PluginUpdater.php b/packages/nextgenthemes/wp-shared/includes/WP/Admin/EDD/PluginUpdater.php new file mode 100755 index 00000000..914f9080 --- /dev/null +++ b/packages/nextgenthemes/wp-shared/includes/WP/Admin/EDD/PluginUpdater.php @@ -0,0 +1,644 @@ +api_url = trailingslashit( $_api_url ); + $this->api_data = $_api_data; + $this->plugin_file = $_plugin_file; + $this->name = plugin_basename( $_plugin_file ); + $this->slug = basename( $_plugin_file, '.php' ); + $this->version = $_api_data['version']; + $this->wp_override = isset( $_api_data['wp_override'] ) ? (bool) $_api_data['wp_override'] : false; + $this->beta = ! empty( $this->api_data['beta'] ) ? true : false; + $this->failed_request_cache_key = 'edd_sl_failed_http_' . md5( $this->api_url ); + + $edd_plugin_data[ $this->slug ] = $this->api_data; + + /** + * Fires after the $edd_plugin_data is setup. + * + * @since x.x.x + * + * @param array $edd_plugin_data Array of EDD SL plugin data. + */ + do_action( 'post_edd_sl_plugin_updater_setup', $edd_plugin_data ); + + // Set up hooks. + $this->init(); + + } + + /** + * Set up WordPress filters to hook into WP's update process. + * + * @uses add_filter() + * + * @return void + */ + public function init() { + + add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) ); + add_filter( 'plugins_api', array( $this, 'plugins_api_filter' ), 10, 3 ); + add_action( 'after_plugin_row', array( $this, 'show_update_notification' ), 10, 2 ); + add_action( 'admin_init', array( $this, 'show_changelog' ) ); + + } + + /** + * Check for Updates at the defined API endpoint and modify the update array. + * + * This function dives into the update API just when WordPress creates its update array, + * then adds a custom API call and injects the custom plugin data retrieved from the API. + * It is reassembled from parts of the native WordPress plugin update code. + * See wp-includes/update.php line 121 for the original wp_update_plugins() function. + * + * @uses api_request() + * + * @param array $_transient_data Update array build by WordPress. + * @return array Modified update array with custom plugin data. + */ + public function check_update( $_transient_data ) { + + global $pagenow; + + if ( ! is_object( $_transient_data ) ) { + $_transient_data = new \stdClass(); + } + + if ( ! empty( $_transient_data->response ) && ! empty( $_transient_data->response[ $this->name ] ) && false === $this->wp_override ) { + return $_transient_data; + } + + $current = $this->get_repo_api_data(); + if ( false !== $current && is_object( $current ) && isset( $current->new_version ) ) { + if ( version_compare( $this->version, $current->new_version, '<' ) ) { + $_transient_data->response[ $this->name ] = $current; + } else { + // Populating the no_update information is required to support auto-updates in WordPress 5.5. + $_transient_data->no_update[ $this->name ] = $current; + } + } + $_transient_data->last_checked = time(); + $_transient_data->checked[ $this->name ] = $this->version; + + return $_transient_data; + } + + /** + * Get repo API data from store. + * Save to cache. + * + * @return \stdClass + */ + public function get_repo_api_data() { + $version_info = $this->get_cached_version_info(); + + if ( false === $version_info ) { + $version_info = $this->api_request( + 'plugin_latest_version', + array( + 'slug' => $this->slug, + 'beta' => $this->beta, + ) + ); + if ( ! $version_info ) { + return false; + } + + // This is required for your plugin to support auto-updates in WordPress 5.5. + $version_info->plugin = $this->name; + $version_info->id = $this->name; + + $this->set_version_info_cache( $version_info ); + } + + return $version_info; + } + + /** + * Show the update notification on multisite subsites. + * + * @param string $file + * @param array $plugin + */ + public function show_update_notification( $file, $plugin ) { + + // Return early if in the network admin, or if this is not a multisite install. + if ( is_network_admin() || ! is_multisite() ) { + return; + } + + // Allow single site admins to see that an update is available. + if ( ! current_user_can( 'activate_plugins' ) ) { + return; + } + + if ( $this->name !== $file ) { + return; + } + + // Do not print any message if update does not exist. + $update_cache = get_site_transient( 'update_plugins' ); + + if ( ! isset( $update_cache->response[ $this->name ] ) ) { + if ( ! is_object( $update_cache ) ) { + $update_cache = new \stdClass(); + } + $update_cache->response[ $this->name ] = $this->get_repo_api_data(); + } + + // Return early if this plugin isn't in the transient->response or if the site is running the current or newer version of the plugin. + if ( empty( $update_cache->response[ $this->name ] ) || version_compare( $this->version, $update_cache->response[ $this->name ]->new_version, '>=' ) ) { + return; + } + + printf( + '', + $this->slug, + $file, + in_array( $this->name, $this->get_active_plugins(), true ) ? 'active' : 'inactive' + ); + + echo ''; + echo '

'; + + $changelog_link = ''; + if ( ! empty( $update_cache->response[ $this->name ]->sections->changelog ) ) { + $changelog_link = add_query_arg( + array( + 'edd_sl_action' => 'view_plugin_changelog', + 'plugin' => urlencode( $this->name ), + 'slug' => urlencode( $this->slug ), + 'TB_iframe' => 'true', + 'width' => 77, + 'height' => 911, + ), + self_admin_url( 'index.php' ) + ); + } + $update_link = add_query_arg( + array( + 'action' => 'upgrade-plugin', + 'plugin' => urlencode( $this->name ), + ), + self_admin_url( 'update.php' ) + ); + + printf( + /* translators: the plugin name. */ + esc_html__( 'There is a new version of %1$s available.', 'easy-digital-downloads' ), + esc_html( $plugin['Name'] ) + ); + + if ( ! current_user_can( 'update_plugins' ) ) { + echo ' '; + esc_html_e( 'Contact your network administrator to install the update.', 'easy-digital-downloads' ); + } elseif ( empty( $update_cache->response[ $this->name ]->package ) && ! empty( $changelog_link ) ) { + echo ' '; + printf( + /* translators: 1. opening anchor tag, do not translate 2. the new plugin version 3. closing anchor tag, do not translate. */ + __( '%1$sView version %2$s details%3$s.', 'easy-digital-downloads' ), + '', + esc_html( $update_cache->response[ $this->name ]->new_version ), + '' + ); + } elseif ( ! empty( $changelog_link ) ) { + echo ' '; + printf( + __( '%1$sView version %2$s details%3$s or %4$supdate now%5$s.', 'easy-digital-downloads' ), + '', + esc_html( $update_cache->response[ $this->name ]->new_version ), + '', + '', + '' + ); + } else { + printf( + ' %1$s%2$s%3$s', + '', + esc_html__( 'Update now.', 'easy-digital-downloads' ), + '' + ); + } + + do_action( "in_plugin_update_message-{$file}", $plugin, $plugin ); + + echo '

'; + } + + /** + * Gets the plugins active in a multisite network. + * + * @return array + */ + private function get_active_plugins() { + $active_plugins = (array) get_option( 'active_plugins' ); + $active_network_plugins = (array) get_site_option( 'active_sitewide_plugins' ); + + return array_merge( $active_plugins, array_keys( $active_network_plugins ) ); + } + + /** + * Updates information on the "View version x.x details" page with custom data. + * + * @uses api_request() + * + * @param mixed $_data + * @param string $_action + * @param object $_args + * @return object $_data + */ + public function plugins_api_filter( $_data, $_action = '', $_args = null ) { + + if ( 'plugin_information' !== $_action ) { + + return $_data; + + } + + if ( ! isset( $_args->slug ) || ( $_args->slug !== $this->slug ) ) { + + return $_data; + + } + + $to_send = array( + 'slug' => $this->slug, + 'is_ssl' => is_ssl(), + 'fields' => array( + 'banners' => array(), + 'reviews' => false, + 'icons' => array(), + ), + ); + + // Get the transient where we store the api request for this plugin for 24 hours + $edd_api_request_transient = $this->get_cached_version_info(); + + //If we have no transient-saved value, run the API, set a fresh transient with the API value, and return that value too right now. + if ( empty( $edd_api_request_transient ) ) { + + $api_response = $this->api_request( 'plugin_information', $to_send ); + + // Expires in 3 hours + $this->set_version_info_cache( $api_response ); + + if ( false !== $api_response ) { + $_data = $api_response; + } + } else { + $_data = $edd_api_request_transient; + } + + // Convert sections into an associative array, since we're getting an object, but Core expects an array. + if ( isset( $_data->sections ) && ! is_array( $_data->sections ) ) { + $_data->sections = $this->convert_object_to_array( $_data->sections ); + } + + // Convert banners into an associative array, since we're getting an object, but Core expects an array. + if ( isset( $_data->banners ) && ! is_array( $_data->banners ) ) { + $_data->banners = $this->convert_object_to_array( $_data->banners ); + } + + // Convert icons into an associative array, since we're getting an object, but Core expects an array. + if ( isset( $_data->icons ) && ! is_array( $_data->icons ) ) { + $_data->icons = $this->convert_object_to_array( $_data->icons ); + } + + // Convert contributors into an associative array, since we're getting an object, but Core expects an array. + if ( isset( $_data->contributors ) && ! is_array( $_data->contributors ) ) { + $_data->contributors = $this->convert_object_to_array( $_data->contributors ); + } + + if ( ! isset( $_data->plugin ) ) { + $_data->plugin = $this->name; + } + + return $_data; + } + + /** + * Convert some objects to arrays when injecting data into the update API + * + * Some data like sections, banners, and icons are expected to be an associative array, however due to the JSON + * decoding, they are objects. This method allows us to pass in the object and return an associative array. + * + * @since 3.6.5 + * + * @param stdClass $data + * + * @return array + */ + private function convert_object_to_array( $data ) { + if ( ! is_array( $data ) && ! is_object( $data ) ) { + return array(); + } + $new_data = array(); + foreach ( $data as $key => $value ) { + $new_data[ $key ] = is_object( $value ) ? $this->convert_object_to_array( $value ) : $value; + } + + return $new_data; + } + + /** + * Disable SSL verification in order to prevent download update failures + * + * @param array $args + * @param string $url + * @return object $array + */ + public function http_request_args( $args, $url ) { + + if ( strpos( $url, 'https://' ) !== false && strpos( $url, 'edd_action=package_download' ) ) { + $args['sslverify'] = $this->verify_ssl(); + } + return $args; + + } + + /** + * Calls the API and, if successfull, returns the object delivered by the API. + * + * @uses get_bloginfo() + * @uses wp_remote_post() + * @uses is_wp_error() + * + * @param string $_action The requested action. + * @param array $_data Parameters for the API action. + * @return false|object|void + */ + private function api_request( $_action, $_data ) { + $data = array_merge( $this->api_data, $_data ); + + if ( $data['slug'] !== $this->slug ) { + return; + } + + // Don't allow a plugin to ping itself + if ( trailingslashit( home_url() ) === $this->api_url ) { + return false; + } + + if ( $this->request_recently_failed() ) { + return false; + } + + return $this->get_version_from_remote(); + } + + /** + * Determines if a request has recently failed. + * + * @since 1.9.1 + * + * @return bool + */ + private function request_recently_failed() { + $failed_request_details = get_option( $this->failed_request_cache_key ); + + // Request has never failed. + if ( empty( $failed_request_details ) || ! is_numeric( $failed_request_details ) ) { + return false; + } + + /* + * Request previously failed, but the timeout has expired. + * This means we're allowed to try again. + */ + if ( time() > $failed_request_details ) { + delete_option( $this->failed_request_cache_key ); + + return false; + } + + return true; + } + + /** + * Logs a failed HTTP request for this API URL. + * We set a timestamp for 1 hour from now. This prevents future API requests from being + * made to this domain for 1 hour. Once the timestamp is in the past, API requests + * will be allowed again. This way if the site is down for some reason we don't bombard + * it with failed API requests. + * + * @see EDD_SL_Plugin_Updater::request_recently_failed + * + * @since 1.9.1 + */ + private function log_failed_request() { + update_option( $this->failed_request_cache_key, strtotime( '+1 hour' ) ); + } + + /** + * If available, show the changelog for sites in a multisite install. + */ + public function show_changelog() { + + if ( empty( $_REQUEST['edd_sl_action'] ) || 'view_plugin_changelog' !== $_REQUEST['edd_sl_action'] ) { + return; + } + + if ( empty( $_REQUEST['plugin'] ) ) { + return; + } + + if ( empty( $_REQUEST['slug'] ) || $this->slug !== $_REQUEST['slug'] ) { + return; + } + + if ( ! current_user_can( 'update_plugins' ) ) { + wp_die( esc_html__( 'You do not have permission to install plugin updates', 'easy-digital-downloads' ), esc_html__( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) ); + } + + $version_info = $this->get_repo_api_data(); + if ( isset( $version_info->sections ) ) { + $sections = $this->convert_object_to_array( $version_info->sections ); + if ( ! empty( $sections['changelog'] ) ) { + echo '
' . wp_kses_post( $sections['changelog'] ) . '
'; + } + } + + exit; + } + + /** + * Gets the current version information from the remote site. + * + * @return array|false + */ + private function get_version_from_remote() { + $api_params = array( + 'edd_action' => 'get_version', + 'license' => ! empty( $this->api_data['license'] ) ? $this->api_data['license'] : '', + 'item_name' => isset( $this->api_data['item_name'] ) ? $this->api_data['item_name'] : false, + 'item_id' => isset( $this->api_data['item_id'] ) ? $this->api_data['item_id'] : false, + 'version' => isset( $this->api_data['version'] ) ? $this->api_data['version'] : false, + 'slug' => $this->slug, + 'author' => $this->api_data['author'], + 'url' => home_url(), + 'beta' => $this->beta, + 'php_version' => phpversion(), + 'wp_version' => get_bloginfo( 'version' ), + ); + + /** + * Filters the parameters sent in the API request. + * + * @param array $api_params The array of data sent in the request. + * @param array $this->api_data The array of data set up in the class constructor. + * @param string $this->plugin_file The full path and filename of the file. + */ + $api_params = apply_filters( 'edd_sl_plugin_updater_api_params', $api_params, $this->api_data, $this->plugin_file ); + + $request = wp_remote_post( + $this->api_url, + array( + 'timeout' => 15, + 'sslverify' => $this->verify_ssl(), + 'body' => $api_params, + ) + ); + + if ( is_wp_error( $request ) || ( 200 !== wp_remote_retrieve_response_code( $request ) ) ) { + $this->log_failed_request(); + + return false; + } + + $request = json_decode( wp_remote_retrieve_body( $request ) ); + + if ( $request && isset( $request->sections ) ) { + $request->sections = maybe_unserialize( $request->sections ); + } else { + $request = false; + } + + if ( $request && isset( $request->banners ) ) { + $request->banners = maybe_unserialize( $request->banners ); + } + + if ( $request && isset( $request->icons ) ) { + $request->icons = maybe_unserialize( $request->icons ); + } + + if ( ! empty( $request->sections ) ) { + foreach ( $request->sections as $key => $section ) { + $request->$key = (array) $section; + } + } + + return $request; + } + + /** + * Get the version info from the cache, if it exists. + * + * @param string $cache_key + * @return object + */ + public function get_cached_version_info( $cache_key = '' ) { + + if ( empty( $cache_key ) ) { + $cache_key = $this->get_cache_key(); + } + + $cache = get_option( $cache_key ); + + // Cache is expired + if ( empty( $cache['timeout'] ) || time() > $cache['timeout'] ) { + return false; + } + + // We need to turn the icons into an array, thanks to WP Core forcing these into an object at some point. + $cache['value'] = json_decode( $cache['value'] ); + if ( ! empty( $cache['value']->icons ) ) { + $cache['value']->icons = (array) $cache['value']->icons; + } + + return $cache['value']; + + } + + /** + * Adds the plugin version information to the database. + * + * @param string $value + * @param string $cache_key + */ + public function set_version_info_cache( $value = '', $cache_key = '' ) { + + if ( empty( $cache_key ) ) { + $cache_key = $this->get_cache_key(); + } + + $data = array( + 'timeout' => strtotime( '+3 hours', time() ), + 'value' => wp_json_encode( $value ), + ); + + update_option( $cache_key, $data, 'no' ); + + // Delete the duplicate option + delete_option( 'edd_api_request_' . md5( serialize( $this->slug . $this->api_data['license'] . $this->beta ) ) ); + } + + /** + * Returns if the SSL of the store should be verified. + * + * @since 1.6.13 + * @return bool + */ + private function verify_ssl() { + return (bool) apply_filters( 'edd_sl_api_request_verify_ssl', true, $this ); + } + + /** + * Gets the unique key (option name) for a plugin. + * + * @since 1.9.0 + * @return string + */ + private function get_cache_key() { + $string = $this->slug . $this->api_data['license'] . $this->beta; + + return 'edd_sl_' . md5( serialize( $string ) ); + } + +} diff --git a/packages/nextgenthemes/wp-shared/includes/WP/Admin/EDD/ThemeUpdater.php b/packages/nextgenthemes/wp-shared/includes/WP/Admin/EDD/ThemeUpdater.php new file mode 100755 index 00000000..4410b59c --- /dev/null +++ b/packages/nextgenthemes/wp-shared/includes/WP/Admin/EDD/ThemeUpdater.php @@ -0,0 +1,160 @@ + 'http://easydigitaldownloads.com', + 'request_data' => array(), + 'theme_slug' => get_template(), + 'item_name' => '', + 'license' => '', + 'version' => '', + 'author' => '', + ) + ); + extract( $args ); + + $this->license = $license; + $this->item_name = $item_name; + $this->version = $version; + $this->theme_slug = sanitize_key( $theme_slug ); + $this->author = $author; + $this->remote_api_url = $remote_api_url; + $this->response_key = $this->theme_slug . '-update-response'; + $this->strings = $strings; + + add_filter( 'site_transient_update_themes', array( &$this, 'theme_update_transient' ) ); + add_filter( 'delete_site_transient_update_themes', array( &$this, 'delete_theme_update_transient' ) ); + add_action( 'load-update-core.php', array( &$this, 'delete_theme_update_transient' ) ); + add_action( 'load-themes.php', array( &$this, 'delete_theme_update_transient' ) ); + add_action( 'load-themes.php', array( &$this, 'load_themes_screen' ) ); + } + + function load_themes_screen() { + add_thickbox(); + add_action( 'admin_notices', array( &$this, 'update_nag' ) ); + } + + function update_nag() { + + $strings = $this->strings; + + $theme = wp_get_theme( $this->theme_slug ); + + $api_response = get_transient( $this->response_key ); + + if ( false === $api_response ) { + return; + } + + $update_url = wp_nonce_url( 'update.php?action=upgrade-theme&theme=' . urlencode( $this->theme_slug ), 'upgrade-theme_' . $this->theme_slug ); + $update_onclick = ' onclick="if ( confirm(\'' . esc_js( $strings['update-notice'] ) . '\') ) {return true;}return false;"'; + + if ( version_compare( $this->version, $api_response->new_version, '<' ) ) { + + echo '
'; + printf( + $strings['update-available'], + $theme->get( 'Name' ), + $api_response->new_version, + '#TB_inline?width=640&inlineId=' . $this->theme_slug . '_changelog', + $theme->get( 'Name' ), + $update_url, + $update_onclick + ); + echo '
'; + echo ''; + } + } + + function theme_update_transient( $value ) { + $update_data = $this->check_for_update(); + if ( $update_data ) { + $value->response[ $this->theme_slug ] = $update_data; + } + return $value; + } + + function delete_theme_update_transient() { + delete_transient( $this->response_key ); + } + + function check_for_update() { + + $update_data = get_transient( $this->response_key ); + + if ( false === $update_data ) { + $failed = false; + + $api_params = array( + 'edd_action' => 'get_version', + 'license' => $this->license, + 'name' => $this->item_name, + 'slug' => $this->theme_slug, + 'author' => $this->author, + ); + + $response = wp_remote_post( + $this->remote_api_url, + array( + 'timeout' => 15, + 'body' => $api_params, + ) + ); + + // Make sure the response was successful + if ( is_wp_error( $response ) || 200 != wp_remote_retrieve_response_code( $response ) ) { + $failed = true; + } + + $update_data = json_decode( wp_remote_retrieve_body( $response ) ); + + if ( ! is_object( $update_data ) ) { + $failed = true; + } + + // If the response failed, try again in 30 minutes + if ( $failed ) { + $data = new \stdClass(); + $data->new_version = $this->version; + set_transient( $this->response_key, $data, strtotime( '+30 minutes' ) ); + return false; + } + + // If the status is 'ok', return the update arguments + if ( ! $failed ) { + $update_data->sections = maybe_unserialize( $update_data->sections ); + set_transient( $this->response_key, $update_data, strtotime( '+12 hours' ) ); + } + } + + if ( version_compare( $this->version, $update_data->new_version, '>=' ) ) { + return false; + } + + return (array) $update_data; + } + +} diff --git a/packages/nextgenthemes/wp-shared/includes/WP/Admin/Notices.php b/packages/nextgenthemes/wp-shared/includes/WP/Admin/Notices.php new file mode 100755 index 00000000..d4408c30 --- /dev/null +++ b/packages/nextgenthemes/wp-shared/includes/WP/Admin/Notices.php @@ -0,0 +1,587 @@ + + * + * @author Nicolas Jonas + * @author Julien Liabeuf + * @version 1.2.1 + * @license GPL-3.0 + * @link https://nextgenthemes.com + * @link https://github.com/julien731/WP-Dismissible-Notices-Handler/blob/develop/handler.php + * @copyright 2021 Nicolas Jonas, 2018 Julien Liabeuf + */ + +if ( 'always' ) { + + final class Notices { + + /** + * @var Notices Holds the unique instance of the handler + * @since 1.0 + */ + private static $instance; + + /** + * Library version + * + * @since 1.0 + * @var string + */ + public $version = '1.2.1'; + + /** + * Required version of PHP. + * + * @since 1.0 + * @var string + */ + public $php_version_required = '5.5'; + + /** + * Minimum version of WordPress required to use the library + * + * @since 1.0 + * @var string + */ + public $wordpress_version_required = '4.7'; + + /** + * @var array Holds all our registered notices + * @since 1.0 + */ + private $notices; + + /** + * Instantiate and return the unique Notices object + * + * @since 1.0 + * @return object Notices Unique instance of the handler + */ + public static function instance(): object { + + if ( ! isset( self::$instance ) && ! ( self::$instance instanceof Notices ) ) { + self::$instance = new Notices(); + self::$instance->init(); + } + + return self::$instance; + + } + + /** + * Initialize the library + * + * @since 1.0 + */ + private function init(): void { + + // Make sure WordPress is compatible + if ( ! self::$instance->is_wp_compatible() ) { + self::$instance->spit_error( + sprintf( + /* translators: %s: required WordPress version */ + esc_html__( 'The library can not be used because your version of WordPress is too old. You need version %s at least.', 'advanced-responsive-video-embedder' ), + self::$instance->wordpress_version_required + ) + ); + + return; + } + + // Make sure PHP is compatible + if ( ! self::$instance->is_php_compatible() ) { + self::$instance->spit_error( + sprintf( + /* translators: %s: required php version */ + esc_html__( 'The library can not be used because your version of PHP is too old. You need version %s at least.', 'advanced-responsive-video-embedder' ), + self::$instance->php_version_required + ) + ); + + return; + } + + add_action( 'admin_notices', array( self::$instance, 'display' ) ); + add_action( 'wp_ajax_dnh_dismiss_notice', array( self::$instance, 'dismiss_notice_ajax' ) ); + + } + + /** + * Check if the current WordPress version fits the requirements + * + * @since 1.0 + */ + private function is_wp_compatible(): bool { + + if ( version_compare( get_bloginfo( 'version' ), self::$instance->wordpress_version_required, '<' ) ) { + return false; + } + + return true; + + } + + /** + * Check if the version of PHP is compatible with this library + * + * @since 1.0 + */ + private function is_php_compatible(): bool { + + if ( version_compare( phpversion(), self::$instance->php_version_required, '<' ) ) { + return false; + } + + return true; + + } + + /** + * Display all the registered notices + * + * @since 1.0 + */ + public function display(): void { + + if ( is_null( self::$instance->notices ) || empty( self::$instance->notices ) ) { + return; + } + + foreach ( self::$instance->notices as $id => $notice ) { + + $id = self::$instance->get_id( $id ); + + // Check if the notice was dismissed + if ( self::$instance->is_dismissed( $id ) ) { + continue; + } + + // Check if the current user has required capability + if ( ! empty( $notice['cap'] ) && ! current_user_can( $notice['cap'] ) ) { + continue; + } + + $class = array( + 'notice', + $notice['type'], + 'is-dismissible', + $notice['class'], + ); + + printf( + '

%3$s

', + esc_attr( "dnh-$id" ), + esc_attr( trim( implode( ' ', $class ) ) ), + wp_kses_post( $notice['content'] ) + ); + + } + + } + + /** + * Spits an error message at the top of the admin screen + * + * @since 1.0 + * + * @param string $error Error message to spit + * + */ + protected function spit_error( string $error ): void { + printf( + '
%1$s %2$s
', + esc_html__( 'Dismissible Notices Handler Error:', 'advanced-responsive-video-embedder' ), + wp_kses_post( $error ) + ); + } + + /** + * Sanitize a notice ID and return it + * + * @since 1.0 + * + * + */ + public function get_id( string $id ): string { + return sanitize_key( $id ); + } + + /** + * Get available notice types + * + * @since 1.0 + * @return array + */ + public function get_types(): array { + + $types = array( + 'error', + 'updated', + // New types of notification style. + 'notice-error', + 'notice-warning', + 'notice-success', + 'notice-info', + ); + + return apply_filters( 'dnh_notice_types', $types ); + + } + + /** + * Get the default arguments for a notice + * + * @since 1.0 + * @return array + */ + private function default_args(): array { + + $args = array( + 'screen' => '', // Coming soon + 'scope' => 'user', // Scope of the dismissal. Either user or global + 'cap' => '', // Required user capability + 'class' => '', // Additional class to add to the notice + ); + + return apply_filters( 'dnh_default_args', $args ); + + } + + /** + * Register a new notice + * + * @since 1.0 + * + * @param string $id Notice ID, used to identify it + * @param string $type Type of notice to display + * @param string $content Notice content + * @param array $args Additional parameters + * + */ + public function register_notice( string $id, string $type, string $content, array $args = array() ): bool { + + if ( is_null( self::$instance->notices ) ) { + self::$instance->notices = array(); + } + + $t = sanitize_text_field( $type ); + $id = self::$instance->get_id( $id ); + $type = in_array( $t, self::$instance->get_types(), true ) ? $t : 'updated'; + $content = wp_kses_post( $content ); + $args = wp_parse_args( $args, self::$instance->default_args() ); + + if ( array_key_exists( $id, self::$instance->notices ) ) { + + self::$instance->spit_error( + sprintf( + /* translators: %s: required php version */ + esc_html__( 'A notice with the ID %s has already been registered.', 'advanced-responsive-video-embedder' ), + "$id" + ) + ); + + return false; + } + + $notice = array( + 'type' => $type, + 'content' => $content, + ); + + $notice = array_merge( $notice, $args ); + + self::$instance->notices[ $id ] = $notice; + + return true; + + } + + /** + * Notice dismissal triggered by Ajax + * + * @since 1.0 + */ + public function dismiss_notice_ajax(): void { + + // phpcs:disable WordPress.Security.NonceVerification.Missing + if ( ! isset( $_POST['id'] ) ) { + echo 0; + exit; + } + + if ( empty( $_POST['id'] ) || false === strpos( $_POST['id'], 'dnh-' ) ) { + echo 0; + exit; + } + + $id = self::$instance->get_id( str_replace( 'dnh-', '', $_POST['id'] ) ); + // phpcs:enable + + echo wp_kses_post( self::$instance->dismiss_notice( $id ) ); + exit; + } + + /** + * Dismiss a notice + * + * @since 1.0 + * + * @param string $id ID of the notice to dismiss + * + */ + public function dismiss_notice( string $id ): bool { + + $notice = self::$instance->get_notice( self::$instance->get_id( $id ) ); + + if ( false === $notice ) { + return false; + } + + if ( self::$instance->is_dismissed( $id ) ) { + return false; + } + + return 'user' === $notice['scope'] ? self::$instance->dismiss_user( $id ) : self::$instance->dismiss_global( $id ); + + } + + /** + * Dismiss notice for the current user + * + * @since 1.0 + * + * @param string $id Notice ID + * + * @return int|bool + */ + private function dismiss_user( string $id ) { + + $dismissed = self::$instance->dismissed_user(); + + if ( in_array( $id, $dismissed, true ) ) { + return false; + } + + array_push( $dismissed, $id ); + + return update_user_meta( get_current_user_id(), 'dnh_dismissed_notices', $dismissed ); + + } + + /** + * Dismiss notice globally on the site + * + * @since 1.0 + * + * @param string $id Notice ID + * + */ + private function dismiss_global( string $id ): bool { + + $dismissed = self::$instance->dismissed_global(); + + if ( in_array( $id, $dismissed, true ) ) { + return false; + } + + array_push( $dismissed, $id ); + + return update_option( 'dnh_dismissed_notices', $dismissed ); + + } + + /** + * Restore a dismissed notice + * + * @since 1.0 + * + * @param string $id ID of the notice to restore + * + */ + public function restore_notice( string $id ): bool { + + $id = self::$instance->get_id( $id ); + $notice = self::$instance->get_notice( $id ); + + if ( false === $notice ) { + return false; + } + + return 'user' === $notice['scope'] ? self::$instance->restore_user( $id ) : self::$instance->restore_global( $id ); + + } + + /** + * Restore a notice dismissed by the current user + * + * @since 1.0 + * + * @param string $id ID of the notice to restore + * + */ + private function restore_user( string $id ): bool { + + $id = self::$instance->get_id( $id ); + $notice = self::$instance->get_notice( $id ); + + if ( false === $notice ) { + return false; + } + + $dismissed = self::$instance->dismissed_user(); + + if ( ! in_array( $id, $dismissed, true ) ) { + return false; + } + + $flip = array_flip( $dismissed ); + $key = $flip[ $id ]; + + unset( $dismissed[ $key ] ); + + return update_user_meta( get_current_user_id(), 'dnh_dismissed_notices', $dismissed ); + + } + + /** + * Restore a notice dismissed globally + * + * @since 1.0 + * + * @param string $id ID of the notice to restore + * + */ + private function restore_global( string $id ): bool { + + $id = self::$instance->get_id( $id ); + $notice = self::$instance->get_notice( $id ); + + if ( false === $notice ) { + return false; + } + + $dismissed = self::$instance->dismissed_global(); + + if ( ! in_array( $id, $dismissed, true ) ) { + return false; + } + + $flip = array_flip( $dismissed ); + $key = $flip[ $id ]; + + unset( $dismissed[ $key ] ); + + return update_option( 'dnh_dismissed_notices', $dismissed ); + + } + + /** + * Get all dismissed notices + * + * This includes notices dismissed globally or per user. + * + * @since 1.0 + * @return array + */ + public function dismissed_notices(): array { + + $user = self::$instance->dismissed_user(); + $global = self::$instance->dismissed_global(); + + return array_merge( $user, $global ); + + } + + /** + * Get user dismissed notices + * + * @since 1.0 + * @return array + */ + private function dismissed_user(): array { + + $dismissed = get_user_meta( get_current_user_id(), 'dnh_dismissed_notices', true ); + + if ( '' === $dismissed ) { + $dismissed = array(); + } + + return $dismissed; + + } + + /** + * Get globally dismissed notices + * + * @since 1.0 + * @return array + */ + private function dismissed_global(): array { + return get_option( 'dnh_dismissed_notices', array() ); + } + + /** + * Check if a notice has been dismissed + * + * @since 1.0 + * + * @param string $id Notice ID + * + */ + public function is_dismissed( string $id ): bool { + + $dismissed = self::$instance->dismissed_notices(); + + if ( ! in_array( self::$instance->get_id( $id ), $dismissed, true ) ) { + return false; + } + + return true; + + } + + /** + * Get all the registered notices + * + * @since 1.0 + * @return array|null + */ + public function get_notices(): ?array { + return self::$instance->notices; + } + + /** + * Return a specific notice + * + * @since 1.0 + * + * @param string $id Notice ID + * + * @return array|false + */ + public function get_notice( string $id ) { + + $id = self::$instance->get_id( $id ); + + if ( ! is_array( self::$instance->notices ) || ! array_key_exists( $id, self::$instance->notices ) ) { + return false; + } + + return self::$instance->notices[ $id ]; + + } + + } + +} diff --git a/packages/nextgenthemes/wp-shared/includes/WP/Admin/alpine.js b/packages/nextgenthemes/wp-shared/includes/WP/Admin/alpine.js new file mode 100644 index 00000000..2f644907 --- /dev/null +++ b/packages/nextgenthemes/wp-shared/includes/WP/Admin/alpine.js @@ -0,0 +1,5 @@ +(()=>{var Ze=!1,Qe=!1,V=[],et=-1;function Kt(e){wn(e)}function wn(e){V.includes(e)||V.push(e),En()}function we(e){let t=V.indexOf(e);t!==-1&&t>et&&V.splice(t,1)}function En(){!Qe&&!Ze&&(Ze=!0,queueMicrotask(vn))}function vn(){Ze=!1,Qe=!0;for(let e=0;ee.effect(t,{scheduler:r=>{tt?Kt(r):r()}}),rt=e.raw}function nt(e){I=e}function Vt(e){let t=()=>{};return[n=>{let i=I(n);return e._x_effects||(e._x_effects=new Set,e._x_runEffects=()=>{e._x_effects.forEach(o=>o())}),e._x_effects.add(i),t=()=>{i!==void 0&&(e._x_effects.delete(i),L(i))},i},()=>{t()}]}function q(e,t,r={}){e.dispatchEvent(new CustomEvent(t,{detail:r,bubbles:!0,composed:!0,cancelable:!0}))}function O(e,t){if(typeof ShadowRoot=="function"&&e instanceof ShadowRoot){Array.from(e.children).forEach(i=>O(i,t));return}let r=!1;if(t(e,()=>r=!0),r)return;let n=e.firstElementChild;for(;n;)O(n,t,!1),n=n.nextElementSibling}function v(e,...t){console.warn(`Alpine Warning: ${e}`,...t)}var qt=!1;function Ut(){qt&&v("Alpine has already been initialized on this page. Calling Alpine.start() more than once can cause problems."),qt=!0,document.body||v("Unable to initialize. Trying to load Alpine before `` is available. Did you forget to add `defer` in Alpine's `