From 2a6fbebb25a433b7e9f5315f14b43426cf7a5a86 Mon Sep 17 00:00:00 2001 From: sybrew Date: Thu, 2 Jun 2022 08:03:06 +0200 Subject: [PATCH] Mega-pint. I mean, mega-push. Introducing Transport extension (dev). Augements sybrew/the-seo-framework#548 (Importer) Augements sybrew/the-seo-framework#98 (PHP 7.2 requirement) --- bootstrap/define.php | 8 - bootstrap/install.php | 168 +++++ bootstrap/load.php | 6 +- bootstrap/update.php | 150 +---- .../focus/trunk/inc/classes/scoring.class.php | 2 +- .../focus/trunk/lib/js/tsfem-focus-inpost.js | 2 +- .../trunk/lib/js/tsfem-focus-parser.worker.js | 4 +- .../lib/js/tsfem-focus-parser.worker.min.js | 2 +- .../trunk/views/inpost/score-template.php | 6 +- .../essentials/honeypot/trunk/honeypot.php | 2 +- .../trunk/inc/classes/admin.class.php | 479 ++++++++++++++ .../trunk/inc/classes/handler.class.php | 373 +++++++++++ .../classes/importers/postmeta/core.class.php | 217 +++++++ .../inc/classes/importers/postmeta/index.php | 0 .../postmeta/wordpress-seo.class.php | 117 ++++ .../transport/trunk/inc/classes/index.php | 0 .../trunk/inc/classes/logger/index.php | 0 .../trunk/inc/classes/logger/server.class.php | 266 ++++++++ .../trunk/inc/classes/logger/store.class.php | 108 ++++ .../transformers/postmeta/core.class.php | 68 ++ .../wordpress-seo-transformer.class.php | 44 ++ extensions/free/transport/trunk/inc/index.php | 0 extensions/free/transport/trunk/index.php | 0 .../free/transport/trunk/lib/css/index.php | 0 extensions/free/transport/trunk/lib/index.php | 0 .../free/transport/trunk/lib/js/index.php | 0 .../transport/trunk/lib/js/tsfem-transport.js | 385 +++++++++++ .../trunk/lib/js/tsfem-transport.min.js | 1 + extensions/free/transport/trunk/readme.md | 0 extensions/free/transport/trunk/transport.php | 126 ++++ .../free/transport/trunk/views/index.php | 0 .../trunk/views/layout/general/footer.php | 33 + .../trunk/views/layout/general/index.php | 0 .../trunk/views/layout/general/meta.php | 15 + .../trunk/views/layout/general/top.php | 37 ++ .../transport/trunk/views/layout/index.php | 0 .../trunk/views/layout/pages/index.php | 0 .../trunk/views/layout/pages/transport.php | 36 ++ .../trunk/views/layout/panes/importer.php | 127 ++++ .../trunk/views/layout/panes/index.php | 0 .../trunk/views/layout/panes/logger.php | 14 + .../local/trunk/inc/classes/fields.class.php | 6 +- .../trunk/inc/classes/settings.class.php | 4 +- .../trunk/inc/traits/schema-packer.trait.php | 24 +- .../trunk/inc/traits/secure-post.trait.php | 3 +- .../local/trunk/views/layout/pages/local.php | 2 +- .../trunk/views/layout/pages/settings.php | 6 +- .../monitor/trunk/inc/classes/admin.class.php | 12 +- .../monitor/trunk/lib/css/tsfem-monitor.css | 2 +- .../monitor/trunk/lib/js/tsfem-monitor.js | 2 +- .../views/layout/{pages => general}/meta.php | 0 .../trunk/views/layout/pages/connect.php | 4 +- inc/classes/adminpages.class.php | 2 +- inc/classes/core.class.php | 24 +- inc/classes/extensionsettings.class.php | 4 +- inc/classes/formgenerator.class.php | 13 +- inc/classes/html.class.php | 44 -- inc/classes/layout.class.php | 9 +- inc/classes/panes.class.php | 48 +- inc/functions/api.php | 19 + inc/traits/core/error.trait.php | 44 +- inc/traits/core/ui.trait.php | 13 +- inc/traits/extension/forms.trait.php | 6 +- inc/traits/extension/views.trait.php | 114 ++++ .../manager/extensions-layout.trait.php | 8 +- inc/traits/manager/extensions.trait.php | 21 +- inc/traits/manager/options.trait.php | 9 +- lib/css/tsfem-form.css | 4 +- lib/css/tsfem-form.min.css | 2 +- lib/css/tsfem-manager.css | 4 +- lib/css/tsfem-manager.min.css | 2 +- lib/css/tsfem-ui.css | 117 ++-- lib/css/tsfem-ui.min.css | 2 +- lib/js/tsfem-form.js | 17 +- lib/js/tsfem-form.min.js | 2 +- lib/js/tsfem-ui.js | 59 +- lib/js/tsfem-ui.min.js | 2 +- lib/js/tsfem.js | 2 +- lib/js/tsfinstaller.js | 606 +++++++++--------- readme.txt | 57 +- the-seo-framework-extension-manager.php | 5 +- views/forms/free.php | 2 +- views/forms/get.php | 10 +- views/forms/key.php | 8 +- views/layout/extension/pane.php | 6 +- views/layout/{pages => general}/meta.php | 0 86 files changed, 3411 insertions(+), 735 deletions(-) create mode 100644 bootstrap/install.php create mode 100644 extensions/free/transport/trunk/inc/classes/admin.class.php create mode 100644 extensions/free/transport/trunk/inc/classes/handler.class.php create mode 100644 extensions/free/transport/trunk/inc/classes/importers/postmeta/core.class.php create mode 100644 extensions/free/transport/trunk/inc/classes/importers/postmeta/index.php create mode 100644 extensions/free/transport/trunk/inc/classes/importers/postmeta/wordpress-seo.class.php create mode 100644 extensions/free/transport/trunk/inc/classes/index.php create mode 100644 extensions/free/transport/trunk/inc/classes/logger/index.php create mode 100644 extensions/free/transport/trunk/inc/classes/logger/server.class.php create mode 100644 extensions/free/transport/trunk/inc/classes/logger/store.class.php create mode 100644 extensions/free/transport/trunk/inc/classes/transformers/postmeta/core.class.php create mode 100644 extensions/free/transport/trunk/inc/classes/transformers/postmeta/wordpress-seo-transformer.class.php create mode 100644 extensions/free/transport/trunk/inc/index.php create mode 100644 extensions/free/transport/trunk/index.php create mode 100644 extensions/free/transport/trunk/lib/css/index.php create mode 100644 extensions/free/transport/trunk/lib/index.php create mode 100644 extensions/free/transport/trunk/lib/js/index.php create mode 100644 extensions/free/transport/trunk/lib/js/tsfem-transport.js create mode 100644 extensions/free/transport/trunk/lib/js/tsfem-transport.min.js create mode 100644 extensions/free/transport/trunk/readme.md create mode 100644 extensions/free/transport/trunk/transport.php create mode 100644 extensions/free/transport/trunk/views/index.php create mode 100644 extensions/free/transport/trunk/views/layout/general/footer.php create mode 100644 extensions/free/transport/trunk/views/layout/general/index.php create mode 100644 extensions/free/transport/trunk/views/layout/general/meta.php create mode 100644 extensions/free/transport/trunk/views/layout/general/top.php create mode 100644 extensions/free/transport/trunk/views/layout/index.php create mode 100644 extensions/free/transport/trunk/views/layout/pages/index.php create mode 100644 extensions/free/transport/trunk/views/layout/pages/transport.php create mode 100644 extensions/free/transport/trunk/views/layout/panes/importer.php create mode 100644 extensions/free/transport/trunk/views/layout/panes/index.php create mode 100644 extensions/free/transport/trunk/views/layout/panes/logger.php rename extensions/premium/monitor/trunk/views/layout/{pages => general}/meta.php (100%) create mode 100644 inc/traits/extension/views.trait.php rename views/layout/{pages => general}/meta.php (100%) diff --git a/bootstrap/define.php b/bootstrap/define.php index 50ceb0e4..93a54751 100644 --- a/bootstrap/define.php +++ b/bootstrap/define.php @@ -262,11 +262,3 @@ define( 'TSFEM_INPOST_NO_CRON', 0b01000 ); define( 'TSFEM_INPOST_NO_REVISION', 0b10000 ); // phpcs:enable, Generic.Functions.FunctionCallArgumentSpacing.TooMuchSpaceAfterComma - -/** - * PHP <7.0 compat - * - * @since 1.0.0 - * @since 2.5.1 Moved to define.php - */ -defined( 'PHP_INT_MIN' ) or define( 'PHP_INT_MIN', ~ PHP_INT_MAX ); diff --git a/bootstrap/install.php b/bootstrap/install.php new file mode 100644 index 00000000..a22c174b --- /dev/null +++ b/bootstrap/install.php @@ -0,0 +1,168 @@ +. + */ + +\add_action( 'tsfem_needs_the_seo_framework', __NAMESPACE__ . '\\_prepare_tsf_installer' ); +/** + * Prepares scripts for TSF "WP v4.6 Shiny Updates" installation. + * + * @since 2.2.0 + * @access private + */ +function _prepare_tsf_installer() { + + if ( ! \is_main_site() ) return; + if ( ! \current_user_can( 'install_plugins' ) ) return; + if ( 'update.php' === $GLOBALS['pagenow'] ) return; + + if ( ! \function_exists( '\\get_plugins' ) ) + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + + $plugins = \get_plugins(); + + if ( isset( $plugins['autodescription/autodescription.php'] ) || isset( $plugins['the-seo-framework/autodescription.php'] ) ) return; + + \add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\\_prepare_tsf_nag_installer_scripts' ); + \add_action( 'admin_notices', __NAMESPACE__ . '\\_nag_install_tsf' ); +} + +/** + * Registers and enqueues the TSF installer-nag required scripts. + * + * @since 2.5.0 + * @access private + */ +function _prepare_tsf_nag_installer_scripts() { + $deps = [ + 'plugin-install', + 'updates', + ]; + $scriptid = 'tsfinstaller'; + $suffix = SCRIPT_DEBUG ? '' : '.min'; + $strings = [ + 'slug' => 'autodescription', + ]; + + \wp_register_script( $scriptid, TSF_EXTENSION_MANAGER_DIR_URL . "lib/js/{$scriptid}{$suffix}.js", $deps, TSF_EXTENSION_MANAGER_VERSION, true ); + \wp_localize_script( $scriptid, "{$scriptid}L10n", $strings ); + + \add_action( 'admin_print_styles', __NAMESPACE__ . '\\_print_tsf_nag_installer_styles' ); + \add_action( 'admin_footer', '\\wp_print_request_filesystem_credentials_modal' ); + \add_action( 'admin_footer', '\\wp_print_admin_notice_templates' ); + + \wp_enqueue_style( 'plugin-install' ); + \wp_enqueue_script( $scriptid ); + \add_thickbox(); +} + +/** + * Outputs "button-small" "Shiny Updates" compatibility style. + * + * @since 2.2.0 + * @access private + */ +function _print_tsf_nag_installer_styles() { + echo ''; +} + +/** + * Nags the site administrator to install TSF to continue. + * + * @since 2.2.0 + * @access private + */ +function _nag_install_tsf() { + + $plugin_slug = 'autodescription'; + $tsf_text = 'The SEO Framework'; + + /** + * @source https://github.com/WordPress/WordPress/blob/4.9-branch/wp-admin/import.php#L162-L178 + * @uses Spaghetti. + * @see WP Core class Plugin_Installer_Skin + */ + $details_url = \add_query_arg( + [ + 'tab' => 'plugin-information', + 'plugin' => $plugin_slug, + 'from' => 'plugins', + 'TB_iframe' => 'true', + 'width' => 600, + 'height' => 550, + ], + \network_admin_url( 'plugin-install.php' ) + ); + $tsf_details_link = sprintf( + '%3$s', + \esc_url( $details_url ), + /* translators: %s: Plugin name */ + \esc_attr( sprintf( \__( 'Learn more about %s', 'the-seo-framework-extension-manager' ), $tsf_text ) ), + \esc_html__( 'View plugin details', 'the-seo-framework-extension-manager' ) + ); + $nag = sprintf( + /* translators: 1 = Extension Manager, 2 = The SEO Framework, 3 = View plugin details. */ + \esc_html__( '%1$s requires %2$s plugin to function. %3$s', 'the-seo-framework-extension-manager' ), + sprintf( '%s', 'Extension Manager' ), + sprintf( '%s', \esc_html( $tsf_text ) ), + $tsf_details_link + ); + + /** + * @source https://github.com/WordPress/WordPress/blob/4.9-branch/wp-admin/import.php#L125-L138 + * @uses Bolognese sauce. + * @see The closest bowl of spaghetti. Or WordPress\Administration\wp.updates/updates.js + * This joke was brought to you by the incomplete API of WP Shiny Updates, where + * WP's import.php has been directly injected into, rather than "calling" it via its API. + * Therefore, leaving the incompleteness undiscovered internally. + * @TODO Open core track ticket. + */ + $install_nonce_url = \wp_nonce_url( + \add_query_arg( + [ + 'action' => 'install-plugin', + 'plugin' => $plugin_slug, + 'from' => 'plugins', + ], + \self_admin_url( 'update.php' ) + ), + 'install-plugin_' . $plugin_slug + ); + $install_action = sprintf( + '%5$s', + \esc_url( $install_nonce_url ), + \esc_attr( $plugin_slug ), + \esc_attr( $tsf_text ), + /* translators: %s: The SEO Framework */ + \esc_attr( sprintf( \__( 'Install %s', 'the-seo-framework-extension-manager' ), $tsf_text ) ), + \esc_html__( 'Install Now', 'the-seo-framework-extension-manager' ) + ); + + // phpcs:disable, WordPress.Security.EscapeOutput.OutputNotEscaped -- it is. + printf( + '

%s

', + \is_rtl() ? $install_action . ' ' . $nag : $nag . ' ' . $install_action + ); + // phpcs:enable, WordPress.Security.EscapeOutput.OutputNotEscaped +} diff --git a/bootstrap/load.php b/bootstrap/load.php index 3a4d7b40..02cb8435 100644 --- a/bootstrap/load.php +++ b/bootstrap/load.php @@ -195,7 +195,7 @@ function _register_autoloader() { ]; foreach ( $integrity_classes as $_class ) { - // phpcs:ignore, TSF.Performance.Functions.PHP -- no other method exists. + // phpcs:ignore, TSF.Performance.Functions.PHP -- no other method exists. if ( class_exists( $_class, false ) ) die; } @@ -256,7 +256,7 @@ function can_load_class() { */ function _autoload_classes( $class ) { - // It's TSF_Extension_Manager, not tsf_extension_manager! + // NB It's TSF_Extension_Manager, not tsf_extension_manager! if ( 0 !== strpos( strtolower( $class ), 'tsf_extension_manager\\', 0 ) ) return; if ( WP_DEBUG ) { @@ -273,7 +273,7 @@ function _autoload_classes( $class ) { } static $_timenow = true; - + // Prevent running two timers at once, restart timing once done loading batch. if ( $_timenow ) { $_bootstrap_timer = microtime( true ); $_timenow = false; diff --git a/bootstrap/update.php b/bootstrap/update.php index 5db3329c..b4c54954 100644 --- a/bootstrap/update.php +++ b/bootstrap/update.php @@ -7,150 +7,22 @@ \defined( 'TSF_EXTENSION_MANAGER_PLUGIN_BASE_FILE' ) or die; -\add_action( 'tsfem_needs_the_seo_framework', __NAMESPACE__ . '\\_prepare_tsf_installer' ); /** - * Prepares scripts for TSF "WP v4.6 Shiny Updates" installation. + * The SEO Framework - Extension Manager plugin + * Copyright (C) 2018-2022 Sybre Waaijer, CyberWire (https://cyberwire.nl/) * - * @since 2.2.0 - * @access private - */ -function _prepare_tsf_installer() { - - if ( \wp_doing_cron() ) return; - if ( ! \is_admin() ) return; // This is implied, though. - if ( ! \is_main_site() ) return; - if ( ! \current_user_can( 'install_plugins' ) ) return; - if ( 'update.php' === $GLOBALS['pagenow'] ) return; - - if ( ! \function_exists( '\\get_plugins' ) ) - require_once ABSPATH . 'wp-admin/includes/plugin.php'; - - $plugins = \get_plugins(); - - if ( isset( $plugins['autodescription/autodescription.php'] ) || isset( $plugins['the-seo-framework/autodescription.php'] ) ) return; - - \add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\\_prepare_tsf_nag_installer_scripts' ); - \add_action( 'admin_notices', __NAMESPACE__ . '\\_nag_install_tsf' ); -} - -/** - * Registers and enqueues the TSF installer-nag required scripts. + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. * - * @since 2.5.0 - * @access private - */ -function _prepare_tsf_nag_installer_scripts() { - $deps = [ - 'plugin-install', - 'updates', - ]; - $scriptid = 'tsfinstaller'; - $suffix = SCRIPT_DEBUG ? '' : '.min'; - $strings = [ - 'slug' => 'autodescription', - ]; - - \wp_register_script( $scriptid, TSF_EXTENSION_MANAGER_DIR_URL . "lib/js/{$scriptid}{$suffix}.js", $deps, TSF_EXTENSION_MANAGER_VERSION, true ); - \wp_localize_script( $scriptid, "{$scriptid}L10n", $strings ); - - \add_action( 'admin_print_styles', __NAMESPACE__ . '\\_print_tsf_nag_installer_styles' ); - \add_action( 'admin_footer', '\\wp_print_request_filesystem_credentials_modal' ); - \add_action( 'admin_footer', '\\wp_print_admin_notice_templates' ); - - \wp_enqueue_style( 'plugin-install' ); - \wp_enqueue_script( $scriptid ); - \add_thickbox(); -} - -/** - * Outputs "button-small" "Shiny Updates" compatibility style. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * @since 2.2.0 - * @access private + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ -function _print_tsf_nag_installer_styles() { - echo ''; -} - -/** - * Nags the site administrator to install TSF to continue. - * - * @since 2.2.0 - * @access private - */ -function _nag_install_tsf() { - - $plugin_slug = 'autodescription'; - $tsf_text = 'The SEO Framework'; - - /** - * @source https://github.com/WordPress/WordPress/blob/4.9-branch/wp-admin/import.php#L162-L178 - * @uses Spaghetti. - * @see WP Core class Plugin_Installer_Skin - */ - $details_url = \add_query_arg( - [ - 'tab' => 'plugin-information', - 'plugin' => $plugin_slug, - 'from' => 'plugins', - 'TB_iframe' => 'true', - 'width' => 600, - 'height' => 550, - ], - \network_admin_url( 'plugin-install.php' ) - ); - $tsf_details_link = sprintf( - '%3$s', - \esc_url( $details_url ), - /* translators: %s: Plugin name */ - \esc_attr( sprintf( \__( 'Learn more about %s', 'the-seo-framework-extension-manager' ), $tsf_text ) ), - \esc_html__( 'View plugin details', 'the-seo-framework-extension-manager' ) - ); - $nag = sprintf( - /* translators: 1 = Extension Manager, 2 = The SEO Framework, 3 = View plugin details. */ - \esc_html__( '%1$s requires %2$s plugin to function. %3$s', 'the-seo-framework-extension-manager' ), - sprintf( '%s', 'Extension Manager' ), - sprintf( '%s', \esc_html( $tsf_text ) ), - $tsf_details_link - ); - - /** - * @source https://github.com/WordPress/WordPress/blob/4.9-branch/wp-admin/import.php#L125-L138 - * @uses Bolognese sauce. - * @see The closest bowl of spaghetti. Or WordPress\Administration\wp.updates/updates.js - * This joke was brought to you by the incomplete API of WP Shiny Updates, where - * WP's import.php has been directly injected into, rather than "calling" it via its API. - * Therefore, leaving the incompleteness undiscovered internally. - * @TODO Open core track ticket. - */ - $install_nonce_url = \wp_nonce_url( - \add_query_arg( - [ - 'action' => 'install-plugin', - 'plugin' => $plugin_slug, - 'from' => 'plugins', - ], - \self_admin_url( 'update.php' ) - ), - 'install-plugin_' . $plugin_slug - ); - $install_action = sprintf( - '%5$s', - \esc_url( $install_nonce_url ), - \esc_attr( $plugin_slug ), - \esc_attr( $tsf_text ), - /* translators: %s: The SEO Framework */ - \esc_attr( sprintf( \__( 'Install %s', 'the-seo-framework-extension-manager' ), $tsf_text ) ), - \esc_html__( 'Install Now', 'the-seo-framework-extension-manager' ) - ); - - // phpcs:disable, WordPress.Security.EscapeOutput.OutputNotEscaped -- it is. - printf( - '

%s

', - \is_rtl() ? $install_action . ' ' . $nag : $nag . ' ' . $install_action - ); - // phpcs:enable, WordPress.Security.EscapeOutput.OutputNotEscaped -} \add_action( 'admin_notices', __NAMESPACE__ . '\\_check_external_blocking' ); /** diff --git a/extensions/essentials/focus/trunk/inc/classes/scoring.class.php b/extensions/essentials/focus/trunk/inc/classes/scoring.class.php index 6e17f4fd..ffa90865 100644 --- a/extensions/essentials/focus/trunk/inc/classes/scoring.class.php +++ b/extensions/essentials/focus/trunk/inc/classes/scoring.class.php @@ -214,7 +214,7 @@ public function get_nearest_numeric_index_value( array $a, $value ) { * @return string The data attributes. */ public function get_data_attributes( $type ) { - return \TSF_Extension_Manager\HTML::make_data_attributes( [ + return \The_SEO_Framework\Interpreters\HTML::make_data_attributes( [ 'scores' => \tsf_extension_manager()->filter_keys( $this->get_template( $type ), [ 'assessment', 'maxScore', 'minScore', 'phrasing', 'rating', 'scoring' ] diff --git a/extensions/essentials/focus/trunk/lib/js/tsfem-focus-inpost.js b/extensions/essentials/focus/trunk/lib/js/tsfem-focus-inpost.js index c46e65ce..0b761cca 100644 --- a/extensions/essentials/focus/trunk/lib/js/tsfem-focus-inpost.js +++ b/extensions/essentials/focus/trunk/lib/js/tsfem-focus-inpost.js @@ -1,5 +1,5 @@ /** - * This file holds Focus' code for interpreting keywords and their subjects. + * This file holds Focus's code for interpreting keywords and their subjects. * Serve JavaScript as an addition, not as an ends or means. * Alas, there's no other way here. * diff --git a/extensions/essentials/focus/trunk/lib/js/tsfem-focus-parser.worker.js b/extensions/essentials/focus/trunk/lib/js/tsfem-focus-parser.worker.js index c6115b31..c65f4634 100644 --- a/extensions/essentials/focus/trunk/lib/js/tsfem-focus-parser.worker.js +++ b/extensions/essentials/focus/trunk/lib/js/tsfem-focus-parser.worker.js @@ -1,5 +1,5 @@ /** - * This worker file holds Focus' code for interpreting contents. + * This worker file holds Focus's code for interpreting contents. * Serve JavaScript as an addition, not as an ends or means. * Alas, there's no other way here. * @@ -178,7 +178,7 @@ const escapeStr = ( str, noquotes ) => { * @param {function} cb The callback function returning a Promise. * @param {number|undefined} timeout The iteration timeout. Optional. Defaults to 0. * @param {number|undefined} stopAt The iteration anti-lag blocker. Optional. Defaults to 2000 ms. - * @return {jQuery.Deferred|Promise} The promise object. + * @return {Promise} The promise object. */ const promiseLoop = ( iterable, cb, timeout = 0, stopAt = 2000 ) => new Promise( ( resolve, reject ) => { let its = iterable.length; diff --git a/extensions/essentials/focus/trunk/lib/js/tsfem-focus-parser.worker.min.js b/extensions/essentials/focus/trunk/lib/js/tsfem-focus-parser.worker.min.js index 8c1d917a..5dd7694f 100644 --- a/extensions/essentials/focus/trunk/lib/js/tsfem-focus-parser.worker.min.js +++ b/extensions/essentials/focus/trunk/lib/js/tsfem-focus-parser.worker.min.js @@ -1 +1 @@ -"use strict";let regex,synonyms,inflections,workerId="",inflectionCount=0,synonymCount=0,inflectionCharCount=0,synonymCharCount=0,contentCharCount=0;const reset=()=>{inflectionCount=0,synonymCount=0,inflectionCharCount=0,synonymCharCount=0,contentCharCount=0,regex=void 0,synonyms=void 0,inflections=void 0},normalizeSpacing=a=>a.replace(/(?!(\n+|\r)+)( |\s)+/gu," "),escapeRegex=a=>a.replace(/[-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&"),PLPN=(()=>{const a="[^0-9A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0345\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05B0-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u05D0-\u05EA\u05EF-\u05F2\u0610-\u061A\u0620-\u0657\u0659-\u065F\u066E-\u06D3\u06D5-\u06DC\u06E1-\u06E8\u06ED-\u06EF\u06FA-\u06FC\u06FF\u0710-\u073F\u074D-\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0817\u081A-\u082C\u0840-\u0858\u0860-\u086A\u08A0-\u08B4\u08B6-\u08BD\u08D4-\u08DF\u08E3-\u08E9\u08F0-\u093B\u093D-\u094C\u094E-\u0950\u0955-\u0963\u0971-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD-\u09C4\u09C7\u09C8\u09CB\u09CC\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09F0\u09F1\u09FC\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3E-\u0A42\u0A47\u0A48\u0A4B\u0A4C\u0A51\u0A59-\u0A5C\u0A5E\u0A70-\u0A75\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD-\u0AC5\u0AC7-\u0AC9\u0ACB\u0ACC\u0AD0\u0AE0-\u0AE3\u0AF9-\u0AFC\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D-\u0B44\u0B47\u0B48\u0B4B\u0B4C\u0B56\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCC\u0BD0\u0BD7\u0C00-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D-\u0C44\u0C46-\u0C48\u0C4A-\u0C4C\u0C55\u0C56\u0C58-\u0C5A\u0C60-\u0C63\u0C80-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCC\u0CD5\u0CD6\u0CDE\u0CE0-\u0CE3\u0CF1\u0CF2\u0D00-\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D-\u0D44\u0D46-\u0D48\u0D4A-\u0D4C\u0D4E\u0D54-\u0D57\u0D5F-\u0D63\u0D7A-\u0D7F\u0D82\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2\u0DF3\u0E01-\u0E3A\u0E40-\u0E46\u0E4D\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB9\u0EBB-\u0EBD\u0EC0-\u0EC4\u0EC6\u0ECD\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F71-\u0F81\u0F88-\u0F97\u0F99-\u0FBC\u1000-\u1036\u1038\u103B-\u103F\u1050-\u108F\u109A-\u109D\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1713\u1720-\u1733\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17B3\u17B6-\u17C8\u17D7\u17DC\u1820-\u1878\u1880-\u18AA\u18B0-\u18F5\u1900-\u191E\u1920-\u192B\u1930-\u1938\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A1B\u1A20-\u1A5E\u1A61-\u1A74\u1AA7\u1B00-\u1B33\u1B35-\u1B43\u1B45-\u1B4B\u1B80-\u1BA9\u1BAC-\u1BAF\u1BBA-\u1BE5\u1BE7-\u1BF1\u1C00-\u1C36\u1C4D-\u1C4F\u1C5A-\u1C7D\u1C80-\u1C88\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u1D00-\u1DBF\u1DE7-\u1DF4\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u24B6-\u24E9\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2DE0-\u2DFF\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FEF\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA674-\uA67B\uA67F-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7BF\uA7C2-\uA7C6\uA7F7-\uA805\uA807-\uA827\uA840-\uA873\uA880-\uA8C3\uA8C5\uA8F2-\uA8F7\uA8FB\uA8FD-\uA8FF\uA90A-\uA92A\uA930-\uA952\uA960-\uA97C\uA980-\uA9B2\uA9B4-\uA9BF\uA9CF\uA9E0-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA36\uAA40-\uAA4D\uAA60-\uAA76\uAA7A-\uAABE\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEF\uAAF2-\uAAF5\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB67\uAB70-\uABEA\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]";let b=escapeRegex(a);return{base:a,trim:new RegExp(`^(${b}\+)*(.*?)(${b}\+)*$`),find:new RegExp(`${a}+`,"gu")}})(),bewilderRegexNonWords=a=>PLPN.trim.exec(a.replace(PLPN.find,`${PLPN.base}+`))[2],escapeStr=(a,b)=>a.length?b?a.replace(/[&<>]/g,a=>({"&":"&","<":"<",">":">"})[a]):a.replace(/[&<>"']/g,a=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[a]):"",promiseLoop=(a,b,c=0,d=2e3)=>new Promise((e,f)=>{let g=a.length;if(!g)return e();const h=i=>{let j,k,l;d&&(k=setTimeout(()=>{clearTimeout(j),l=setTimeout(()=>{f()},250)},d)),j=setTimeout(()=>new Promise((c,d)=>{try{b(a[i]),c()}catch(a){d()}}).then(()=>{if(d&&(clearTimeout(k),l)){if(i{f()}),c)};h(0)}),countChars=a=>(a=a.match(/(?=([^<>]+))\1(?=<|$)/gi),a=a&&a.join(" ")||"",a=a.replace(/\s+/giu," "),+a.length||0),countWords=(a,b)=>{let c,d=bewilderRegexNonWords(escapeRegex(escapeStr(a,!0)));if(!d)return 0;for(let e=0;eb.replace(new RegExp(escapeRegex(escapeStr(a,!0)),"gi"),"/"),countCharacters=a=>new Promise(b=>{setTimeout(()=>{contentCharCount+=countChars(a),b()},5)}),countInflections=a=>{let b=inflections,c=a;return b.length&&b.sort((c,a)=>a.length-c.length),promiseLoop(b,a=>{let b=countWords(a,c);inflectionCount+=b,inflectionCharCount+=a.length*b,c=stripWord(a,c)},5,1e4)},countSynonyms=a=>{let b=synonyms,c=a;return b.length&&b.sort((c,a)=>a.length-c.length),promiseLoop(b,a=>{let b=countWords(a,c);synonymCount+=b,synonymCharCount+=a.length*b,c=stripWord(a,c)},5,1e4)};onmessage=a=>{workerId=a.data.id,reset();let b=a.data.data,c=normalizeSpacing(b.content);regex=b.regex,inflections=b.inflections,synonyms=b.synonyms,c?Promise.all([b.assess.getCharCount&&countCharacters(c),countInflections(c),countSynonyms(c)]).then(()=>{postMessage({inflectionCount,synonymCount,inflectionCharCount,synonymCharCount,contentCharCount})}).catch(a=>{postMessage({workerId,error:a})}):postMessage(void 0)},onerror=(a,b,c,d,e)=>{postMessage({workerId,error:e})}; +let regex,synonyms,inflections,workerId="",inflectionCount=0,synonymCount=0,inflectionCharCount=0,synonymCharCount=0,contentCharCount=0;const reset=()=>{inflectionCount=0,synonymCount=0,inflectionCharCount=0,synonymCharCount=0,contentCharCount=0,regex=void 0,synonyms=void 0,inflections=void 0},normalizeSpacing=a=>a.replace(/(?!(\n+|\r)+)( |\s)+/gu," "),escapeRegex=a=>a.replace(/[-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&"),PLPN=(()=>{const a="[^0-9A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0345\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05B0-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u05D0-\u05EA\u05EF-\u05F2\u0610-\u061A\u0620-\u0657\u0659-\u065F\u066E-\u06D3\u06D5-\u06DC\u06E1-\u06E8\u06ED-\u06EF\u06FA-\u06FC\u06FF\u0710-\u073F\u074D-\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0817\u081A-\u082C\u0840-\u0858\u0860-\u086A\u08A0-\u08B4\u08B6-\u08BD\u08D4-\u08DF\u08E3-\u08E9\u08F0-\u093B\u093D-\u094C\u094E-\u0950\u0955-\u0963\u0971-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD-\u09C4\u09C7\u09C8\u09CB\u09CC\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09F0\u09F1\u09FC\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3E-\u0A42\u0A47\u0A48\u0A4B\u0A4C\u0A51\u0A59-\u0A5C\u0A5E\u0A70-\u0A75\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD-\u0AC5\u0AC7-\u0AC9\u0ACB\u0ACC\u0AD0\u0AE0-\u0AE3\u0AF9-\u0AFC\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D-\u0B44\u0B47\u0B48\u0B4B\u0B4C\u0B56\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCC\u0BD0\u0BD7\u0C00-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D-\u0C44\u0C46-\u0C48\u0C4A-\u0C4C\u0C55\u0C56\u0C58-\u0C5A\u0C60-\u0C63\u0C80-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCC\u0CD5\u0CD6\u0CDE\u0CE0-\u0CE3\u0CF1\u0CF2\u0D00-\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D-\u0D44\u0D46-\u0D48\u0D4A-\u0D4C\u0D4E\u0D54-\u0D57\u0D5F-\u0D63\u0D7A-\u0D7F\u0D82\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2\u0DF3\u0E01-\u0E3A\u0E40-\u0E46\u0E4D\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB9\u0EBB-\u0EBD\u0EC0-\u0EC4\u0EC6\u0ECD\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F71-\u0F81\u0F88-\u0F97\u0F99-\u0FBC\u1000-\u1036\u1038\u103B-\u103F\u1050-\u108F\u109A-\u109D\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1713\u1720-\u1733\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17B3\u17B6-\u17C8\u17D7\u17DC\u1820-\u1878\u1880-\u18AA\u18B0-\u18F5\u1900-\u191E\u1920-\u192B\u1930-\u1938\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A1B\u1A20-\u1A5E\u1A61-\u1A74\u1AA7\u1B00-\u1B33\u1B35-\u1B43\u1B45-\u1B4B\u1B80-\u1BA9\u1BAC-\u1BAF\u1BBA-\u1BE5\u1BE7-\u1BF1\u1C00-\u1C36\u1C4D-\u1C4F\u1C5A-\u1C7D\u1C80-\u1C88\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u1D00-\u1DBF\u1DE7-\u1DF4\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u24B6-\u24E9\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2DE0-\u2DFF\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FEF\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA674-\uA67B\uA67F-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7BF\uA7C2-\uA7C6\uA7F7-\uA805\uA807-\uA827\uA840-\uA873\uA880-\uA8C3\uA8C5\uA8F2-\uA8F7\uA8FB\uA8FD-\uA8FF\uA90A-\uA92A\uA930-\uA952\uA960-\uA97C\uA980-\uA9B2\uA9B4-\uA9BF\uA9CF\uA9E0-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA36\uAA40-\uAA4D\uAA60-\uAA76\uAA7A-\uAABE\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEF\uAAF2-\uAAF5\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB67\uAB70-\uABEA\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]";let b=escapeRegex(a);return{base:a,trim:new RegExp(`^(${b}\+)*(.*?)(${b}\+)*$`),find:new RegExp(`${a}+`,"gu")}})(),bewilderRegexNonWords=a=>PLPN.trim.exec(a.replace(PLPN.find,`${PLPN.base}+`))[2],escapeStr=(a,b)=>a.length?b?a.replace(/[&<>]/g,a=>({"&":"&","<":"<",">":">"})[a]):a.replace(/[&<>"']/g,a=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[a]):"",promiseLoop=(a,b,c=0,d=2e3)=>new Promise((e,f)=>{let g=a.length;if(!g)return e();const h=i=>{let j,k,l;d&&(k=setTimeout(()=>{clearTimeout(j),l=setTimeout(()=>{f()},250)},d)),j=setTimeout(()=>new Promise((c,d)=>{try{b(a[i]),c()}catch(a){d()}}).then(()=>{if(d&&(clearTimeout(k),l)){if(i{f()}),c)};h(0)}),countChars=a=>(a=a.match(/(?=([^<>]+))\1(?=<|$)/gi),a=a&&a.join(" ")||"",a=a.replace(/\s+/giu," "),+a.length||0),countWords=(a,b)=>{let c,d=bewilderRegexNonWords(escapeRegex(escapeStr(a,!0)));if(!d)return 0;for(let e=0;eb.replace(new RegExp(escapeRegex(escapeStr(a,!0)),"gi"),"/"),countCharacters=a=>new Promise(b=>{setTimeout(()=>{contentCharCount+=countChars(a),b()},5)}),countInflections=a=>{let b=inflections,c=a;return b.length&&b.sort((c,a)=>a.length-c.length),promiseLoop(b,a=>{let b=countWords(a,c);inflectionCount+=b,inflectionCharCount+=a.length*b,c=stripWord(a,c)},5,1e4)},countSynonyms=a=>{let b=synonyms,c=a;return b.length&&b.sort((c,a)=>a.length-c.length),promiseLoop(b,a=>{let b=countWords(a,c);synonymCount+=b,synonymCharCount+=a.length*b,c=stripWord(a,c)},5,1e4)};onmessage=a=>{workerId=a.data.id,reset();let b=a.data.data,c=normalizeSpacing(b.content);regex=b.regex,inflections=b.inflections,synonyms=b.synonyms,c?Promise.all([b.assess.getCharCount&&countCharacters(c),countInflections(c),countSynonyms(c)]).then(()=>{postMessage({inflectionCount,synonymCount,inflectionCharCount,synonymCharCount,contentCharCount})}).catch(a=>{postMessage({workerId,error:a})}):postMessage(void 0)},onerror=(a,b,c,d,e)=>{postMessage({workerId,error:e})}; diff --git a/extensions/essentials/focus/trunk/views/inpost/score-template.php b/extensions/essentials/focus/trunk/views/inpost/score-template.php index bfed68e6..a3db605f 100644 --- a/extensions/essentials/focus/trunk/views/inpost/score-template.php +++ b/extensions/essentials/focus/trunk/views/inpost/score-template.php @@ -17,17 +17,17 @@ $scoring->values = $sub_scores['values']; printf( - '%s %s', + '%s %s', \esc_html__( 'JavaScript is required to perform a subject analysis.', 'the-seo-framework-extension-manager' ), $has_keyword ? \esc_html__( 'Below you find the previous assessments.', 'the-seo-framework-extension-manager' ) : '' ); printf( - '%s', + '%s', ( $has_keyword ? 'style=display:none' : '' ), \esc_html__( 'No keyword has been set, so no analysis can be made.', 'the-seo-framework-extension-manager' ) ); printf( - '%s', + '%s', 'style=display:none', \esc_html__( 'Something went wrong evaluating the subject.', 'the-seo-framework-extension-manager' ) ); diff --git a/extensions/essentials/honeypot/trunk/honeypot.php b/extensions/essentials/honeypot/trunk/honeypot.php index 10af96bf..2e48e338 100644 --- a/extensions/essentials/honeypot/trunk/honeypot.php +++ b/extensions/essentials/honeypot/trunk/honeypot.php @@ -379,7 +379,7 @@ private function output_timer_honeypot() { JSON_FORCE_OBJECT ); - // Can we make this even smaller without losing functionality? + // Can we make this even smaller without losing functionality? Unpacked source: /timer.js $script = <<{let b,c,d=document.getElementsByName(a.n)[0],e=255*(1-Math.random()),f=f=>{void 0===b&&(b=f),c=f-b,c<1e3*(+a.t/+a.s)?(d.value=+a.t+e-c/1e3,g()):d.value=""},g=()=>setTimeout(()=>requestAnimationFrame(f),100+200*Math.random());d&&(d.value=+a.t+e,g())})($php_values); JS; diff --git a/extensions/free/transport/trunk/inc/classes/admin.class.php b/extensions/free/transport/trunk/inc/classes/admin.class.php new file mode 100644 index 00000000..8a1470a2 --- /dev/null +++ b/extensions/free/transport/trunk/inc/classes/admin.class.php @@ -0,0 +1,479 @@ +_has_died() or false === ( \tsf_extension_manager()->_verify_instance( $_instance, $bits[1] ) or \tsf_extension_manager()->_maybe_die() ) ) + return; + +/** + * Transport extension for The SEO Framework + * Copyright (C) 2022 Sybre Waaijer, CyberWire (https://cyberwire.nl/) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * Transports HTML. + */ +// use \TSF_Extension_Manager\HTML as HTML; + +/** + * Require user interface trait. + * + * @since 1.0.0 + */ +\TSF_Extension_Manager\_load_trait( 'core/ui' ); + +/** + * Require extension settings trait. + * + * @since 1.0.0 + */ +\TSF_Extension_Manager\_load_trait( 'extension/options' ); + +/** + * Require extension forms trait. + * + * @since 1.0.0 + */ +// \TSF_Extension_Manager\_load_trait( 'extension/forms' ); + +/** + * Require extension forms trait. + * + * @since 1.0.0 + */ +\TSF_Extension_Manager\_load_trait( 'extension/views' ); + +/** + * Class TSF_Extension_Manager\Extension\Transport\Admin + * + * Holds extension admin page methods. + * + * @since 1.0.0 + * @access private + * @errorval 106xxxx + * @uses TSF_Extension_Manager\Traits + * @final + */ +final class Admin { + use \TSF_Extension_Manager\Construct_Master_Once_Final_Interface, + \TSF_Extension_Manager\UI, + // \TSF_Extension_Manager\Extension_Options, + // \TSF_Extension_Manager\Extension_Forms, + \TSF_Extension_Manager\Extension_Views, + \TSF_Extension_Manager\Error; + + /** + * @since 1.0.0 + * + * @var string The validation nonce name. + */ + protected $ajax_nonce_action; + + /** + * Name of the page hook when the menu is registered. + * + * @since 1.0.0 + * + * @var string Page hook. + */ + protected $transport_menu_page_hook; + + /** + * The extension page ID/slug. + * + * @since 1.0.0 + * + * @var string Page ID/Slug + */ + protected $transport_page_slug; + + /** + * Constructor, initializes WordPress actions. + * + * @since 1.0.0 + */ + private function construct() { + + /** + * @see trait TSF_Extension_Manager\Extension_Views + */ + $this->view_location_base = TSFEM_E_TRANSPORT_DIR_PATH . 'views' . DIRECTORY_SEPARATOR; + + $this->ajax_nonce_action = 'tsfem_e_transport_ajax'; + + $this->importers = [ + 'WordPress_SEO' => [ + 'title' => 'Yoost SEO', + 'importers' => [ + 'settings' => false, // Let's keep this at false, for now. Perhaps we want to move the homepage stuff, but that's tricky. + 'postmeta' => [ + 'supports' => [ + 'title', + 'description', + 'canonical_url', + 'noindex', + 'nofollow', + 'noarchive', + 'og_title', + 'og_description', + 'twitter_title', + 'twitter_description', + 'og_image', + 'article_type', + ], + 'transform' => [ /* "Transformed fields cannot be recovered without a backup" */ + 'title', + 'description', + 'noindex', + 'nofollow', + 'noarchive', + 'og_title', + 'og_description', + 'twitter_title', + 'twitter_description', + ], + ], + 'termmeta' => [ + 'supports' => [ + 'title', + 'description', + 'canonical_url', + 'noindex', + 'nofollow', + 'noarchive', + 'og_title', + 'og_description', + 'twitter_title', + 'twitter_description', + 'og_image', + ], + 'transform' => [ /* "Transformed fields cannot be recovered without a backup" */ + 'title', + 'description', + 'noindex', + 'nofollow', + 'noarchive', + 'og_title', + 'og_description', + 'twitter_title', + 'twitter_description', + ], + ], + ], + ], + // TODO + // 'SEO_By_Rank_Math' => [ + // 'title' => 'Rank Math SEO', + // ], + // 'WP_SEOPress' => [ + // 'title' => 'SEOPress', + // ], + // 'All_In_One_SEO_Pack' => [ + // 'title' => 'All In One SEO', + // ], + ]; + + $this->transport_page_slug = 'theseoframework-transport'; + + /** + * Set error notice option. + * + * @see trait TSF_Extension_Manager\Error + */ + $this->error_notice_option = 'tsfem_e_transport_error_notice_option'; + + // Nothing to do here... + if ( \tsf()->is_headless['settings'] ) return; + + // Initialize menu links + \add_action( 'admin_menu', [ $this, '_init_menu' ] ); + + // Initialize Transport page actions. + \add_action( 'admin_init', [ $this, '_load_transport_admin_actions' ] ); + + // Update POST listener. + \add_action( 'wp_ajax_tsfem_e_transport', [ $this, '_wp_ajax_transport' ] ); + } + + /** + * Initializes extension menu. + * + * @since 1.0.0 + * @uses \TSF_Extension_Manager\can_do_extension_settings() + * @access private + */ + public function _init_menu() { + if ( \TSF_Extension_Manager\can_do_extension_settings() ) + \add_action( 'admin_menu', [ $this, '_add_menu_link' ], 100 ); + } + + /** + * Adds menu link for transport, when possible, underneath The SEO Framework + * SEO settings. + * + * @since 1.0.0 + * @uses \tsf()->seo_settings_page_slug. + * @access private + */ + public function _add_menu_link() { + $this->transport_menu_page_hook = \add_submenu_page( + \tsf()->seo_settings_page_slug, // parent_slug + 'Transport', // page_title + 'Transport', // menu_title + TSF_EXTENSION_MANAGER_EXTENSION_ADMIN_ROLE, + $this->transport_page_slug, // menu_slug + [ $this, '_init_transport_page' ] // callback + ); + } + + /** + * Hooks admin actions into the TSF Extension Manager pagehook. + * + * @since 1.0.0 + * @uses $this->transport_menu_page_hook variable. + * @access private + */ + public function _load_transport_admin_actions() { + \add_action( "load-{$this->transport_menu_page_hook}", [ $this, '_do_transport_admin_actions' ] ); + } + + /** + * Handles Transport AJAX POST requests. + * + * @since 1.0.0 + * @access private + * + * @return void If nonce failed. + */ + public function _wp_ajax_transport() { + + if ( ! \wp_doing_ajax() ) exit; + + if ( ! \TSF_Extension_Manager\can_do_extension_settings() || ! \check_ajax_referer( $this->ajax_nonce_action, 'nonce', false ) ) + \tsf_extension_manager()->send_json( [ 'results' => $this->get_ajax_notice( false, 1069001 ) ], 'failure' ); // nice + + switch ( $_REQUEST['handle'] ?? null ) : + case 'import': + ( new Handler )->_import( $this->importers ); + break; + + default: + \tsf_extension_manager()->send_json( [ 'results' => $this->get_ajax_notice( false, 1060106 ) ], 'failure' ); + break; + endswitch; + } + /** + * Initializes user interface styles, scripts and footer. + * + * @since 1.0.0 + */ + protected function init_tsfem_ui() { + + /** + * Set UI hook. + * + * @see trait TSF_Extension_Manager\UI + */ + $this->ui_hook = $this->transport_menu_page_hook; + + \add_action( 'tsfem_before_enqueue_scripts', [ $this, '_register_transport_scripts' ] ); + + $this->init_ui(); + } + + /** + * Registers default Transport admin scripts. + * Also registers TSF scripts, for TT (tooltip) support. + * + * @since 1.0.0 + * @access private + * @internal + * + * @param string $scripts The scripts builder class name. + */ + public function _register_transport_scripts( $scripts ) { + + if ( \TSF_Extension_Manager\has_run( __METHOD__ ) ) return; + + $scripts::register( [ + // [ + // 'id' => 'tsfem-transport', + // 'type' => 'css', + // 'deps' => [ 'tsf-tt', 'tsfem-ui' ], + // 'autoload' => true, + // 'hasrtl' => true, + // 'name' => 'tsfem-transport', + // 'base' => TSFEM_E_TRANSPORT_DIR_URL . 'lib/css/', + // 'ver' => TSFEM_E_TRANSPORT_VERSION, + // 'inline' => null, + // ], + [ + 'id' => 'tsfem-transport', + 'type' => 'js', + 'deps' => [ 'tsf-tt', 'tsfem-ui' ], + 'autoload' => true, + 'name' => 'tsfem-transport', + 'base' => TSFEM_E_TRANSPORT_DIR_URL . 'lib/js/', + 'ver' => TSFEM_E_TRANSPORT_VERSION, + 'l10n' => [ + 'name' => 'tsfem_e_transportL10n', + 'data' => [ + // This won't ever run when the user can't. But, sanity. + 'nonce' => \TSF_Extension_Manager\can_do_extension_settings() ? \wp_create_nonce( $this->ajax_nonce_action ) : '', + 'i18n' => [ + 'optionNames' => [ + 'settings' => \esc_html__( 'SEO Settings', 'the-seo-framework-extension-manager' ), + 'postmeta' => \esc_html__( 'Post Metadata', 'the-seo-framework-extension-manager' ), + 'termmeta' => \esc_html__( 'Term Metadata', 'the-seo-framework-extension-manager' ), + ], + 'logMessages' => [ + /* translators: %s = plugin name, such as Yoost SEO */ + 'requestImport' => \esc_html__( 'Request importing for %s…', 'the-seo-framework-extension-manager' ), + 'unknownError' => \esc_html__( 'Unknown error', 'the-seo-framework-extension-manager' ), + 'unknownErrorFull' => \esc_html__( 'An unknown error occured. Please refresh this page and try again.', 'the-seo-framework-extension-manager' ), + ], + ], + ], + ], + ], + ] ); + } + + /** + * Hooks admin actions into the TSF Extension Manager pagehook. + * Early enough for admin_notices and admin_head :). + * + * @since 1.0.0 + * @access private + * + * @return bool True on actions loaded, false on second load. + */ + public function _do_transport_admin_actions() { + + if ( false === $this->is_transport_page() ) + return false; + + if ( \TSF_Extension_Manager\has_run( __METHOD__ ) ) + return false; + + /** + * Initialize user interface. + * + * @see trait TSF_Extension_Manager\UI + */ + $this->init_tsfem_ui(); + + /** + * Initialize error interface. + * + * @see trait TSF_Extension_Manager\Error + */ + $this->init_errors(); + + // Add something special. + \add_action( 'admin_head', [ $this, '_output_theme_color_meta' ], 0 ); + + return true; + } + /** + * Determines whether we're on the transport overview page. + * + * @since 1.0.0 + * + * @return bool + */ + public function is_transport_page() { + // Don't load from $_GET request. + return \The_SEO_Framework\memo( \tsf()->is_menu_page( $this->transport_menu_page_hook ) ); + } + + /** + * Initializes the admin page output. + * + * @since 1.0.0 + * @access private + */ + public function _init_transport_page() { + \add_action( 'tsfem_header', [ $this, '_output_transport_header' ] ); + \add_action( 'tsfem_content', [ $this, '_output_transport_content' ] ); + \add_action( 'tsfem_footer', [ $this, '_output_transport_footer' ] ); + + $this->ui_wrap( 'panes' ); + } + + /** + * Outputs transport header. + * + * @since 1.1.0 + * @access private + */ + public function _output_transport_header() { + $this->get_view( 'layout/general/top' ); + } + + /** + * Outputs transport content. + * + * @since 1.1.0 + * @access private + */ + public function _output_transport_content() { + $this->get_view( 'layout/pages/transport' ); + } + + /** + * Outputs transport footer. + * + * @since 1.1.0 + * @access private + */ + public function _output_transport_footer() { + $this->get_view( 'layout/general/footer' ); + } + + /** + * Outputs theme color meta tag for Vivaldi and mobile browsers. + * Does not always work. So many browser bugs... It's just fancy. + * + * @since 1.0.0 + * @access private + */ + public function _output_theme_color_meta() { + $this->get_view( 'layout/general/meta' ); + } + + /** + * Outpouts importer pane. + * + * @since 1.0.0 + */ + public function _importer_overview() { + $this->get_view( 'layout/panes/importer' ); + } + + /** + * Outputs logger pane. + * + * @since 1.0.0 + */ + public function _logger_overview() { + $this->get_view( 'layout/panes/logger' ); + } +} diff --git a/extensions/free/transport/trunk/inc/classes/handler.class.php b/extensions/free/transport/trunk/inc/classes/handler.class.php new file mode 100644 index 00000000..fbb85cfa --- /dev/null +++ b/extensions/free/transport/trunk/inc/classes/handler.class.php @@ -0,0 +1,373 @@ +_has_died() or false === ( \tsf_extension_manager()->_verify_instance( $_instance, $bits[1] ) or \tsf_extension_manager()->_maybe_die() ) ) + return; + +/** + * Transport extension for The SEO Framework + * Copyright (C) 2022 Sybre Waaijer, CyberWire (https://cyberwire.nl/) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +// phpcs:disable, WordPress.Security.NonceVerification -- This file expects that to have been done. + +/** + * Class TSF_Extension_Manager\Extension\Transport\Handler + * + * Handles callbacks for Transport. + * + * @since 1.0.0 + * @access private + * @errorval 106xxxx + * @uses TSF_Extension_Manager\Traits + * @final + */ +final class Handler { + use \TSF_Extension_Manager\Error; + + /** + * @since 1.0.0 + * @var string The transporter lock key (as stored in the database). + */ + const LOCK_SETTING = 'tsfem_e_transporter.lock'; + + /** + * @since 1.0.0 + * @var array A map of log IDs. + */ + const LOG_ID = [ + 'import' => 'import', + ]; + + /** + * Stops importing request/stream. + * + * @since 1.0.0 + * + * @TODO add better comment... this is private scope, so eh. + * @param array $args Data containing reason et al. + */ + private function _halt_server( $args ) { + if ( $args['server']->is_streaming() ) { + $args['server']->send( $args['poll_data'], $args['logger_uid'], $args['event'] ); + $args['server']->flush(); + exit; + } else { + \tsf_extension_manager()->send_json( $args['poll_data'], $args['type'] ); + } + } + + /** + * Handles Transport AJAX POST requests. + * + * @since 1.0.0 + * @access private + * + * @param array $supported_importers A map of supported importers. + * @return void If nonce failed. + */ + public function _import( $supported_importers ) { + + server : { + $logger_uid = uniqid( static::LOG_ID['import'], true ); + + $store = new Logger\Store( $logger_uid ); + $server = new Logger\Server; + + $streaming = $server->start(); + } + + verify: { + parse_str( $_REQUEST['data'] ?? '', $import_settings ); + + $import_settings = $import_settings['tsfem-e-transport-importer'] ?? null; + + if ( ! $import_settings ) + $this->_halt_server( [ + 'server' => $server, + 'poll_data' => [ 'results' => $this->get_ajax_notice( false, 1060200 ) ], + 'logger_uid' => $logger_uid, + 'event' => 'tsfem-e-transport-failure', + 'type' => 'failure', + ] ); + + if ( ! isset( $supported_importers[ $import_settings['choosePlugin'] ?? '' ] ) ) + $this->_halt_server( [ + 'server' => $server, + 'poll_data' => [ 'results' => $this->get_ajax_notice( false, 1060201 ) ], + 'logger_uid' => $logger_uid, + 'event' => 'tsfem-e-transport-failure', + 'type' => 'failure', + ] ); + } + + prepare : { + $timeout = 5 * MINUTE_IN_SECONDS; + + if ( ! $this->lock_transport( $timeout ) ) + $this->_halt_server( [ + 'server' => $server, + 'poll_data' => [ 'results' => $this->get_ajax_notice( false, 1060202 ) ], + 'logger_uid' => $logger_uid, + 'event' => 'tsfem-e-transport-failure', + 'type' => 'failure', + ] ); + + // Register this AFTER the lock is set. Otherwise, it may clear the lock in another thread. + register_shutdown_function( [ $this, 'release_transport_lock' ] ); + + \wp_raise_memory_limit( 'tsfem_e_transport_import' ); + + $ini_max_execution_time = (int) ini_get( 'max_execution_time' ); + + if ( 0 !== $ini_max_execution_time ) + set_time_limit( max( $ini_max_execution_time, $timeout ) ); + + $time_limit = ini_get( 'max_input_time' ); + $time_limit = $time_limit < 1 ? ini_get( 'max_execution_time' ) : $time_limit; + $time_limit = $time_limit < 1 ? $timeout : $time_limit; + + // Add current time to start rolling, 5 seconds penalty for startup/shutdown time (allows graceful shutdown). + $time_limit += time() - 5; + } + + // TODO gauge memory usage and timeouts? -> We should! + // TODO start timer + memory usage + // TODO keep track of timer (timeout) + memory usage and kill if exceed. + try { + if ( \in_array( 'postmeta', $import_settings['selectType'], true ) ) : + $_class = __NAMESPACE__ . "\\Importers\\PostMeta\\{$import_settings['choosePlugin']}"; + + foreach ( ( new $_class )->import() as $handle => $data ) : + switch ( $handle ) : + case 'currentPostId': + [ $post_id, $total_posts, $post_iterator ] = $data; + $streaming and $store->store( + sprintf( + /* translators: 1 = post number, 2 = totalposts, 3 = post ID */ + \esc_html__( 'Importing post %1$d of %2$d. (ID: %3$d)', 'the-seo-framework-extension-manager' ), + $post_iterator, + $total_posts, + $post_id + ) + ); + break; + case 'results': + [ $results, $actions, $post_id, $is_lastpost ] = $data; + if ( empty( $results['updated'] ) ) { + if ( $actions['transport'] ) { + $streaming and $store->store( + sprintf( + /* translators: %d = post ID */ + \esc_html__( 'Post ID %d failed import.', 'the-seo-framework-extension-manager' ), + $post_id + ) + ); + } else { + $streaming and $store->store( + sprintf( + /* translators: %d = post ID */ + \esc_html__( 'Skipped import for Post ID %d.', 'the-seo-framework-extension-manager' ), + $post_id + ) + ); + } + } else { + if ( $actions['transformed'] ) { + $streaming and $store->store( + sprintf( + /* translators: %d = post ID */ + \esc_html__( 'Post ID %d transformed succesfully.', 'the-seo-framework-extension-manager' ), + $post_id + ) + ); + } else { + $streaming and $store->store( + sprintf( + /* translators: %d = post ID */ + \esc_html__( 'Post ID %d imported succesfully.', 'the-seo-framework-extension-manager' ), + $post_id + ) + ); + } + } + if ( $is_lastpost ) { + $store->store( '===============' ); + } else { + $store->store( '---------------' ); + } + break; + + // These below are hit less often, thus later in the switch. + case 'nowConverting': + [ $from_index, $to_index, $from_database, $to_database ] = $data; + $streaming and $store->store( + vsprintf( + /* translators: 1,2 = database location. */ + \esc_html__( 'Starting import from "%1$s" to "%2$s".', 'the-seo-framework-extension-manager' ), + [ + \esc_html( $from_index ), + \esc_html( $to_index ), + ] + ) + ); + break; + case 'foundPosts': + [ $total_posts, $post_ids ] = $data; + $streaming and $store->store( + sprintf( + \esc_html( + /* translators: %d = number of posts found. */ + \_n( + 'Found %d post to import.', + 'Found %d posts to import.', + $total_posts, + 'the-seo-framework-extension-manager' + ) + ), + $total_posts + ) + ); + if ( $total_posts ) { + $store->store( '= = = = = = = =' ); + } else { + $store->store( '===============' ); + } + break; + default: + break; + endswitch; + + // TODO FIXME: This needs to output after every post, not yield. + if ( $time_limit < time() ) { + $this->release_transport_lock(); + $this->_halt_server( [ + 'server' => $server, + 'poll_data' => [ + 'results' => $this->get_ajax_notice( false, 1060203 ), + 'logMsg' => $streaming + ? \esc_html__( 'Transporting time limit reached. Automatically restarting (total numbers might decrease)…', 'the-seo-framework-extension-manager' ) + : \esc_html__( 'Transporting time limit reached. Please try again to resume.', 'the-seo-framework-extension-manager' ), + ], + 'logger_uid' => $logger_uid, + 'event' => 'tsfem-e-transport-timeout', + 'type' => 'failure', + ] ); + } + $streaming = $server->poll( $store ); + endforeach; + endif; + // if ( \in_array( 'termmeta', $import_settings['selectType'], true ) ) { + // $_class = __NAMESPACE__ . "\\Importers\\TermMeta\\{$import_settings['choosePlugin']}"; + // $importer = new $_class; + // } + } catch ( \Exception $e ) { + + $server->poll( $store ); + $this->release_transport_lock(); + + $this->_halt_server( [ + 'server' => $server, + 'poll_data' => [ + 'results' => $this->get_ajax_notice( false, 1060204 ), + 'logMsg' => + sprintf( + ( $streaming + ? \esc_html__( 'Server stopped execution. Reason: %s. Automatically restarting (total numbers might decrease)…', 'the-seo-framework-extension-manager' ) + : \esc_html__( 'Server stopped execution: Reason: %s. Please try again to resume.', 'the-seo-framework-extension-manager' ) + ), + $e->getMessage() ?: 'undefined' + ) + ], + 'logger_uid' => $logger_uid, + 'event' => 'tsfem-e-transport-crash', + 'type' => 'failure', + ] ); + } + + $server->poll( $store ); + $this->release_transport_lock(); + + $this->_halt_server( [ + 'server' => $server, + 'poll_data' => [ + 'results' => $this->get_ajax_notice( true, 1060205 ), + 'logMsg' => \esc_html__( 'Completed transport.', 'the-seo-framework-extension-manager' ), + ], + 'logger_uid' => $logger_uid, + 'event' => 'tsfem-e-transport-done', + 'type' => 'success', + ] ); + exit; + } + + /** + * Locks the transporter. + * + * @since 1.0.0 + * + * @param int $release_timeout The time (in seconds) a lock should regarded as valid. + * @return bool False if already locked, true if new lock is placed. + */ + protected function lock_transport( $release_timeout = 60 ) { + global $wpdb; + + $lock_result = $wpdb->query( + $wpdb->prepare( + "INSERT IGNORE INTO `$wpdb->options` ( `option_name`, `option_value`, `autoload` ) VALUES (%s, %s, 'no') /* LOCK */", + static::LOCK_SETTING, + time() + ) + ); + + if ( ! $lock_result ) { + $lock_result = \get_option( static::LOCK_SETTING ); + + // If a lock couldn't be created, and there isn't a lock, bail. + if ( ! $lock_result ) + return false; + + // Check to see if the lock is still valid. If it is, bail. + if ( $lock_result > ( time() - $release_timeout ) ) + return false; + + // There must exist an expired lock, clear it... + $this->release_transport_lock(); + + // ...and re-gain it. + return $this->lock_transport( $release_timeout ); + } + + // Update the lock, as by this point we've definitely got a lock, just need to fire the actions. + \update_option( static::LOCK_SETTING, time() ); + + return true; + } + + /** + * Releases transporter lock set in lock_transport(). + * + * @since 1.0.0 + */ + public function release_transport_lock() { + \delete_option( static::LOCK_SETTING ); + } +} diff --git a/extensions/free/transport/trunk/inc/classes/importers/postmeta/core.class.php b/extensions/free/transport/trunk/inc/classes/importers/postmeta/core.class.php new file mode 100644 index 00000000..659b40e8 --- /dev/null +++ b/extensions/free/transport/trunk/inc/classes/importers/postmeta/core.class.php @@ -0,0 +1,217 @@ +postmeta + * (?string) 'to_database' The database to set the data to, defaults to $wpdb->postmeta + * ] + * ] + */ + protected $conversion_sets; + + /** + * Sets up class, mainly required variables. + * + * @since 1.0.0 + */ + protected function construct() { + $this->setup_vars(); + } + + /** + * Sets up variables. + * + * @since 1.0.0 + * @abstract + */ + abstract protected function setup_vars(); + + /** + * Walks the postdata. + * + * @since 1.0.0 + * @generator + * @abstract Feel free to overwrite this method. + * @global \wpdb $wpdb WordPress Database handler. + * @throws \Exception With WP_DEBUG enabled, exceptions are sent to the user. + * + * @yield string $key => mixed $data On sporadic intervals that could be pinged. + * @return array Results the transformation results. + */ + final public function import() { + global $wpdb; + + // Assume that we don't need to keep track of how much data is transported? + // Assume we do not need to transport in batches? -> Tackle when we need to? -> Tell user to try again, and again...? + // Let analyser find that "hey, are you sure you want to transport _so_much_data_?" + // Test if large_network()? + + $_globals_postmeta = $wpdb->postmeta; + + foreach ( $this->conversion_sets as $conversion_set ) : + [ $from_index, $to_index, $transformer, $from_database, $to_database ] = array_pad( $conversion_set, 5, null ); + + // Sanity is a virtue. + $from_database = \esc_sql( $from_database ) ?: $_globals_postmeta; + $to_database = \esc_sql( $to_database ) ?: $_globals_postmeta; + + yield 'nowConverting' => [ $from_index, $to_index, $from_database, $to_database ]; + + $post_ids = $wpdb->get_col( $wpdb->prepare( + // phpcs:ignore, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $from_database is escaped. + "SELECT post_id FROM `$from_database` WHERE meta_key = %s", + $from_index + ) ) ?: []; + + if ( WP_DEBUG && $wpdb->last_error ) throw new \Exception( $wpdb->last_error ); + + $total_posts = \count( $post_ids ); + yield 'foundPosts' => [ $total_posts, $post_ids ]; + + $post_iterator = 1; + + foreach ( $post_ids as $post_id ) : + + $results = []; + + yield 'currentPostId' => [ $post_id, $total_posts, $post_iterator++ ]; + + $identical_index = $from_index === $to_index && $from_database === $to_database; + + $actions = [ + 'transformed' => false, + 'transform' => (bool) $transformer, + // If the data goes nowhere there's no need to delete nor transport. + 'transport' => ! $identical_index, + 'delete' => ! $identical_index, + ]; + + $old_value = null; + $new_value = $wpdb->get_var( $wpdb->prepare( + // phpcs:ignore, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $to_database is escaped. + "SELECT meta_value FROM `$to_database` WHERE post_id = %d AND meta_key = %s", + $post_id, + $to_index + ) ); + if ( WP_DEBUG && $wpdb->last_error ) throw new \Exception( $wpdb->last_error ); + + if ( ! \is_null( $new_value ) ) { + // If new data exists, don't overwrite with old. + $actions['transport'] = false; + // If new data exists and index is the same, still try to transform. Otherwise, forgo. + $actions['transform'] = $actions['transform'] && $identical_index; + } else { + $old_value = $wpdb->get_var( $wpdb->prepare( + // phpcs:ignore, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $from_database is escaped. + "SELECT meta_value FROM `$from_database` WHERE post_id = %d AND meta_key = %s", + $post_id, + $from_index + ) ); + if ( WP_DEBUG && $wpdb->last_error ) throw new \Exception( $wpdb->last_error ); + } + + if ( $actions['transform'] ) { + $old_value = $new_value ?? $old_value; + $new_value = \call_user_func_array( $transformer, [ $from_index, $from_database, $old_value ] ); + + $actions['transformed'] = $old_value !== $new_value; + } + + if ( $actions['transport'] ) { + if ( $to_database === $from_database ) { + if ( $actions['transformed'] ) { + $results['updated'] = $wpdb->update( + $to_database, + [ + 'meta_key' => $to_index, + 'meta_value' => $new_value, + ], + [ + 'post_id' => $post_id, + 'meta_key' => $from_index, + ] + ); + if ( WP_DEBUG && $wpdb->last_error ) throw new \Exception( $wpdb->last_error ); + } else { + $results['updated'] = $wpdb->update( + $to_database, + [ 'meta_key' => $to_index ], + [ + 'post_id' => $post_id, + 'meta_key' => $from_index, + ] + ); + if ( WP_DEBUG && $wpdb->last_error ) throw new \Exception( $wpdb->last_error ); + } + // Simply moved, mustn't be deleted. Should've already been false. Just in case: + $actions['delete'] = false; + } else { + // We don't care whether it's transformed here. + $results['updated'] = $wpdb->insert( + $to_database, + [ + 'post_id' => $post_id, + 'meta_key' => $to_index, + 'meta_value' => $new_value, + ] + ); + if ( WP_DEBUG && $wpdb->last_error ) throw new \Exception( $wpdb->last_error ); + } + } elseif ( $actions['transformed'] ) { + $results['updated'] = $wpdb->update( + $to_database, + [ 'meta_value' => $new_value ], + [ + 'post_id' => $post_id, + 'meta_key' => $to_index, + ] + ); + if ( WP_DEBUG && $wpdb->last_error ) throw new \Exception( $wpdb->last_error ); + // Altered, mustn't be deleted. Should've already been false. Just in case: + $actions['delete'] = false; + } + + if ( $actions['delete'] ) { + $results['deleted'] = $wpdb->delete( + $from_database, + [ + 'post_id' => $post_id, + 'meta_key' => $from_index, + ] + ); + if ( WP_DEBUG && $wpdb->last_error ) throw new \Exception( $wpdb->last_error ); + } + + $is_lastpost = $post_iterator === $total_posts; + yield 'results' => [ $results, $actions, $post_id, $is_lastpost ]; + + // This can bust cache of caching plugins. Intended: Update the post! + \clean_post_cache( $post_id ); + endforeach; + endforeach; + + return true; + } +} diff --git a/extensions/free/transport/trunk/inc/classes/importers/postmeta/index.php b/extensions/free/transport/trunk/inc/classes/importers/postmeta/index.php new file mode 100644 index 00000000..e69de29b diff --git a/extensions/free/transport/trunk/inc/classes/importers/postmeta/wordpress-seo.class.php b/extensions/free/transport/trunk/inc/classes/importers/postmeta/wordpress-seo.class.php new file mode 100644 index 00000000..6e269bb4 --- /dev/null +++ b/extensions/free/transport/trunk/inc/classes/importers/postmeta/wordpress-seo.class.php @@ -0,0 +1,117 @@ +syntax_key = '%%'; + + // 'supports' => [ + // 'title', + // 'description', + // 'canonical_url', + // 'noindex', + // 'nofollow', + // 'noarchive', + // 'og_title', + // 'og_description', + // 'twitter_title', + // 'twitter_description', + // 'og_image', + // 'article_type', + // ], + + // 'transform' => [ /* "Transformed fields cannot be recovered without a backup" */ + // 'title', + // 'description', + // 'noindex', + // 'nofollow', + // 'noarchive', + // 'og_title', + // 'og_description', + // 'twitter_title', + // 'twitter_description', + // ], + + /** + * $from_index, + * $to_index, + * $transformer, + * $from_database, + * $to_database, + */ + $this->conversion_sets = [ + [ + '_yoast_wpseo_title', + '_genesis_title', + null, + // $wpdb->postmeta, + // $wpdb->postmeta, + ], + [ + '_yoast_wpseo_metadesc', + '_genesis_description', + null, + // $wpdb->postmeta, + // $wpdb->postmeta, + ], + [ + '_yoast_wpseo_meta-robots-noindex', + '_genesis_noindex', + null, + // $wpdb->postmeta, + // $wpdb->postmeta, + ], + [ + '_yoast_wpseo_meta-robots-nofollow', + '_genesis_nofollow', + null, + // $wpdb->postmeta, + // $wpdb->postmeta, + ], + // [ // Doesn't exist? + // '_yoast_wpseo_meta-robots-noarchive', + // '_genesis_noarchive', + // null, + // // $wpdb->postmeta, + // // $wpdb->postmeta, + // ], + [ + '_yoast_wpseo_canonical', + '_genesis_canonical_uri', + null, + // $wpdb->postmeta, + // $wpdb->postmeta, + ], + [ + '_yoast_wpseo_redirect', + 'redirect', + null, + // $wpdb->postmeta, + // $wpdb->postmeta, + ], + ]; + } +} diff --git a/extensions/free/transport/trunk/inc/classes/index.php b/extensions/free/transport/trunk/inc/classes/index.php new file mode 100644 index 00000000..e69de29b diff --git a/extensions/free/transport/trunk/inc/classes/logger/index.php b/extensions/free/transport/trunk/inc/classes/logger/index.php new file mode 100644 index 00000000..e69de29b diff --git a/extensions/free/transport/trunk/inc/classes/logger/server.class.php b/extensions/free/transport/trunk/inc/classes/logger/server.class.php new file mode 100644 index 00000000..e518c6b7 --- /dev/null +++ b/extensions/free/transport/trunk/inc/classes/logger/server.class.php @@ -0,0 +1,266 @@ + + * @since 1.0.0 + */ + private function construct() { + + foreach ( + preg_split( '/\s*,\s*/', $this->get_headers()['accept'] ?? '' ) + as $mime + ) { + if ( 0 !== preg_match( '/^(\*\/\*|text\/event-stream;?)/', $mime ) ) { + static::$supports_stream = true; + break; + } + } + + // Debug: var_dump() + // static::$supports_stream = false; + } + + /** + * Whether we're streaming + * + * @since 1.0.0 + * + * @return bool True if streaming, false otherwise. + */ + public function is_streaming() { + return static::$supports_stream && static::$streaming; + } + + /** + * Initializes stream by setting up headers and starts output buffer. + * + * @since 1.0.0 + * + * @param int $interval The polling interval in seconds. + * @return bool false on failure, true otherwise. + */ + public function start( $interval = 3000 ) { + + if ( ! static::$supports_stream ) return false; + + \tsf()->clean_response_header(); + + // Disable. With it enabled it would otherwise cause double-output. + ob_implicit_flush( false ); + + foreach ( [ + 'Content-Type' => 'text/event-stream', + 'Cache-Control' => 'no-store, no-cache, no-transform', // Discourages all caching. + 'X-Accel-Buffering' => 'no', // Required for Comet/HTTPStreaming on NGINX. + 'Vary' => '*', + ] as $header => $value ) + header( "$header: $value" ); + + static::$streaming = true; + + ob_start(); + + // phpcs:ignore, WordPress.Security.EscapeOutput -- unsigned int. + printf( "retry: %u\n\n", $interval ); + + static::$lastpadstamp = time(); + + return $this->flush(); + } + + /** + * Sends stream from store in polled chunks. + * Cleans everything sent before streaming. + * + * @since 1.0.0 + * + * @param Store $store The store to poll. + * @return bool false on failure, true otherwise. + */ + public function poll( $store ) { + + if ( ! static::$supports_stream ) return false; + if ( connection_aborted() ) return false; + + while ( ob_get_length() ) ob_end_clean(); + + foreach ( $store->get_flush_store() as $data ) + $this->send( $data, $store->id ); + + return $this->flush(); + } + + /** + * Flushes buffer. + * + * @since 1.0.0 + * + * @return bool true on success, false on failure. + */ + public function flush() { + + if ( ! static::$supports_stream ) return false; + if ( connection_aborted() ) return false; + + static::$buffersize += ob_get_length(); + + if ( static::$buffersize ) { + if ( static::$buffersize > static::$chunksize ) { + if ( $forcepad ) { + $this->padchunk(); + static::$buffersize = 0; + } else { + static::$buffersize = static::$buffersize % static::$chunksize; + } + static::$lastpadstamp = time(); + } elseif ( ( static::$lastpadstamp + static::$padinterval ) < time() ) { + $this->padchunk(); + static::$buffersize = 0; + static::$lastpadstamp = time(); + } + } + + ob_flush(); + flush(); + + return true; + } + + /** + * Sends stream. + * + * @since 1.0.0 + * + * @param string|array $data The data to send. Expected to be escaped. + * Strings will be converted to array at index 'content'. + * @param string $id The sender unique ID. + * @param string $event The event to send, which can read via a listener. + */ + public function send( $data, $id, $event = 'tsfem-e-transport-log' ) { + + if ( ! static::$supports_stream ) return false; + + if ( \is_string( $data ) ) + $data = [ 'content' => $data ]; + + printf( "event: %s\nid: %s\n", \esc_html( $event ), \esc_html( $id ) ); + echo 'data: ', json_encode( $data, JSON_FORCE_OBJECT ), "\n\n"; + } + + /** + * Pads the response chunk buffer. + * + * @since 1.0.0 + */ + private function padchunk() { + + $length = static::$chunksize - ( static::$buffersize % static::$chunksize ); + + if ( $length <= 2 ) { + // phpcs:ignore, WordPress.Security.EscapeOutput -- it's just newline..., man, chill. + echo str_repeat( "\n", $length ); + } else { + // phpcs:ignore, WordPress.Security.EscapeOutput -- it's just null..., man, chill. + echo ':' . str_repeat( "\x00", $length - 3 ) . "\n\n"; + } + } + + /** + * Returns a normalized list of headers. + * + * @since 1.0.0 + * @source + * + * @return array Headers. + */ + private function get_headers() { + + $headers = []; + + if ( \function_exists( 'apache_request_headers' ) ) { + foreach ( \apache_request_headers() as $header => $value ) + $headers[ strtolower( $header ) ] = $value; + } else { + if ( isset( $_SERVER['CONTENT_TYPE'] ) ) + $headers['content-type'] = $_SERVER['CONTENT_TYPE']; + + if ( isset( $_SERVER['CONTENT_LENGTH'] ) ) + $headers['content-length'] = $_SERVER['CONTENT_LENGTH']; + + foreach ( $_SERVER as $key => $value ) { + if ( 'HTTP_' === substr( $key, 0, 5 ) ) { + $headers[ strtolower( str_replace( '_', '-', substr( $key, 5 ) ) ) ] + = $value; + } + } + } + + return $headers; + } +} diff --git a/extensions/free/transport/trunk/inc/classes/logger/store.class.php b/extensions/free/transport/trunk/inc/classes/logger/store.class.php new file mode 100644 index 00000000..9270ccfa --- /dev/null +++ b/extensions/free/transport/trunk/inc/classes/logger/store.class.php @@ -0,0 +1,108 @@ +id = $id; + } + + /** + * Stores data. + * + * @since 1.0.0 + * + * @param mixed $data The data to store + */ + public function store( $data ) { + static::$store[ $this->id ][ microtime() ] = $data; + } + + /** + * Retrieves store data. + * + * @since 1.0.0 + * + * @return array The current store. + */ + public function get_store() { + return static::$store[ $this->id ] ?? []; + } + + /** + * Clears current store and returns what's been cleared. + * + * @since 1.0.0 + * + * @return array The current store. + */ + public function get_flush_store() { + $store = $this->get_store(); + $this->clear_store(); + return $store; + } + + /** + * Clears store. + */ + public function clear_store() { + unset( static::$store[ $this->id ] ); + } + + // public function store( $data ) { + // $store = \get_option( TSFEM_E_TRANSPORT_LOGSERVER_STORE, [] ) ?: []; + // $store[ $this->id ][ microtime() ] = $data; + // \update_option( TSFEM_E_TRANSPORT_LOGSERVER_STORE, $store ); + // } + // public function get_store( $after ) { + // $store = \get_option( TSFEM_E_TRANSPORT_LOGSERVER_STORE ) ?? []; + // if ( isset( $store[ $this->id ] ) ) { + // // Clear old data. + // foreach ( $store[ $this->id ] as $microtime => $values ) { + // if ( $microtime < $after ) + // unset( $data[ $this->id ][ $microtime ] ); + // } + // \update_option( TSFEM_E_TRANSPORT_LOGSERVER_STORE, $store ); + // } + // return $store[ $this->id ] ?? null; + // } + // public function clear_store() { + // $store = \get_option( TSFEM_E_TRANSPORT_LOGSERVER_STORE ) ?? []; + // unset( $store[ $this->id ] ); + // \update_option( TSFEM_E_TRANSPORT_LOGSERVER_STORE, $store ); + // } +} diff --git a/extensions/free/transport/trunk/inc/classes/transformers/postmeta/core.class.php b/extensions/free/transport/trunk/inc/classes/transformers/postmeta/core.class.php new file mode 100644 index 00000000..32a084b4 --- /dev/null +++ b/extensions/free/transport/trunk/inc/classes/transformers/postmeta/core.class.php @@ -0,0 +1,68 @@ + [ 'title' => [ '%%date%%' => [ $this => 'post_date' ] ] ] ] + */ + protected $syntax_transformers; + + /** + * Tests if syntax key is found in string. + * + * @since 1.0.0 + * @uses $this->syntax_key + * + * @param string $string The string to test. + * @return bool + */ + abstract protected function has_syntax_key( $string ); + + /** + * Transforms string according to syntax. + * + * @since 1.0.0 + * @uses $this->has_syntax_key() + * @uses $this->syntax_transformers + * + * @param string $string The string to transform. + * @return string The transformed string. + */ + protected function transform( $string, $type ) { + + // if ( $this->has_syntax_key( $string, $type ) ) { + foreach ( $this->syntax_transformers[ $type ] as $syntax => $transformer ) { + $string = \call_user_func( $transformer, $string, $syntax ); + if ( ! $this->has_syntax_key( $string ) ) break; + } + // } + + return $string; + } +} diff --git a/extensions/free/transport/trunk/inc/classes/transformers/postmeta/wordpress-seo-transformer.class.php b/extensions/free/transport/trunk/inc/classes/transformers/postmeta/wordpress-seo-transformer.class.php new file mode 100644 index 00000000..3c763280 --- /dev/null +++ b/extensions/free/transport/trunk/inc/classes/transformers/postmeta/wordpress-seo-transformer.class.php @@ -0,0 +1,44 @@ +syntax_key = '%%'; + } + + /** + * Tests if syntax key is found in string. + * + * @since 1.0.0 + * @uses $this->syntax_key + * + * @param string $string The string to test. + * @return bool + */ + protected function has_syntax_key( $string ) { + return false !== strpos( $string, $this->syntax_key ); + } +} diff --git a/extensions/free/transport/trunk/inc/index.php b/extensions/free/transport/trunk/inc/index.php new file mode 100644 index 00000000..e69de29b diff --git a/extensions/free/transport/trunk/index.php b/extensions/free/transport/trunk/index.php new file mode 100644 index 00000000..e69de29b diff --git a/extensions/free/transport/trunk/lib/css/index.php b/extensions/free/transport/trunk/lib/css/index.php new file mode 100644 index 00000000..e69de29b diff --git a/extensions/free/transport/trunk/lib/index.php b/extensions/free/transport/trunk/lib/index.php new file mode 100644 index 00000000..e69de29b diff --git a/extensions/free/transport/trunk/lib/js/index.php b/extensions/free/transport/trunk/lib/js/index.php new file mode 100644 index 00000000..e69de29b diff --git a/extensions/free/transport/trunk/lib/js/tsfem-transport.js b/extensions/free/transport/trunk/lib/js/tsfem-transport.js new file mode 100644 index 00000000..588ae140 --- /dev/null +++ b/extensions/free/transport/trunk/lib/js/tsfem-transport.js @@ -0,0 +1,385 @@ +/** + * This file holds Import's code for interpreting keywords and their subjects. + * Serve JavaScript as an addition, not as an ends or means. + * Alas, there's no other way here. + * + * @author Sybre Waaijer + * @link + */ + +/** + * Import extension for The SEO Framework + * Copyright (C) 2022 Sybre Waaijer, CyberWire (https://cyberwire.nl/) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +'use strict'; + +/** + * Holds tsfem_e_import values in an object to avoid polluting global namespace. + * + * This is a self-constructed function assigned as an object. + * + * @since 1.0.0 + * + * @constructor + */ +window.tsfem_e_import = function() { + + /** + * @since 1.0.0 + * @access private + * @type {{i18n:{?},nonce:string}|null} l10n The l10n parameters set in PHP to var. + */ + const l10n = tsfem_e_transportL10n; + + const _enableButtons = disable => { + disable ||= false; + [ + 'importer-submit' + ].forEach( buttonName => { + const button = document.getElementById( `tsfem-e-transport-${buttonName}` ); + button.classList.toggle( 'tsfem-button-disabled', disable ); + button.disabled = disable; + } ); + } + const _disableButtons = () => _enableButtons( true ); + + /** + * Visualizes the AJAX response to the user. + * + * @since 1.0.0 + * @access private + * + * @function + * @param {?string} type null|'start' = start loading, 'end' = stop loading. + * @param {number} success 0 = error, 1 = success, 2 = unknown but success. + * @param {string} notice The updated notice. + */ + const _setLoggerLoader = ( type, success, notice ) => { + + const loggerLoaderQuery = '#tsfem-e-transport-log-ajax'; + + switch ( type || 'start' ) { + case 'start': + // Reset ajax loader + tsfem.resetAjaxLoader( loggerLoaderQuery ); + // Set ajax loader. + tsfem.setAjaxLoader( loggerLoaderQuery ); + break; + case 'end': + tsfem.updatedResponse( loggerLoaderQuery, success, notice ); + break; + } + } + + /** + * Starts importing. + * + * @since 1.0.0 + * @access private + * + * @function + */ + const _handleImport = event => { + + const button = event.target; + + if ( button.disabled ) return; + + let buttonLoadingClasses = [ 'tsfem-button-loading' ]; + + button.classList.add( ...buttonLoadingClasses ); + _disableButtons(); + _setLoggerLoader(); + + const formNs = 'tsfem-e-transport-importer'; + const form = document.getElementById( formNs ); + const formData = new FormData( form ); + [ ...formData.keys() ].forEach( name => { + form.querySelectorAll( `[name="[${name}]"]` ).forEach( el => { + el.dataset.handlerDisabled = ! el.disabled; // Mark if already disabled. + el.disable = true; + } ); + } ); + + const handler = 'undefined' !== typeof( EventSource ) ? _handleEventStream : _handlePost; + + handler( + 'import', + formData, + l10n.i18n.logMessages.requestImport.replace( + '%s', + form.querySelector( `[value="${formData.get( `${formNs}[choosePlugin]` )}"]` )?.dataset.title + ), + ).then( successMsg => { + _setLoggerLoader( 'end', 1, successMsg ); + } ).catch( errorMsg => { + _setLoggerLoader( 'end', 0, errorMsg ); + } ).finally( () => { + _enableButtons(); + button.classList.remove( ...buttonLoadingClasses ); + + [ ...formData.keys() ].forEach( name => { + form.querySelectorAll( `[name="${name}"]` ).forEach( el => { + if ( el.dataset.handlerDisabled ) + el.disabled = false; + + delete el.dataset.handlerDisabled; + } ); + } ); + } ); + } + + /** + * Handles AJAX post requests securely. + * + * @since 1.0.0 + * @access private + * + * @param {String} handle The transport action type/handle. + * @param {FormData} formData The formdata to post for handle. + * @param {?String} logStart The starting log text. Expected to be escaped. + * @function + */ + const _handleEventStream = ( handle, formData, logStart ) => new Promise( ( resolve, reject ) => { + + const url = new URL( ajaxurl, new URL( document.baseURI ).origin ); + + url.searchParams.append( 'action', 'tsfem_e_transport' ); + url.searchParams.append( 'handle', handle ); + url.searchParams.append( 'nonce', l10n.nonce ); + url.searchParams.append( 'data', formData && ( new URLSearchParams( [ ...formData.entries() ] ) ).toString() ); + + const SSE = new EventSource( url.href ); + + + SSE.onopen = () => { _log( logStart ) }; + SSE.onerror = event => { + SSE.close(); + _log( l10n.i18n.logMessages.unknownErrorFull ); + reject( l10n.i18n.logMessages.unknownError ); + } + + const extractEventData = data => { + try { + return JSON.parse( data ); + } catch ( error ) { + return void 0; + } + } + const doEventResolve = event => { + SSE.close(); + let data = extractEventData( event.data ); + _log( data?.logMsg ); + resolve( data?.results.notice ); + } + const doEventReject = event => { + SSE.close(); + let data = extractEventData( event.data ); + _log( data?.logMsg ); + reject( data?.results.notice ); + } + + SSE.addEventListener( 'tsfem-e-transport-log', event => { + _log( extractEventData( event.data )?.content ); + } ); + SSE.addEventListener( 'tsfem-e-transport-done', doEventResolve ); + + SSE.addEventListener( 'tsfem-e-transport-failure', doEventReject ); + SSE.addEventListener( 'tsfem-e-transport-crash', doEventReject ); + SSE.addEventListener( 'tsfem-e-transport-timeout', event => { + // Only log, retry automatically. + _log( extractEventData( event.data )?.logMsg ); + } ); + } ); + + /** + * Handles AJAX post requests securely. + * + * @since 1.0.0 + * @access private + * + * @param {String} handle The transport action type/handle. + * @param {FormData} formData The formdata to post for handle. + * @param {?String} logStart The starting log text. Expected to be escaped. + * @function + */ + const _handlePost = ( handle, formData, logStart ) => new Promise( ( resolve, reject ) => { + + _log( logStart ); + + wp.ajax.post( + 'tsfem_e_transport', + { + handle, + nonce: l10n.nonce, + data: formData && ( new URLSearchParams( [ ...formData.entries() ] ) ).toString(), + } + ).done( data => { + resolve( data?.results?.notice ); + _log( data?.logMsg ); + } ).fail( response => { + reject( response.data?.results?.notice ); + _log( response.data?.logMsg ); + } ); + } ); + + const _logger = document.getElementById( 'tsfem-e-transport-logger' ); + /** + * Writes data to logger. + * + * @since 1.0.0 + * @access private + * + * @param {String} message The message to log + * @function + */ + const _log = message => { + if ( _logger && message?.length ) + _logger.innerHTML += '\n∶ ' + tsf.escapeString( tsf.decodeEntities( message ) ); + } + + /** + * Sets up importer select display. + * + * This callback doesn't have its element-fetchers optimized; however, since it is a one-time operation, + * optimization isn't necessary, and can even be counter-productive by hogging memory needlessly. + * + * @since 1.0.0 + * @access private + * + * @function + */ + const _importerSelectPlugin = event => { + + event.preventDefault(); + event.stopPropagation(); + + if ( event.target.dataset?.lastValue === event.target.value ) + return; + + event.target.dataset.lastValue = event.target.value; + + const basename = 'tsfem-e-transport-importer'; + const data = JSON.parse( event.target.selectedOptions[0]?.dataset.importers ); + + const optionsWrap = document.getElementById( `${basename}-options` ); + const supportsTransformationHelp = document.getElementById( `${basename}-supports-transformation-help` ); + const submit = document.getElementById( `${basename}-submit` ); + + optionsWrap.innerHTML = ''; + optionsWrap.style.display = 'none'; + supportsTransformationHelp.style.display = 'none'; + + let hasTransformation = false, + hasOptions = false; + + const handleTypeChange = e => { + // We could "effeciently" keep track of how many items are checked (out of total) + // We do that in ../../..tsfem-form.js, but this doesn't require that level of optimization. + // Query all the fields! All the time! Yay for pushing out code ASAP. This is me not giving a hoot. + // typeSupports.style.display = e.target.checked ? null : 'none'; + const enabledTypes = document.querySelectorAll( `[name^="${basename}\\[selectType\\]"]:checked` ); + hasTransformation = false; + enabledTypes.forEach( el => { + hasTransformation = !! data[ el.value ]?.transform?.length; + } ); + + supportsTransformationHelp.style.display = hasTransformation ? null : 'none'; + + const disableSubmit = ! enabledTypes.length; + submit.disabled = disableSubmit; + submit.classList.toggle( 'tsfem-button-disabled', disableSubmit ); + } + + const populateOptionsTemplate = type => { + const optionsTemplate = document.getElementById( `${basename}-options-template` ).content.cloneNode( true ); + optionsTemplate.querySelector( `.${basename}-selectType-description` ).innerText = l10n.i18n.optionNames[ type ] ?? ''; + + const typeInput = optionsTemplate.querySelector( `[name^="${basename}\\[selectType\\]"]` ); + typeInput.value = type; + + optionsWrap.appendChild( optionsTemplate ); + + typeInput.addEventListener( 'change', handleTypeChange ); + typeInput.dispatchEvent( new Event( 'change' ) ); + + // const supportsTemplate = document.getElementById( `${basename}supports-template` ).content.cloneNode( true ); + // const typeSupports = optionsTemplate.querySelector( `.${basename}selectType-supports` ); + // optionsTemplate.querySelector( `.${basename}-selectType-supports` ).appendChild( supportsTemplate ); + // data[ name ].supports?.forEach( type => { + // hasTransformation = ! data[ type ].transform?.includes( type ); + // // TODO? This ugly. + // /* + // const item = supportsTemplate.querySelector( `.${basename}-support\\[${type}\\]` ); + // if ( item ) { + // item.style.display = null; + + // if ( ! data[ type ].transform?.includes( type ) ) { + // item.querySelector( `.${basename}-transform` ).style.display = 'none'; + // } else { + // hasTransformation = true; + // } + // } + // */ + // } ); + } + + // if ( data.settings ) {} // TODO? FORGO? + if ( data.postmeta ) { + populateOptionsTemplate( 'postmeta' ); + hasOptions = true; + } + if ( data.termmeta ) { + populateOptionsTemplate( 'termmeta' ); + hasOptions = true; + } + + if ( hasOptions ) + optionsWrap.style.display = null; + + // Register this. JS event handler should allow it only once. + submit.addEventListener( 'click', _handleImport ); + } + + /** + * @since 1.0.0 + * @access private + * + * @function + */ + const _prepareUI = () => { + const importSelector = document.getElementById( 'tsfem-e-transport-importer[choosePlugin]' ); + importSelector.addEventListener( 'change', _importerSelectPlugin ); + importSelector.dispatchEvent( new Event( 'change' ) ); + } + + return Object.assign( { + /** + * Initialises all aspects of the scripts. + * You shouldn't call this. + * + * @since 1.0.0 + * @access protected + * + * @function + * @return {undefined} + */ + load: () => { + document.body.addEventListener( 'tsf-onload', _prepareUI ); + } + } ); +}(); +window.tsfem_e_import.load(); diff --git a/extensions/free/transport/trunk/lib/js/tsfem-transport.min.js b/extensions/free/transport/trunk/lib/js/tsfem-transport.min.js new file mode 100644 index 00000000..4cdeef17 --- /dev/null +++ b/extensions/free/transport/trunk/lib/js/tsfem-transport.min.js @@ -0,0 +1 @@ +'use strict';window.tsfem_e_import=function(){const a=tsfem_e_transportL10n,b=a=>{a||=!1,["importer-submit"].forEach(b=>{const c=document.getElementById(`tsfem-e-transport-${b}`);c.classList.toggle("tsfem-button-disabled",a),c.disabled=a})},c=()=>b(!0),d=(a,b,c)=>{const d="#tsfem-e-transport-log-ajax";switch(a||"start"){case"start":tsfem.resetAjaxLoader(d),tsfem.setAjaxLoader(d);break;case"end":tsfem.updatedResponse(d,b,c);}},e=e=>{const h=e.target;if(h.disabled)return;let i=["tsfem-button-loading"];h.classList.add(...i),c(),d();const j="tsfem-e-transport-importer",k=document.getElementById(j),l=new FormData(k);[...l.keys()].forEach(a=>{k.querySelectorAll(`[name="[${a}]"]`).forEach(a=>{a.dataset.handlerDisabled=!a.disabled,a.disable=!0})});const m="undefined"==typeof EventSource?g:f;m("import",l,a.i18n.logMessages.requestImport.replace("%s",k.querySelector(`[value="${l.get(`${j}[choosePlugin]`)}"]`)?.dataset.title)).then(a=>{d("end",1,a)}).catch(a=>{d("end",0,a)}).finally(()=>{b(),h.classList.remove(...i),[...l.keys()].forEach(a=>{k.querySelectorAll(`[name="${a}"]`).forEach(a=>{a.dataset.handlerDisabled&&(a.disabled=!1),delete a.dataset.handlerDisabled})})})},f=(b,c,d)=>new Promise((e,f)=>{const g=new URL(ajaxurl,new URL(document.baseURI).origin);g.searchParams.append("action","tsfem_e_transport"),g.searchParams.append("handle",b),g.searchParams.append("nonce",a.nonce),g.searchParams.append("data",c&&new URLSearchParams([...c.entries()]).toString());const h=new EventSource(g.href);h.onopen=()=>{i(d)},h.onerror=()=>{h.close(),i(a.i18n.logMessages.unknownErrorFull),f(a.i18n.logMessages.unknownError)};const j=a=>{try{return JSON.parse(a)}catch(a){}},k=a=>{h.close();let b=j(a.data);i(b?.logMsg),f(b?.results.notice)};h.addEventListener("tsfem-e-transport-log",a=>{i(j(a.data)?.content)}),h.addEventListener("tsfem-e-transport-done",a=>{h.close();let b=j(a.data);i(b?.logMsg),e(b?.results.notice)}),h.addEventListener("tsfem-e-transport-failure",k),h.addEventListener("tsfem-e-transport-crash",k),h.addEventListener("tsfem-e-transport-timeout",a=>{i(j(a.data)?.logMsg)})}),g=(b,c,d)=>new Promise((e,f)=>{i(d),wp.ajax.post("tsfem_e_transport",{handle:b,nonce:a.nonce,data:c&&new URLSearchParams([...c.entries()]).toString()}).done(a=>{e(a?.results?.notice),i(a?.logMsg)}).fail(a=>{f(a.data?.results?.notice),i(a.data?.logMsg)})}),h=document.getElementById("tsfem-e-transport-logger"),i=a=>{h&&a?.length&&(h.innerHTML+="\n∶ "+tsf.escapeString(tsf.decodeEntities(a)))},j=b=>{if(b.preventDefault(),b.stopPropagation(),b.target.dataset?.lastValue===b.target.value)return;b.target.dataset.lastValue=b.target.value;const c="tsfem-e-transport-importer",d=JSON.parse(b.target.selectedOptions[0]?.dataset.importers),f=document.getElementById(`${c}-options`),g=document.getElementById(`${c}-supports-transformation-help`),h=document.getElementById(`${c}-submit`);f.innerHTML="",f.style.display="none",g.style.display="none";let i=!1,j=!1;const k=()=>{const a=document.querySelectorAll(`[name^="${c}\\[selectType\\]"]:checked`);i=!1,a.forEach(a=>{i=!!d[a.value]?.transform?.length}),g.style.display=i?null:"none";const b=!a.length;h.disabled=b,h.classList.toggle("tsfem-button-disabled",b)},l=b=>{const d=document.getElementById(`${c}-options-template`).content.cloneNode(!0);d.querySelector(`.${c}-selectType-description`).innerText=a.i18n.optionNames[b]??"";const e=d.querySelector(`[name^="${c}\\[selectType\\]"]`);e.value=b,f.appendChild(d),e.addEventListener("change",k),e.dispatchEvent(new Event("change"))};d.postmeta&&(l("postmeta"),j=!0),d.termmeta&&(l("termmeta"),j=!0),j&&(f.style.display=null),h.addEventListener("click",e)},k=()=>{const a=document.getElementById("tsfem-e-transport-importer[choosePlugin]");a.addEventListener("change",j),a.dispatchEvent(new Event("change"))};return Object.assign({load:()=>{document.body.addEventListener("tsf-onload",k)}})}(),window.tsfem_e_import.load(); diff --git a/extensions/free/transport/trunk/readme.md b/extensions/free/transport/trunk/readme.md new file mode 100644 index 00000000..e69de29b diff --git a/extensions/free/transport/trunk/transport.php b/extensions/free/transport/trunk/transport.php new file mode 100644 index 00000000..7627a51c --- /dev/null +++ b/extensions/free/transport/trunk/transport.php @@ -0,0 +1,126 @@ +. + */ + +/** + * The extension version. + * + * @since 1.0.0 + * NOTE: The presence does NOT guarantee the extension is loaded!!! + */ +\define( 'TSFEM_E_TRANSPORT_VERSION', '1.0.0' ); + +/** + * The extension file, absolute unix path. + * + * @since 1.0.0 + */ +\define( 'TSFEM_E_TRANSPORT_BASE_FILE', __FILE__ ); + +/** + * The extension map URL. Used for calling browser files. + * + * @since 1.0.0 + */ +\define( 'TSFEM_E_TRANSPORT_DIR_URL', \TSF_Extension_Manager\extension_dir_url( TSFEM_E_TRANSPORT_BASE_FILE ) ); + +/** + * The extension file relative to the plugins dir. + * + * @since 1.0.0 + */ +\define( 'TSFEM_E_TRANSPORT_DIR_PATH', \TSF_Extension_Manager\extension_dir_path( TSFEM_E_TRANSPORT_BASE_FILE ) ); + +/** + * The plugin class map absolute path. + * + * @since 1.0.0 + */ +\define( 'TSFEM_E_TRANSPORT_PATH_CLASS', TSFEM_E_TRANSPORT_DIR_PATH . 'inc' . DIRECTORY_SEPARATOR . 'classes' . DIRECTORY_SEPARATOR ); + +/** + * The logserver store option index. + * + * @since 1.0.0 + */ +\define( 'TSFEM_E_TRANSPORT_LOGSERVER_STORE', 'tsfem_e_transport_logserver_store' ); + +/** + * Verify integrity and set up autoloader. + * + * @since 1.0.0 + */ +if ( false === \tsf_extension_manager()->_init_early_extension_autoloader( TSFEM_E_TRANSPORT_PATH_CLASS, 'Transport', $_instance, $bits ) ) + return; + +\add_action( 'plugins_loaded', __NAMESPACE__ . '\\transport_init', 11 ); +/** + * Initializes the extension. + * + * @since 1.0.0 + * + * @return bool True if class is loaded. + */ +function transport_init() { + + static $loaded; + + // Don't init the class twice. + if ( isset( $loaded ) ) + return $loaded; + + if ( \is_admin() ) { + new Admin; + return $loaded = true; + } + + return $loaded = false; +} + +/** + * Returns the active base class. + * + * @since 1.0.0 + * + * @return string The active class name. + */ +function get_active_class() { + + if ( \is_admin() ) + return __NAMESPACE__ . '\\Admin'; + + return ''; +} diff --git a/extensions/free/transport/trunk/views/index.php b/extensions/free/transport/trunk/views/index.php new file mode 100644 index 00000000..e69de29b diff --git a/extensions/free/transport/trunk/views/layout/general/footer.php b/extensions/free/transport/trunk/views/layout/general/footer.php new file mode 100644 index 00000000..a67c164d --- /dev/null +++ b/extensions/free/transport/trunk/views/layout/general/footer.php @@ -0,0 +1,33 @@ +_verify_include_secret( $_secret ); + +/** + * Because positivity. + */ +$mottos = [ + 'automated', + 'airy', + 'open', + 'orderly', + 'elegant', + 'agile', +]; + +$motto_key = mt_rand( 0, count( $mottos ) - 1 ); +$motto = "An {$mottos[ $motto_key ]} Future"; + +?> + + +_verify_include_secret( $_secret ); + +?> + + + +_verify_include_secret( $_secret ); + +?> +
+

+ TSFEM_E_TRANSPORT_DIR_URL . 'lib/images/icon.svg', + '1x' => TSFEM_E_TRANSPORT_DIR_URL . 'lib/images/icon-29x29px.png', + ]; + $size = '1em'; + + printf( + '', + sprintf( + '%2$s', + esc_attr( $size ), + sprintf( + '', + esc_url( $image['svg'], [ 'https', 'http' ] ), + esc_url( $image['1x'], [ 'https', 'http' ] ), + esc_attr( $size ) + ) + ) + ); + ?> +

+
+_verify_include_secret( $_secret ); + +tsf_extension_manager()->_do_pane_wrap_callable( + __( 'Importer', 'the-seo-framework-extension-manager' ), + [ $this, '_importer_overview' ], + [ + 'full' => false, + 'collapse' => true, + 'wide' => false, + 'move' => false, + 'push' => true, + 'pane_id' => 'tsfem-e-transport-importer-pane', + 'ajax' => true, + 'ajax_id' => 'tsfem-e-transport-importer-ajax', + ] +); +tsf_extension_manager()->_do_pane_wrap_callable( + __( 'Logger', 'the-seo-framework-extension-manager' ), + [ $this, '_logger_overview' ], + [ + 'full' => true, + 'collapse' => true, + 'move' => false, + 'pane_id' => 'tsfem-e-transport-log-pane', + 'ajax' => true, + 'ajax_id' => 'tsfem-e-transport-log-ajax', + ] +); diff --git a/extensions/free/transport/trunk/views/layout/panes/importer.php b/extensions/free/transport/trunk/views/layout/panes/importer.php new file mode 100644 index 00000000..800b4969 --- /dev/null +++ b/extensions/free/transport/trunk/views/layout/panes/importer.php @@ -0,0 +1,127 @@ +_verify_include_secret( $_secret ); + +?> +
+
+
+

+

+
+
+
+
+

+ convert_markdown escapes. + tsf()->convert_markdown( + sprintf( + /* translators: %s = URL to backup documentation */ + esc_html__( 'Importer updates indexes in the meta databases of this WordPress installation and can transform values. Old data may be deleted or transformed irreversibly. Although careful consideration was made, transaction errors can occur and data can get lost. Always make a backup before importing. [Learn about WordPress backups](%s).', 'the-seo-framework-extension-manager' ), + esc_url( _x( + 'https://wordpress.org/support/article/wordpress-backups/', + 'backup documentation', + 'the-seo-framework-extension-manager' + ) ) + ), + [ 'a' ] + ); + ?> +
+
+

+
+ importers as $importer => $data ) { + $_importer_options .= vsprintf( + '', + [ + esc_attr( $importer ), + $_selected && $_available ? 'selected' : ( $_available ? '' : 'disabled' ), + The_SEO_Framework\Interpreters\HTML::make_data_attributes( $data ), + esc_html( $data['title'] ), + ] + ); + + // When the first available is selected, disable selected clause. + $_available + and $_selected = false; + } + vprintf( + '

+

', + [ + 'tsfem-e-transport-importer[choosePlugin]', + esc_html__( 'Choose a plugin to import SEO data from.', 'the-seo-framework-extension-manager' ), + $_importer_options, // phpcs:ignore, WordPress.Security.EscapeOutput.OutputNotEscaped + ] + ); + ?> + + + +
+ + +
    + __( 'Meta Title', 'the-seo-framework-extension-manager' ), + 'description' => __( 'Meta Description', 'the-seo-framework-extension-manager' ), + 'canonical_url' => __( 'Canonical URL', 'the-seo-framework-extension-manager' ), + 'noindex' => __( 'Robots Indexing', 'the-seo-framework-extension-manager' ), + 'nofollow' => __( 'Robots Link Following', 'the-seo-framework-extension-manager' ), + 'noarchive' => __( 'Robots Archiving', 'the-seo-framework-extension-manager' ), + 'og_title' => __( 'Open Graph Title', 'the-seo-framework-extension-manager' ), + 'og_description' => __( 'Open Graph Description', 'the-seo-framework-extension-manager' ), + 'twitter_title' => __( 'Twitter Title', 'the-seo-framework-extension-manager' ), + 'twitter_description' => __( 'Twitter Description', 'the-seo-framework-extension-manager' ), + 'og_image' => __( 'Open Graph Image', 'the-seo-framework-extension-manager' ), + 'article_type' => __( 'Article Type', 'the-seo-framework-extension-manager' ), + ] as $selection => $i18n ) { + vprintf( + '', + [ + esc_attr( $selection ), // redundant escape + esc_html( $i18n ), + ] + ); + } + ?> +
+ + */ + ?> +
+
+
diff --git a/extensions/free/transport/trunk/views/layout/panes/index.php b/extensions/free/transport/trunk/views/layout/panes/index.php new file mode 100644 index 00000000..e69de29b diff --git a/extensions/free/transport/trunk/views/layout/panes/logger.php b/extensions/free/transport/trunk/views/layout/panes/logger.php new file mode 100644 index 00000000..dfc0e530 --- /dev/null +++ b/extensions/free/transport/trunk/views/layout/panes/logger.php @@ -0,0 +1,14 @@ +_verify_include_secret( $_secret ); + +?> +
+
+
diff --git a/extensions/premium/local/trunk/inc/classes/fields.class.php b/extensions/premium/local/trunk/inc/classes/fields.class.php index 0e325c0b..78de6df3 100644 --- a/extensions/premium/local/trunk/inc/classes/fields.class.php +++ b/extensions/premium/local/trunk/inc/classes/fields.class.php @@ -3674,7 +3674,7 @@ private function get_reservation_action_fields() { '_type' => 'multi', '_desc' => [ \__( 'Target specifications', 'the-seo-framework-extension-manager' ), - \__( 'Specify where the user can complete a reservation.', 'the-seo-framework-extension-manager' ), + \__( 'Specify where visitors can complete a reservation.', 'the-seo-framework-extension-manager' ), ], '_data' => [ 'is-showif-listener' => '1', @@ -3712,8 +3712,8 @@ private function get_reservation_target_fields() { '_req' => false, '_type' => 'url', '_desc' => [ - \__( 'Form URL', 'the-seo-framework-extension-manager' ), - \__( 'The location where the visitor can perform a reservation action.', 'the-seo-framework-extension-manager' ), + \__( 'Form location URL', 'the-seo-framework-extension-manager' ), + \__( 'The location where visitors can perform a reservation action.', 'the-seo-framework-extension-manager' ), ], ], 'inLanguage' => [ diff --git a/extensions/premium/local/trunk/inc/classes/settings.class.php b/extensions/premium/local/trunk/inc/classes/settings.class.php index d5c514af..26a4e055 100644 --- a/extensions/premium/local/trunk/inc/classes/settings.class.php +++ b/extensions/premium/local/trunk/inc/classes/settings.class.php @@ -353,7 +353,7 @@ public function _register_local_scripts( $scripts ) { * * @param self $_s Used for integrity. */ - public function _get_local_settings_overview( self $_s ) { // phpcs:ignore, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + public function _local_settings_overview( self $_s ) { // phpcs:ignore, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable $this->get_view( 'layout/pages/settings' ); } @@ -411,7 +411,7 @@ private function get_test_button() { return sprintf( '', sprintf( '%s[%s]', TSF_EXTENSION_MANAGER_EXTENSION_OPTIONS, $this->o_index ), - 'hide-if-no-js tsfem-button tsfem-button-external', + 'hide-if-no-tsf-js tsfem-button tsfem-button-external', \esc_html__( 'See Markup', 'the-seo-framework-extension-manager' ) ); } diff --git a/extensions/premium/local/trunk/inc/traits/schema-packer.trait.php b/extensions/premium/local/trunk/inc/traits/schema-packer.trait.php index bb0b8163..dae2c649 100644 --- a/extensions/premium/local/trunk/inc/traits/schema-packer.trait.php +++ b/extensions/premium/local/trunk/inc/traits/schema-packer.trait.php @@ -50,10 +50,8 @@ private function should_change_precision() { if ( isset( $cache ) ) return $cache; - $precision = \function_exists( 'ini_get' ) ? ini_get( 'serialize_precision' ) : null; - //= -1 means it's optimized correctly. 7 to 14 would also do, actually. - if ( isset( $precision ) && -1 !== (int) $precision ) + if ( -1 !== (int) ini_get( 'serialize_precision' ) ) return $cache = true; return $cache = false; @@ -67,26 +65,8 @@ private function should_change_precision() { * @return bool */ private function can_change_precision() { - static $cache; - - if ( isset( $cache ) ) - return $cache; - - if ( ! \function_exists( 'ini_get_all' ) ) - return $cache = false; - - $ini_all = ini_get_all(); - - if ( empty( $ini_all['serialize_precision']['access'] ) ) - return $cache = false; - - $access = &$ini_all['serialize_precision']['access']; - - if ( INI_USER & $access || INI_ALL & $access ) - return $cache = true; - - return $cache = false; + return $cache ?? ( $cache = \wp_is_ini_value_changeable( 'serialize_precision' ) ); } /** diff --git a/extensions/premium/local/trunk/inc/traits/secure-post.trait.php b/extensions/premium/local/trunk/inc/traits/secure-post.trait.php index 20db3e92..1208de05 100644 --- a/extensions/premium/local/trunk/inc/traits/secure-post.trait.php +++ b/extensions/premium/local/trunk/inc/traits/secure-post.trait.php @@ -94,9 +94,8 @@ protected function init_post_checks() { // AJAX only, not registered. Also, this method AFTER admin_init, so it went by unnoticed. // \add_action( 'admin_init', [ $this, '_handle_update_post' ] ); - if ( \wp_doing_ajax() ) { + if ( \wp_doing_ajax() ) $this->init_ajax_post_checks(); - } } /** diff --git a/extensions/premium/local/trunk/views/layout/pages/local.php b/extensions/premium/local/trunk/views/layout/pages/local.php index 259808bb..66b6f929 100644 --- a/extensions/premium/local/trunk/views/layout/pages/local.php +++ b/extensions/premium/local/trunk/views/layout/pages/local.php @@ -10,7 +10,7 @@ tsf_extension_manager()->_do_pane_wrap_callable( __( 'Local Settings', 'the-seo-framework-extension-manager' ), - [ $this, '_get_local_settings_overview' ], + [ $this, '_local_settings_overview' ], [ 'full' => true, 'collapse' => false, diff --git a/extensions/premium/local/trunk/views/layout/pages/settings.php b/extensions/premium/local/trunk/views/layout/pages/settings.php index 94bb9cc4..ebb73118 100644 --- a/extensions/premium/local/trunk/views/layout/pages/settings.php +++ b/extensions/premium/local/trunk/views/layout/pages/settings.php @@ -8,17 +8,15 @@ defined( 'TSF_EXTENSION_MANAGER_PRESENT' ) and $_class = TSF_Extension_Manager\Extension\Local\get_layout_class() and $this instanceof $_class or die; -// TODO Set TSF v4.0 JS check instead. - ?>
-
+

-
+


diff --git a/extensions/premium/monitor/trunk/inc/classes/admin.class.php b/extensions/premium/monitor/trunk/inc/classes/admin.class.php index 055acf4a..dd267355 100644 --- a/extensions/premium/monitor/trunk/inc/classes/admin.class.php +++ b/extensions/premium/monitor/trunk/inc/classes/admin.class.php @@ -807,7 +807,7 @@ public function _output_monitor_footer() { * @access private */ public function _output_theme_color_meta() { - $this->get_view( 'layout/pages/meta' ); + $this->get_view( 'layout/general/meta' ); } /** @@ -987,7 +987,7 @@ protected function get_site_settings_view() { $options[ $id ] = [ 'edit' => vsprintf( - '', + '', [ $form_id, \esc_attr( $_field_id ), @@ -996,7 +996,7 @@ protected function get_site_settings_view() { ] ), 'js' => vsprintf( - '%s', + '%s', [ \esc_attr( $_field_id ), \esc_html( $args['value'] @@ -1042,10 +1042,10 @@ protected function get_site_settings_view() { ); $content .= sprintf( - '
%s
', + '
%s
', \esc_url( \tsf_extension_manager()->get_admin_page_url( $this->monitor_page_slug ), [ 'https', 'http' ] ), $form_id, - 'hide-if-js', + 'hide-if-tsf-js', $nonce_action . $nonce . $submit ); } @@ -1303,7 +1303,7 @@ protected function get_disconnect_site_view() { . '
'; $button = sprintf( - '
%s
', + '
%s
', \esc_url( \tsf_extension_manager()->get_admin_page_url( $this->monitor_page_slug ), [ 'https', 'http' ] ), $nonce_action . $nonce . $switcher ); diff --git a/extensions/premium/monitor/trunk/lib/css/tsfem-monitor.css b/extensions/premium/monitor/trunk/lib/css/tsfem-monitor.css index 3ca055b0..46ebf85f 100644 --- a/extensions/premium/monitor/trunk/lib/css/tsfem-monitor.css +++ b/extensions/premium/monitor/trunk/lib/css/tsfem-monitor.css @@ -165,7 +165,7 @@ height: 1px; border: 1px solid; border-radius: 3px; - transition: all 0.5s ease-in-out; + transition: all .5s ease-in-out; background: currentColor; } diff --git a/extensions/premium/monitor/trunk/lib/js/tsfem-monitor.js b/extensions/premium/monitor/trunk/lib/js/tsfem-monitor.js index 48cff733..29520d79 100644 --- a/extensions/premium/monitor/trunk/lib/js/tsfem-monitor.js +++ b/extensions/premium/monitor/trunk/lib/js/tsfem-monitor.js @@ -173,7 +173,7 @@ window.tsfem_e_monitor = { return; let loading = 'tsfem-button-disabled tsfem-button-loading', - loader = '#tsfem-e-monitor-issues-pane .tsfem-pane-header .tsfem-ajax'; + loader = '#tsfem-e-monitor-issues-pane .tsfem-pane-header .tsfem-ajax'; $button.addClass( loading ); $button.prop( 'disabled', true ); diff --git a/extensions/premium/monitor/trunk/views/layout/pages/meta.php b/extensions/premium/monitor/trunk/views/layout/general/meta.php similarity index 100% rename from extensions/premium/monitor/trunk/views/layout/pages/meta.php rename to extensions/premium/monitor/trunk/views/layout/general/meta.php diff --git a/extensions/premium/monitor/trunk/views/layout/pages/connect.php b/extensions/premium/monitor/trunk/views/layout/pages/connect.php index 26090ef0..9ed0a287 100644 --- a/extensions/premium/monitor/trunk/views/layout/pages/connect.php +++ b/extensions/premium/monitor/trunk/views/layout/pages/connect.php @@ -14,8 +14,8 @@

-

-
+

+

get_view( 'layout/pages/meta' ); + $this->get_view( 'layout/general/meta' ); } /** diff --git a/inc/classes/core.class.php b/inc/classes/core.class.php index 4f9eaff0..50f35563 100644 --- a/inc/classes/core.class.php +++ b/inc/classes/core.class.php @@ -250,21 +250,21 @@ final public function are_options_valid() { final public function _clean_response_header() { $retval = 0; - // PHP 5.6+ //= $i = 0; + $i = 0; if ( $level = ob_get_level() ) { // phpcs:ignore, WordPress.CodeAnalysis.AssignmentInCondition -- this is fine. while ( $level-- ) { ob_end_clean(); } - $retval |= 1; //= 2 ** $i + $retval |= 2 ** $i; } - // PHP 5.6+ //= $i++; + $i++; // wp_ajax sets required headers early. if ( ! headers_sent() ) { header_remove(); - $retval |= 2; //= 2 ** $i + $retval |= 2 ** $i; } return $retval; @@ -303,6 +303,7 @@ final public function set_status_header( $code = 200, $type = '' ) { * is actually JSON encoded. When it's 1, we can safely assume it's JSON. * * @since 1.2.0 + * @since 2.6.0 Now outputs boolean key 'success' if $type is of 'success'. * @TODO set a standard for $data, i.e. [ 'results'=>[],'html'=>"", etc. ]; * * @param mixed $data The data that needs to be send. @@ -321,7 +322,9 @@ final public function send_json( $data, $type = 'success' ) { $this->set_status_header( null, 'json' ); } - echo json_encode( compact( 'data', 'type', 'json' ) ); + $success = 'success' === $type; + + echo json_encode( compact( 'data', 'type', 'json', 'success' ) ); exit; } @@ -865,9 +868,7 @@ final public function _get_timed_hash( $uid, $length = 3600, $end = 0 ) { $now_x = floor( ( $_time - $_delta ) / $length ); - $string = $uid . '\\' . $now_x . '\\' . $uid; - - return $this->hash( $string, 'auth' ); + return $this->hash( "$uid\\$now_x\\$uid", 'auth' ); } /** @@ -1143,6 +1144,7 @@ final protected function get_extension_autload_path( $namespace ) { * @since 1.2.0 * @since 1.3.0 Now handles namespaces instead of class bases. * @since 2.5.1 Now supports mixed class case. + * @since 2.6.0 Now supports branched path files. * * @param string $class The extension classname. * @return bool False if file hasn't yet been included, otherwise true. @@ -1179,7 +1181,11 @@ final protected function autoload_extension_class( $class ) { $_path = $this->get_extension_autload_path( $_ns ); if ( $_path ) { - $_file = str_replace( '_', '-', str_replace( $_ns . '\\', '', $_class ) ); + $_file = str_replace( + [ "$_ns\\", '\\', '/', '_' ], + [ '', DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR, '-' ], + $_class + ); $this->get_verification_codes( $_instance, $bits ); diff --git a/inc/classes/extensionsettings.class.php b/inc/classes/extensionsettings.class.php index 59f72a6c..03f53035 100644 --- a/inc/classes/extensionsettings.class.php +++ b/inc/classes/extensionsettings.class.php @@ -291,9 +291,7 @@ public function _do_ajax_form_save() { foreach ( $sanitizations as $_key => $_cb ) { $store[ $slug ][ $_key ] = \call_user_func( $_cb, - isset( $data[ TSF_EXTENSION_MANAGER_EXTENSION_OPTIONS ][ $slug ][ $_key ] ) - ? $data[ TSF_EXTENSION_MANAGER_EXTENSION_OPTIONS ][ $slug ][ $_key ] - : null + $data[ TSF_EXTENSION_MANAGER_EXTENSION_OPTIONS ][ $slug ][ $_key ] ?? null ); } } diff --git a/inc/classes/formgenerator.class.php b/inc/classes/formgenerator.class.php index 67b5bbdf..834cd2dc 100644 --- a/inc/classes/formgenerator.class.php +++ b/inc/classes/formgenerator.class.php @@ -26,6 +26,13 @@ // phpcs:disable, Squiz.Commenting.VariableComment.DuplicateVar, PSR2.Classes.PropertyDeclaration.Multiple -- @TODO later. This class is too complex. +/** + * Require extension settings trait. + * + * @since 2.6.0 + */ +_load_trait( 'extension/options' ); + /** * Holds settings generator functions for package TSF_Extension_Manager\Extension. * @@ -404,7 +411,7 @@ private function get_form_wrap( $what, $url, $validator ) { switch ( $what ) : case 'start': return vsprintf( - '

', + '', [ \esc_url( $url ), $this->get_form_id(), @@ -455,7 +462,7 @@ private function get_form_button( $what, $name ) { switch ( $what ) : case 'submit': return vsprintf( - '', + '', [ $this->get_form_id(), \esc_html( $name ), @@ -2051,7 +2058,7 @@ private function create_image_field( array $args ) { [ $this->create_field_description( $args, $s_url_id ), vsprintf( - '
%s%s
%s
', + '
%s%s
%s
', [ vsprintf( '', diff --git a/inc/classes/html.class.php b/inc/classes/html.class.php index b36fa093..353a1252 100644 --- a/inc/classes/html.class.php +++ b/inc/classes/html.class.php @@ -170,48 +170,4 @@ public static function make_sequential_dropdown_option_list( array $options, $se return static::make_dropdown_option_list( $_options, $selected ?: 0 ); } - - /** - * Makes either simple or JSON-encoded data-* attributes for HTML elements. - * - * Converts CamelCase to dash-case when needed. - * Data value may be anything, and is JSON encoded. Use jQuery.data() to extract. - * - * @since 1.5.0 - * - * @param array $data : { - * string $k => mixed $v - * } - * @return string The HTML data attributes, with added space to the start. - */ - public static function make_data_attributes( array $data ) { - - $ret = []; - - foreach ( $data as $k => $v ) { - if ( ! is_scalar( $v ) ) { - $ret[] = sprintf( - 'data-%s="%s"', - strtolower( preg_replace( - '/([A-Z])/', - '-$1', - preg_replace( '/[^a-z0-9_\-]/i', '', $k ) - ) ), // dash case. - htmlspecialchars( json_encode( $v, JSON_UNESCAPED_SLASHES ), ENT_COMPAT, 'UTF-8' ) - ); - } else { - $ret[] = sprintf( - 'data-%s="%s"', - strtolower( preg_replace( - '/([A-Z])/', - '-$1', - preg_replace( '/[^a-z0-9_\-]/i', '', $k ) - ) ), // dash case. - \esc_attr( $v ) - ); - } - } - - return ' ' . implode( ' ', $ret ); - } } diff --git a/inc/classes/layout.class.php b/inc/classes/layout.class.php index 256fba15..18ab1b03 100644 --- a/inc/classes/layout.class.php +++ b/inc/classes/layout.class.php @@ -29,7 +29,7 @@ * * @since 2.5.2 */ -\TSF_Extension_Manager\_load_trait( 'factory/time' ); +_load_trait( 'factory/time' ); /** * Class TSF_Extension_Manager\Layout. @@ -42,7 +42,7 @@ * @final */ final class Layout extends Secure_Abstract { - use \TSF_Extension_Manager\Time; + use Time; /** * Initializes class variables. Always use reset when done with this class. @@ -164,7 +164,8 @@ private static function get_disconnect_button() { . $button . '
'; - $output = sprintf( '%s', + $output = sprintf( + '
%s
', \esc_url( $tsfem->get_admin_page_url() ), $nonce_action . $nonce . $switcher ); @@ -552,7 +553,7 @@ private static function get_account_upgrade_form() { $form = $input . $nonce_action . $nonce . $submit; return sprintf( - '
%s
', + '
%s
', \esc_attr( self::$request_name['activate-key'] ), \esc_url( \tsf_extension_manager()->get_admin_page_url() ), 'input-activation', diff --git a/inc/classes/panes.class.php b/inc/classes/panes.class.php index e48da3b9..35e50937 100644 --- a/inc/classes/panes.class.php +++ b/inc/classes/panes.class.php @@ -82,10 +82,7 @@ protected function get_seo_trends_and_updates_overview() { * @return string The escaped account actions overview. */ protected function get_extensions_actions_overview() { - - $output = $this->get_actions_output(); - - return sprintf( '
%s
', $output ); + return sprintf( '
%s
', $this->get_actions_output() ); } /** @@ -96,10 +93,7 @@ protected function get_extensions_actions_overview() { * @return string The extensions overview. */ protected function get_extension_overview() { - - $output = $this->get_extensions_output(); - - return sprintf( '
%s
', $output ); + return sprintf( '
%s
', $this->get_extensions_output() ); } /** @@ -113,18 +107,18 @@ protected function get_trends_output() { $feed = $this->get_trends_feed(); - $output = ''; - if ( -1 === $feed ) { - $feed_error = \esc_html__( "Unfortunately, your server can't process this request as of yet.", 'the-seo-framework-extension-manager' ); - - $output .= sprintf( '

%s

', $feed_error ); + $output = sprintf( + '

%s

', + \esc_html__( "Unfortunately, your server can't process this request as of yet.", 'the-seo-framework-extension-manager' ) + ); } elseif ( empty( $feed ) ) { - $feed_error = \esc_html__( 'There are no trends and updates to report yet.', 'the-seo-framework-extension-manager' ); - - $output .= sprintf( '

%s

', $feed_error ); + $output = sprintf( + '

%s

', + \esc_html__( 'There are no trends and updates to report yet.', 'the-seo-framework-extension-manager' ) + ); } else { - $output .= sprintf( '
%s
', $feed ); + $output = sprintf( '
%s
', $feed ); } return sprintf( '', $output ); @@ -146,16 +140,20 @@ protected function ajax_get_trends_output() { $data = $this->get_trends_feed( true ); if ( -1 === $data ) { - $feed_error = \esc_html__( "Unfortunately, your server can't process this request as of yet.", 'the-seo-framework-extension-manager' ); - $output = sprintf( '

%s

', $feed_error ); + $output = sprintf( + '

%s

', + \esc_html__( "Unfortunately, your server can't process this request as of yet.", 'the-seo-framework-extension-manager' ) + ); $send = [ 'status' => 'parse_error', 'error_output' => sprintf( '', $output ), ]; } elseif ( empty( $data ) ) { - $feed_error = \esc_html__( 'There are no trends and updates to report yet.', 'the-seo-framework-extension-manager' ); - $output = sprintf( '

%s

', $feed_error ); + $output = sprintf( + '

%s

', + \esc_html__( 'There are no trends and updates to report yet.', 'the-seo-framework-extension-manager' ) + ); $send = [ 'status' => 'unknown_error', @@ -252,16 +250,16 @@ protected function get_feed_enabler_button() { \esc_attr( $enable ) ); - $form = $nonce_action . $nonce . $submit; + $form = "{$nonce_action}{$nonce}{$submit}"; $nojs = sprintf( - '
%s
', + '
%s
', \esc_url( $this->get_admin_page_url() ), $form ); - $js = '

' . \esc_html( $enable ) . '

'; + $js = '

' . \esc_html( $enable ) . '

'; - return sprintf( '
%s
', $js . $nojs ); + return sprintf( '
%s
', "{$js}{$nojs}" ); } /** diff --git a/inc/functions/api.php b/inc/functions/api.php index 483c5ed4..147b2d3f 100644 --- a/inc/functions/api.php +++ b/inc/functions/api.php @@ -33,6 +33,8 @@ * or even other plugins. * * @since 1.0.0 + * @see `tsfem()` alias. + * @api * * @return null|object The plugin class object. */ @@ -40,6 +42,23 @@ function tsf_extension_manager() { return \TSF_Extension_Manager\_init_tsf_extension_manager(); } + /** + * Returns the class from cache. + * + * This is the recommended way of calling the class, if needed. + * Call this after action 'init' priority 0 otherwise it will kill the plugin, + * or even other plugins. + * + * @since 2.6.0 + * @see `tsf_extension_manager()` alias. + * @api + * + * @return null|object The plugin class object. + */ + function tsfem() { + return \TSF_Extension_Manager\_init_tsf_extension_manager(); + } + /** * Returns the database version of TSFEM members. * The 'core' member represents the main plugin. diff --git a/inc/traits/core/error.trait.php b/inc/traits/core/error.trait.php index 9153d565..0ea43d0a 100644 --- a/inc/traits/core/error.trait.php +++ b/inc/traits/core/error.trait.php @@ -294,6 +294,7 @@ protected function get_error_notice_by_key( $key, $get_type = true ) { case 701: case 708: case 1010702: + case 1060106: $message = \esc_html__( 'Invalid API request type.', 'the-seo-framework-extension-manager' ); $type = 'error'; break; @@ -560,6 +561,11 @@ protected function get_error_notice_by_key( $key, $get_type = true ) { $type = 'warning'; break; + case 1060201: + $message = \esc_html__( "Importer does't exist.", 'the-seo-framework-extension-manager' ); + $type = 'error'; + break; + case 1010607: $message = \esc_html__( 'Data has just been updated.', 'the-seo-framework-extension-manager' ); $type = 'warning'; @@ -586,23 +592,9 @@ protected function get_error_notice_by_key( $key, $get_type = true ) { $type = 'updated'; break; - case 1060301: - $message = \esc_html__( "The SEO settings couldn't be converted to file.", 'the-seo-framework-extension-manager' ); - $type = 'error'; - break; - - case 1060302: - $message = \esc_html__( 'An unknown source outputted data before sending the file. Therefore, Transporter is unable to complete your request.', 'the-seo-framework-extension-manager' ); - $type = 'error'; - break; - - case 1060401: - $message = \esc_html__( 'Download will start shortly.', 'the-seo-framework-extension-manager' ); - $type = 'info'; - break; - case 17100: case 18101: + case 1060200: case 1070100: case 1090100: $message = \esc_html__( 'Invalid data was sent to the server.', 'the-seo-framework-extension-manager' ); @@ -631,12 +623,32 @@ protected function get_error_notice_by_key( $key, $get_type = true ) { $type = 'updated'; break; + case 1060202: + $message = \esc_html__( 'Transporting in session, trying to connect to logger...', 'the-seo-framework-extension-manager' ); + $type = 'updated'; + break; + case 1070102: case 1090102: $message = \esc_html__( 'Changes are saved.', 'the-seo-framework-extension-manager' ); $type = 'updated'; break; + case 1060203: + $message = \esc_html__( 'Timeout', 'the-seo-framework-extension-manager' ); + $type = 'error'; + break; + + case 1060204: + $message = \esc_html__( 'Crash', 'the-seo-framework-extension-manager' ); + $type = 'error'; + break; + + case 1060205: + $message = \esc_html__( 'Done!', 'the-seo-framework-extension-manager' ); + $type = 'updated'; + break; + case 1011700: case 1071100: case 1071101: @@ -746,8 +758,6 @@ protected function get_error_notice_by_key( $key, $get_type = true ) { case 1010505: case 1010604: case 1010605: - case 1060101: - case 1060402: $message = \esc_html__( 'An unknown error occurred. Contact the plugin author if this error keeps coming back.', 'the-seo-framework-extension-manager' ); $type = 'error'; break; diff --git a/inc/traits/core/ui.trait.php b/inc/traits/core/ui.trait.php index 0dee36a5..e0cb8528 100644 --- a/inc/traits/core/ui.trait.php +++ b/inc/traits/core/ui.trait.php @@ -64,6 +64,10 @@ private function init_ui() { \add_action( 'admin_footer_text', '__return_empty_string', PHP_INT_MAX ); \add_action( 'update_footer', '__return_empty_string', PHP_INT_MAX ); + // Prevent annoying nags (they're hidden by CSS anyway). + \remove_action( 'admin_notices', 'update_nag', 3 ); + \remove_action( 'admin_notices', 'maintenance_nag', 10 ); + // Add body class. \add_action( 'admin_body_class', [ $this, '_add_admin_body_class' ], 999, 1 ); @@ -106,9 +110,9 @@ final protected function page_wrap() { */ final public function header_wrap() { echo '
'; - echo '
'; + echo '
'; \do_action( 'tsfem_header' ); - echo '
'; + echo '
'; $this->notice_wrap(); echo '
'; } @@ -132,15 +136,16 @@ final public function notice_wrap() { * @since 1.5.0 * @since 2.0.1 Now listens to $this->wrap_type * @since 2.2.0 Is no longer a tsfem-flex-item. + * @since 2.6.0 Added a super wrap to allow a condensed layout. */ final public function panes_wrap() { printf( - '
', + '
', // phpcs:ignore, WordPress.Security.EscapeOutput.OutputNotEscaped \in_array( $this->wrap_type, [ 'column', 'row' ], true ) ? $this->wrap_type : 'column' ); \do_action( 'tsfem_content' ); - echo '
'; + echo '
'; } /** diff --git a/inc/traits/extension/forms.trait.php b/inc/traits/extension/forms.trait.php index 875902f6..e0c3a9d2 100644 --- a/inc/traits/extension/forms.trait.php +++ b/inc/traits/extension/forms.trait.php @@ -273,7 +273,7 @@ public function _get_action_button( $url = '', array $items = [] ) { } $output .= sprintf( - '
%s
', + '
%s
', \esc_url( $url, [ 'https', 'http' ] ), \esc_attr( $items['id'] ), \esc_attr( $items['class'] ), @@ -281,7 +281,7 @@ public function _get_action_button( $url = '', array $items = [] ) { ); $button = sprintf( - '%s', + '%s', \esc_attr( $items['ajax-id'] ), \esc_attr( $items['ajax-class'] ), \esc_attr( $items['ajax-title'] ), @@ -291,7 +291,7 @@ public function _get_action_button( $url = '', array $items = [] ) { $output .= $button; } else { $output .= sprintf( - '
%s
', + '
%s
', \esc_url( $url, [ 'https', 'http' ] ), \esc_attr( $items['id'] ), \esc_attr( $items['class'] ), diff --git a/inc/traits/extension/views.trait.php b/inc/traits/extension/views.trait.php new file mode 100644 index 00000000..d295c6d0 --- /dev/null +++ b/inc/traits/extension/views.trait.php @@ -0,0 +1,114 @@ +. + */ + +/** + * Holds View functionality. + * + * @since 2.6.0 + * @access private + * @uses trait TSF_Extension_Manager\Extension_Options + * @see TSF_Extension_Manager\Traits\Extension_Options + */ +trait Extension_Views { + + /** + * @since 2.6.0 + * @var string The view location base. + */ + protected $view_location_base; + + /** + * Fetches files based on input to reduce memory overhead. + * Passes on input vars. + * + * @since 2.6.0 + * + * @param string $view The file name. + * @param array $__args The arguments to be supplied within the file name. + * Each array key is converted to a variable with its value attached. + */ + protected function get_view( $view, $__args = [] ) { + + foreach ( $__args as $__k => $__v ) $$__k = $__v; + unset( $__k, $__v, $__args ); + + // phpcs:ignore, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- forwarded to include... + $_secret = $this->create_view_secret( uniqid( '', true ) ); + + include $this->_get_view_location( $view ); + } + + /** + * Stores and returns view secret. + * + * This is not cryptographically secure, but it's enough to fend others off including our files where they shouldn't. + * Our view-files have a certain expectation of inputs to meet. If they don't meet that, we could expose our users to security issues. + * We could not measure any meaningful performance impact by using this (0.02% of 54x get_view() runtime). + * + * @since 2.6.0 + * + * @param string|null $value The secret. + * @return string|null The stored secret. + */ + protected function create_view_secret( $value = null ) { + // Use unique key that's shared accross all trait's instances. + return \The_SEO_Framework\umemo( __METHOD__, $value ); + } + + /** + * Verifies view secret. + * + * This can be bypassed unless we extract this method from `$this` by mimicking + * the functionality. However, the purpose is that `$this` cannot get leaked via + * a hypothetical (opcode) file cache vulnerability, which is served well here. + * The files can still get accessed through the wilfully cunning, though won't + * provide any useful integrity violation. + * + * @since 2.6.0 + * @access private + * + * @param string $value The secret. + * @return bool + */ + public function _verify_include_secret( $value ) { + return isset( $value ) && $this->create_view_secret() === $value; + } + + /** + * Gets view location. + * + * @since 2.6.0 + * @access private + * @TODO add path traversal mitigation via realpath()? + * -> $file must always be dev-supplied, never user-. + * + * @param string $file The file name. + * @return string The view location. + */ + public function _get_view_location( $file ) { + return "{$this->view_location_base}$file.php"; + } +} diff --git a/inc/traits/manager/extensions-layout.trait.php b/inc/traits/manager/extensions-layout.trait.php index 7e1bf53c..2e9f5afd 100644 --- a/inc/traits/manager/extensions-layout.trait.php +++ b/inc/traits/manager/extensions-layout.trait.php @@ -349,18 +349,18 @@ private static function get_extension_button_form( $slug = '', $type = '', $disa $nonce_action = \tsf_extension_manager()->_get_nonce_action_field( self::$request_name[ $nonce_key ] ); $extension = sprintf( - '', + '', $cache['input_name'], $s_slug ); $submit = sprintf( - '', + '', $s_slug, $s_class, \esc_attr( $text ) ); $nojs = sprintf( - '
%s
', + '
%s
', $cache['admin_url'], $s_slug, $nonce_action . $nonce . $extension . $submit @@ -368,7 +368,7 @@ private static function get_extension_button_form( $slug = '', $type = '', $disa js:; $js = sprintf( - '', + '', $s_slug, $s_class, $s_slug, diff --git a/inc/traits/manager/extensions.trait.php b/inc/traits/manager/extensions.trait.php index 4e5b625d..5c8ebc44 100644 --- a/inc/traits/manager/extensions.trait.php +++ b/inc/traits/manager/extensions.trait.php @@ -56,6 +56,8 @@ trait Extensions_Properties { * Fetches all extensions. * * @since 1.0.0 + * @TODO Once we deprecate extensions, we'd want to do that here 'deprecated'=>true, + * and hide those from view when deactivated. Constant activation is still possible. * * @return array The extensions list. */ @@ -119,6 +121,19 @@ private static function get_extensions() { 'requires_tsf' => '4.1.4', 'tested_tsf' => '4.2', ], + 'transport' => [ + 'slug' => 'transport', + 'network' => '0', + 'type' => 'free', + 'area' => 'setup', + 'author' => 'Sybre Waaijer', + 'party' => 'first', + 'last_updated' => '1651931204', + 'requires' => '5.5', + 'tested' => '6.0', + 'requires_tsf' => '4.1.4', + 'tested_tsf' => '4.2', + ], 'cord' => [ 'slug' => 'cord', 'network' => '0', @@ -226,9 +241,9 @@ private static function get_extensions() { */ private static function get_external_extensions_checksum() { return [ - 'sha256' => '5b80680cd8b82eb998370ea50b5aa5afdbe2c4a45d1386a1cec399f462741dc9', - 'sha1' => '00968dd9ff0d9791e083c2766a5d17af1dd0ad0a', - 'md5' => '3ddc34319cd368d11459cbc5cb34029e', + 'sha256' => '542e47beb1f4eb8f563909576e24f8ae99052475a830d22d4bf59409866dafc9', + 'sha1' => '9f03c10713d3adcc47e0f0f3d6c558943f843c7b', + 'md5' => 'a903c19b2df26eea0a657a5674845cb1', ]; } diff --git a/inc/traits/manager/options.trait.php b/inc/traits/manager/options.trait.php index 66693aaa..36875ac4 100644 --- a/inc/traits/manager/options.trait.php +++ b/inc/traits/manager/options.trait.php @@ -82,16 +82,11 @@ final protected function get_option( $option, $default = null ) { return null; if ( $this->killed_options ) - return []; + return null; static $options_cache = []; - if ( isset( $options_cache[ $option ] ) ) - return $options_cache[ $option ]; - - $options = $this->get_all_options(); - - return $options_cache[ $option ] = isset( $options[ $option ] ) ? $options[ $option ] : $default; + return $options_cache[ $option ] = $options_cache[ $option ] ?? $this->get_all_options()[ $option ] ?? $default; } /** diff --git a/lib/css/tsfem-form.css b/lib/css/tsfem-form.css index a65402bc..64ccd503 100644 --- a/lib/css/tsfem-form.css +++ b/lib/css/tsfem-form.css @@ -159,7 +159,7 @@ body.rtl .tsfem-form-title-icon-good:before { height: 1px; border: 1px solid; border-radius: 3px; - transition: all 0.5s ease-in-out; + transition: all .5s ease-in-out; background: currentColor; } body.rtl .tsfem-form-collapse-icon:before, @@ -350,7 +350,7 @@ body.rtl .tsfem-form-image-buttons-wrap button { } .tsfem-form-multi-select-wrap { - max-height: 350px; + max-height: 448px; /* 20 rows of 16px checkboxes with 1.4 lineheight */ padding: 0px 15px; box-shadow: 0 0 0 1px #ccd0d4; overflow: hidden; diff --git a/lib/css/tsfem-form.min.css b/lib/css/tsfem-form.min.css index 01a19dd6..b99762de 100644 --- a/lib/css/tsfem-form.min.css +++ b/lib/css/tsfem-form.min.css @@ -1 +1 @@ -.tsfem-form-collapse-wrap{width:100%}.tsfem-form-collapse{position:relative;width:100%;margin:0;padding:0;box-shadow:0 0 0 1px rgba(0,0,0,.18) inset;border-radius:4px}.tsfem-form-collapse-sub-wrap:not(:empty){margin-top:1em}.tsfem-form-collapse-sub-wrap.tsfem-form-multi-setting-input{margin-top:0}.tsfem-form-collapse-sub-wrap .tsfem-form-collapse{margin-bottom:1em}.tsfem-form-collapse-sub-wrap .tsfem-form-collapse:last-of-type{margin-bottom:0}.tsfem-form-collapse>input{position:absolute;width:0;height:0;opacity:0;margin:0!important;padding:0!important;border:0;-webkit-appearance:none;-moz-appearance:none;appearance:none}.tsfem-form-collapse>input:checked:before{content:none;float:none;margin:0;width:0}.tsfem-form-collapse-header{position:relative;width:100%;padding:1em;background:#fff;box-shadow:0 0 0 1px rgba(0,0,0,.18),0 0 0 0 rgba(0,0,0,.18) inset;align-items:center;cursor:pointer;z-index:1;transition:box-shadow .33s;border-radius:4px}.tsfem-form-collapse-header-good .tsfem-form-collapse-icon{color:#0cc34b}.tsfem-form-collapse-header-error .tsfem-form-collapse-icon{color:#dd3811}.tsfem-form-collapse-header:hover,.tsfem-form-collapse>input:focus+.tsfem-form-collapse-header{box-shadow:0 0 4px 1px rgba(0,0,0,.3),0 0 1px 0 rgba(0,0,0,.3) inset}.tsfem-form-collapse>input:active+.tsfem-form-collapse-header,.tsfem-forum-collapse-header:active{box-shadow:0 0 0 1px rgba(0,0,0,.3),0 0 2px 1px rgba(0,0,0,.18) inset}.tsfem-form-collapse>input:focus:not(:checked):not(:hover)+.tsfem-form-collapse-header{box-shadow:0 0 0 1px rgba(0,0,0,.18),0 0 0 0 rgba(0,0,0,.18) inset}.tsfem-form-collapse-title-wrap{line-height:1.15em;font-size:1.15em;padding:0;margin:0;vertical-align:middle}.tsfem-form-title-icon:before{display:inline-block;width:19px;line-height:inherit;font-family:dashicons;font-style:normal;font-weight:400;font-size:inherit;vertical-align:bottom;line-height:1em;content:"";margin-right:2px}body.rtl .tsfem-form-title-icon:before{margin-right:0;margin-left:2px}.tsfem-form-title-icon-good:before,.tsfem-form-title-icon-okay:before{color:#0cc34b;font-size:1.1em;content:"\f147";margin-left:-2px;margin-right:4px}body.rtl .tsfem-form-title-icon-good:before,body.rtl .tsfem-form-title-icon-okay:before{margin-left:4px;margin-right:-2px}.tsfem-form-title-icon-okay:before,.tsfem-form-title-icon-warning:before{color:#ffa01b}.tsfem-form-title-icon-warning:before{content:"\f227"}.tsfem-form-title-icon-bad:before,.tsfem-form-title-icon-error:before{color:#dd3811;content:"\f227"}.tsfem-form-title-icon-error:before{content:"\f153"}.tsfem-form-title-icon-unknown:before{content:"\f223"}.tsfem-form-collapse-icon{position:relative;max-width:18px;max-height:3px}.tsfem-form-collapse-icon:after,.tsfem-form-collapse-icon:before{content:"";position:absolute;width:6px;right:.5em;top:0;height:1px;border:1px solid;border-radius:3px;transition:all .5s ease-in-out;background:currentColor}body.rtl .tsfem-form-collapse-icon:after,body.rtl .tsfem-form-collapse-icon:before{right:unset;left:.5em}.tsfem-form-collapse-icon:before{transform:translate(-2px,0) rotate(-45deg)}.tsfem-form-collapse-icon:after{transform:translate(2px,0) rotate(45deg)}.tsfem-form-collapse>input[type=checkbox]:checked+.tsfem-form-collapse-header .tsfem-form-collapse-icon:before{transform:translate(-2px,0) rotate(45deg)}.tsfem-form-collapse>input[type=checkbox]:checked+.tsfem-form-collapse-header .tsfem-form-collapse-icon:after{transform:translate(2px,0) rotate(-45deg)}.tsfem-form-collapse-content{display:block;margin:0 auto;box-sizing:border-box;padding:calc(.5em + .6vw);animation:tsfemForm-fade-in .25s}.tsfem-form-collapse>input[type=checkbox]:checked~.tsfem-form-collapse-content{display:none}@keyframes tsfemForm-fade-in{from{opacity:0}to{opacity:1}}.tsfem-form-iterator-selector-wrap,.tsfem-form-iterator-setting,.tsfem-form-multi-setting,.tsfem-form-setting{width:100%;flex-direction:row}.tsfem-form-iterator-setting-input,.tsfem-form-iterator-setting-label,.tsfem-form-multi-setting-input,.tsfem-form-multi-setting-label,.tsfem-form-setting-input,.tsfem-form-setting-label{background-color:#fff;padding:1em;flex:10 1 350px;box-shadow:0 0 0 1px #ccd0d4}.tsfem-form-iterator-setting-label,.tsfem-form-multi-setting-label,.tsfem-form-setting-label{background-color:#f9f9f9;flex:2 1 200px;border-radius:3px}.tsfem-form-plain-settings-label{margin-bottom:1em;flex:1 1 100%}.tsfem-form-iterator-setting-label-item,.tsfem-form-multi-setting-label-item,.tsfem-form-setting-action,.tsfem-form-setting-label-item{flex-wrap:nowrap;flex-direction:row;justify-content:space-between;flex-grow:0;max-width:max-content}.tsfem-form-iterator-setting-input,.tsfem-form-multi-setting-input,.tsfem-form-setting-input,.tsfem-form-setting-label-inner-wrap{justify-content:center}.tsfem-form-iterator-selector-wrap .tsfem-form-setting-label-inner-wrap{will-change:contents}.tsfem-form-iterator-setting-label-inner-wrap,.tsfem-form-multi-setting-label-inner-wrap,.tsfem-form-select-multi-a11y-label-inner-wrap{justify-content:flex-start}.tsfem-form-iterator-setting-label-item>*,.tsfem-form-multi-setting-label-item>*,.tsfem-form-setting-label-item>*{margin-right:4px}.tsfem-form-iterator-setting-label-item>:last-child,.tsfem-form-multi-setting-label-item>:last-child,.tsfem-form-setting-label-item>:last-child{margin-right:0}.tsfem-form-setting-action{margin-top:1em}.tsfem-form-setting-input textarea{max-height:250px}.tsfem-form-option-title.tsfem-form-option-has-description{margin:0 0 .5em}.tsfem-form-option-description{margin:.5em 0}.tsfem-form-plain-settings-label .tsfem-form-option-description:first-of-type{margin-top:0}.tsfem-form-plain-settings-label .tsfem-form-option-description:last-of-type{margin-bottom:0}.tsfem-form-setting-input select,.tsfem-form-setting-input textarea,.tsfem-form-validate .tsfem-form-setting-input input[type=color],.tsfem-form-validate .tsfem-form-setting-input input[type=date],.tsfem-form-validate .tsfem-form-setting-input input[type=hidden],.tsfem-form-validate .tsfem-form-setting-input input[type=month],.tsfem-form-validate .tsfem-form-setting-input input[type=number],.tsfem-form-validate .tsfem-form-setting-input input[type=password],.tsfem-form-validate .tsfem-form-setting-input input[type=range],.tsfem-form-validate .tsfem-form-setting-input input[type=search],.tsfem-form-validate .tsfem-form-setting-input input[type=tel],.tsfem-form-validate .tsfem-form-setting-input input[type=text],.tsfem-form-validate .tsfem-form-setting-input input[type=time],.tsfem-form-validate .tsfem-form-setting-input input[type=url],.tsfem-form-validate .tsfem-form-setting-input input[type=week]{display:inline-block;width:100%;max-width:unset}.tsfem-form-image-buttons-wrap{margin-top:1.15em;margin-left:1px;vertical-align:top;line-height:1.4;flex-grow:0}body.rtl .tsfem-form-image-buttons-wrap{margin-left:0;margin-right:1px}.tsfem-form-image-buttons-wrap button{margin-right:1.15em}body.rtl .tsfem-form-image-buttons-wrap button{margin-right:0;margin-left:1.15em}.tsfem-form-multi-select-wrap{max-height:350px;padding:0 15px;box-shadow:0 0 0 1px #ccd0d4;overflow:hidden;overflow-y:auto;position:relative}.tsfem-form-multi-select-wrap-row ul{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:flex-start}.tsfem-form-multi-select-wrap-row ul li{margin:7px 14px 7px 0}body.rtl .tsfem-form-multi-select-wrap-row ul li{margin:7px 0 7px 14px}.tsfem-form-multi-a11y-wrap .tsfem-form-multi-a11y-wrap{margin-left:20px}body.rtl .tsfem-form-multi-a11y-wrap .tsfem-form-multi-a11y-wrap{margin-left:0;margin-right:20px}.tsfem-form-iterator-timer{height:3px;width:100%;display:block;box-shadow:0 1px 3px 0 #ccc}.tsfem-form-iterator-timer span{height:3px;width:0%;max-width:100%;display:block;background:#057c99}.tsfem-form-iterator-timer-invalid span{background:#d14b44}.tsfem-form-checkbox-required{height:100%!important;width:0!important;min-width:0!important;margin:0!important;padding:0!important;pointer-events:none;position:absolute;opacity:0;top:0;left:0}body.rtl .tsfem-form-checkbox-required{left:unset;right:0}.tsfem-form-validate .tsfem-form-multi-invalid,.tsfem-form-validate .tsfem-form-setting-input input[type=color]:invalid,.tsfem-form-validate .tsfem-form-setting-input input[type=date]:invalid,.tsfem-form-validate .tsfem-form-setting-input input[type=hidden]:invalid,.tsfem-form-validate .tsfem-form-setting-input input[type=month]:invalid,.tsfem-form-validate .tsfem-form-setting-input input[type=number]:invalid,.tsfem-form-validate .tsfem-form-setting-input input[type=password]:invalid,.tsfem-form-validate .tsfem-form-setting-input input[type=range]:invalid,.tsfem-form-validate .tsfem-form-setting-input input[type=search]:invalid,.tsfem-form-validate .tsfem-form-setting-input input[type=tel]:invalid,.tsfem-form-validate .tsfem-form-setting-input input[type=text]:invalid,.tsfem-form-validate .tsfem-form-setting-input input[type=time]:invalid,.tsfem-form-validate .tsfem-form-setting-input input[type=url]:invalid,.tsfem-form-validate .tsfem-form-setting-input input[type=week]:invalid,.tsfem-form-validate .tsfem-form-setting-input select:invalid,.tsfem-form-validate .tsfem-form-setting-input textarea:invalid{border-left:3px solid #d14b44}.tsfem-form-validate .tsfem-form-multi-valid,.tsfem-form-validate .tsfem-form-setting-input input[type=color]:valid,.tsfem-form-validate .tsfem-form-setting-input input[type=date]:valid,.tsfem-form-validate .tsfem-form-setting-input input[type=hidden]:valid,.tsfem-form-validate .tsfem-form-setting-input input[type=month]:valid,.tsfem-form-validate .tsfem-form-setting-input input[type=number]:valid,.tsfem-form-validate .tsfem-form-setting-input input[type=password]:valid,.tsfem-form-validate .tsfem-form-setting-input input[type=range]:valid,.tsfem-form-validate .tsfem-form-setting-input input[type=search]:valid,.tsfem-form-validate .tsfem-form-setting-input input[type=tel]:valid,.tsfem-form-validate .tsfem-form-setting-input input[type=text]:valid,.tsfem-form-validate .tsfem-form-setting-input input[type=time]:valid,.tsfem-form-validate .tsfem-form-setting-input input[type=url]:valid,.tsfem-form-validate .tsfem-form-setting-input input[type=week]:valid,.tsfem-form-validate .tsfem-form-setting-input select:valid,.tsfem-form-validate .tsfem-form-setting-input textarea:valid{border-left:3px solid #008964}#tsfem-page-wrap .tsfem-form-validate .tsfem-form-setting-input input[type=color]:invalid:focus,#tsfem-page-wrap .tsfem-form-validate .tsfem-form-setting-input input[type=date]:invalid:focus,#tsfem-page-wrap .tsfem-form-validate .tsfem-form-setting-input input[type=hidden]:invalid:focus,#tsfem-page-wrap .tsfem-form-validate .tsfem-form-setting-input input[type=month]:invalid:focus,#tsfem-page-wrap .tsfem-form-validate .tsfem-form-setting-input input[type=number]:invalid:focus,#tsfem-page-wrap .tsfem-form-validate .tsfem-form-setting-input input[type=password]:invalid:focus,#tsfem-page-wrap .tsfem-form-validate .tsfem-form-setting-input input[type=range]:invalid:focus,#tsfem-page-wrap .tsfem-form-validate .tsfem-form-setting-input input[type=search]:invalid:focus,#tsfem-page-wrap .tsfem-form-validate .tsfem-form-setting-input input[type=tel]:invalid:focus,#tsfem-page-wrap .tsfem-form-validate .tsfem-form-setting-input input[type=text]:invalid:focus,#tsfem-page-wrap .tsfem-form-validate .tsfem-form-setting-input input[type=time]:invalid:focus,#tsfem-page-wrap .tsfem-form-validate .tsfem-form-setting-input input[type=url]:invalid:focus,#tsfem-page-wrap .tsfem-form-validate .tsfem-form-setting-input input[type=week]:invalid:focus,#tsfem-page-wrap .tsfem-form-validate .tsfem-form-setting-input select:invalid:focus,#tsfem-page-wrap .tsfem-form-validate .tsfem-form-setting-input textarea:invalid:focus{border-color:#0ebfe9} +.tsfem-form-collapse-wrap{width:100%}.tsfem-form-collapse{position:relative;width:100%;margin:0;padding:0;box-shadow:0 0 0 1px rgba(0,0,0,.18) inset;border-radius:4px}.tsfem-form-collapse-sub-wrap:not(:empty){margin-top:1em}.tsfem-form-collapse-sub-wrap.tsfem-form-multi-setting-input{margin-top:0}.tsfem-form-collapse-sub-wrap .tsfem-form-collapse{margin-bottom:1em}.tsfem-form-collapse-sub-wrap .tsfem-form-collapse:last-of-type{margin-bottom:0}.tsfem-form-collapse>input{position:absolute;width:0;height:0;opacity:0;margin:0!important;padding:0!important;border:0;-webkit-appearance:none;-moz-appearance:none;appearance:none}.tsfem-form-collapse>input:checked:before{content:none;float:none;margin:0;width:0}.tsfem-form-collapse-header{position:relative;width:100%;padding:1em;background:#fff;box-shadow:0 0 0 1px rgba(0,0,0,.18),0 0 0 0 rgba(0,0,0,.18) inset;align-items:center;cursor:pointer;z-index:1;transition:box-shadow .33s;border-radius:4px}.tsfem-form-collapse-header-good .tsfem-form-collapse-icon{color:#0cc34b}.tsfem-form-collapse-header-error .tsfem-form-collapse-icon{color:#dd3811}.tsfem-form-collapse-header:hover,.tsfem-form-collapse>input:focus+.tsfem-form-collapse-header{box-shadow:0 0 4px 1px rgba(0,0,0,.3),0 0 1px 0 rgba(0,0,0,.3) inset}.tsfem-form-collapse>input:active+.tsfem-form-collapse-header,.tsfem-forum-collapse-header:active{box-shadow:0 0 0 1px rgba(0,0,0,.3),0 0 2px 1px rgba(0,0,0,.18) inset}.tsfem-form-collapse>input:focus:not(:checked):not(:hover)+.tsfem-form-collapse-header{box-shadow:0 0 0 1px rgba(0,0,0,.18),0 0 0 0 rgba(0,0,0,.18) inset}.tsfem-form-collapse-title-wrap{line-height:1.15em;font-size:1.15em;padding:0;margin:0;vertical-align:middle}.tsfem-form-title-icon:before{display:inline-block;width:19px;line-height:inherit;font-family:dashicons;font-style:normal;font-weight:400;font-size:inherit;vertical-align:bottom;line-height:1em;content:"";margin-right:2px}body.rtl .tsfem-form-title-icon:before{margin-right:0;margin-left:2px}.tsfem-form-title-icon-good:before,.tsfem-form-title-icon-okay:before{color:#0cc34b;font-size:1.1em;content:"\f147";margin-left:-2px;margin-right:4px}body.rtl .tsfem-form-title-icon-good:before,body.rtl .tsfem-form-title-icon-okay:before{margin-left:4px;margin-right:-2px}.tsfem-form-title-icon-okay:before,.tsfem-form-title-icon-warning:before{color:#ffa01b}.tsfem-form-title-icon-warning:before{content:"\f227"}.tsfem-form-title-icon-bad:before,.tsfem-form-title-icon-error:before{color:#dd3811;content:"\f227"}.tsfem-form-title-icon-error:before{content:"\f153"}.tsfem-form-title-icon-unknown:before{content:"\f223"}.tsfem-form-collapse-icon{position:relative;max-width:18px;max-height:3px}.tsfem-form-collapse-icon:after,.tsfem-form-collapse-icon:before{content:"";position:absolute;width:6px;right:.5em;top:0;height:1px;border:1px solid;border-radius:3px;transition:all .5s ease-in-out;background:currentColor}body.rtl .tsfem-form-collapse-icon:after,body.rtl .tsfem-form-collapse-icon:before{right:unset;left:.5em}.tsfem-form-collapse-icon:before{transform:translate(-2px,0) rotate(-45deg)}.tsfem-form-collapse-icon:after{transform:translate(2px,0) rotate(45deg)}.tsfem-form-collapse>input[type=checkbox]:checked+.tsfem-form-collapse-header .tsfem-form-collapse-icon:before{transform:translate(-2px,0) rotate(45deg)}.tsfem-form-collapse>input[type=checkbox]:checked+.tsfem-form-collapse-header .tsfem-form-collapse-icon:after{transform:translate(2px,0) rotate(-45deg)}.tsfem-form-collapse-content{display:block;margin:0 auto;box-sizing:border-box;padding:calc(.5em + .6vw);animation:tsfemForm-fade-in .25s}.tsfem-form-collapse>input[type=checkbox]:checked~.tsfem-form-collapse-content{display:none}@keyframes tsfemForm-fade-in{from{opacity:0}to{opacity:1}}.tsfem-form-iterator-selector-wrap,.tsfem-form-iterator-setting,.tsfem-form-multi-setting,.tsfem-form-setting{width:100%;flex-direction:row}.tsfem-form-iterator-setting-input,.tsfem-form-iterator-setting-label,.tsfem-form-multi-setting-input,.tsfem-form-multi-setting-label,.tsfem-form-setting-input,.tsfem-form-setting-label{background-color:#fff;padding:1em;flex:10 1 350px;box-shadow:0 0 0 1px #ccd0d4}.tsfem-form-iterator-setting-label,.tsfem-form-multi-setting-label,.tsfem-form-setting-label{background-color:#f9f9f9;flex:2 1 200px;border-radius:3px}.tsfem-form-plain-settings-label{margin-bottom:1em;flex:1 1 100%}.tsfem-form-iterator-setting-label-item,.tsfem-form-multi-setting-label-item,.tsfem-form-setting-action,.tsfem-form-setting-label-item{flex-wrap:nowrap;flex-direction:row;justify-content:space-between;flex-grow:0;max-width:max-content}.tsfem-form-iterator-setting-input,.tsfem-form-multi-setting-input,.tsfem-form-setting-input,.tsfem-form-setting-label-inner-wrap{justify-content:center}.tsfem-form-iterator-selector-wrap .tsfem-form-setting-label-inner-wrap{will-change:contents}.tsfem-form-iterator-setting-label-inner-wrap,.tsfem-form-multi-setting-label-inner-wrap,.tsfem-form-select-multi-a11y-label-inner-wrap{justify-content:flex-start}.tsfem-form-iterator-setting-label-item>*,.tsfem-form-multi-setting-label-item>*,.tsfem-form-setting-label-item>*{margin-right:4px}.tsfem-form-iterator-setting-label-item>:last-child,.tsfem-form-multi-setting-label-item>:last-child,.tsfem-form-setting-label-item>:last-child{margin-right:0}.tsfem-form-setting-action{margin-top:1em}.tsfem-form-setting-input textarea{max-height:250px}.tsfem-form-option-title.tsfem-form-option-has-description{margin:0 0 .5em}.tsfem-form-option-description{margin:.5em 0}.tsfem-form-plain-settings-label .tsfem-form-option-description:first-of-type{margin-top:0}.tsfem-form-plain-settings-label .tsfem-form-option-description:last-of-type{margin-bottom:0}.tsfem-form-setting-input select,.tsfem-form-setting-input textarea,.tsfem-form-validate .tsfem-form-setting-input input[type=color],.tsfem-form-validate .tsfem-form-setting-input input[type=date],.tsfem-form-validate .tsfem-form-setting-input input[type=hidden],.tsfem-form-validate .tsfem-form-setting-input input[type=month],.tsfem-form-validate .tsfem-form-setting-input input[type=number],.tsfem-form-validate .tsfem-form-setting-input input[type=password],.tsfem-form-validate .tsfem-form-setting-input input[type=range],.tsfem-form-validate .tsfem-form-setting-input input[type=search],.tsfem-form-validate .tsfem-form-setting-input input[type=tel],.tsfem-form-validate .tsfem-form-setting-input input[type=text],.tsfem-form-validate .tsfem-form-setting-input input[type=time],.tsfem-form-validate .tsfem-form-setting-input input[type=url],.tsfem-form-validate .tsfem-form-setting-input input[type=week]{display:inline-block;width:100%;max-width:unset}.tsfem-form-image-buttons-wrap{margin-top:1.15em;margin-left:1px;vertical-align:top;line-height:1.4;flex-grow:0}body.rtl .tsfem-form-image-buttons-wrap{margin-left:0;margin-right:1px}.tsfem-form-image-buttons-wrap button{margin-right:1.15em}body.rtl .tsfem-form-image-buttons-wrap button{margin-right:0;margin-left:1.15em}.tsfem-form-multi-select-wrap{max-height:448px;padding:0 15px;box-shadow:0 0 0 1px #ccd0d4;overflow:hidden;overflow-y:auto;position:relative}.tsfem-form-multi-select-wrap-row ul{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:flex-start}.tsfem-form-multi-select-wrap-row ul li{margin:7px 14px 7px 0}body.rtl .tsfem-form-multi-select-wrap-row ul li{margin:7px 0 7px 14px}.tsfem-form-multi-a11y-wrap .tsfem-form-multi-a11y-wrap{margin-left:20px}body.rtl .tsfem-form-multi-a11y-wrap .tsfem-form-multi-a11y-wrap{margin-left:0;margin-right:20px}.tsfem-form-iterator-timer{height:3px;width:100%;display:block;box-shadow:0 1px 3px 0 #ccc}.tsfem-form-iterator-timer span{height:3px;width:0%;max-width:100%;display:block;background:#057c99}.tsfem-form-iterator-timer-invalid span{background:#d14b44}.tsfem-form-checkbox-required{height:100%!important;width:0!important;min-width:0!important;margin:0!important;padding:0!important;pointer-events:none;position:absolute;opacity:0;top:0;left:0}body.rtl .tsfem-form-checkbox-required{left:unset;right:0}.tsfem-form-validate .tsfem-form-multi-invalid,.tsfem-form-validate .tsfem-form-setting-input input[type=color]:invalid,.tsfem-form-validate .tsfem-form-setting-input input[type=date]:invalid,.tsfem-form-validate .tsfem-form-setting-input input[type=hidden]:invalid,.tsfem-form-validate .tsfem-form-setting-input input[type=month]:invalid,.tsfem-form-validate .tsfem-form-setting-input input[type=number]:invalid,.tsfem-form-validate .tsfem-form-setting-input input[type=password]:invalid,.tsfem-form-validate .tsfem-form-setting-input input[type=range]:invalid,.tsfem-form-validate .tsfem-form-setting-input input[type=search]:invalid,.tsfem-form-validate .tsfem-form-setting-input input[type=tel]:invalid,.tsfem-form-validate .tsfem-form-setting-input input[type=text]:invalid,.tsfem-form-validate .tsfem-form-setting-input input[type=time]:invalid,.tsfem-form-validate .tsfem-form-setting-input input[type=url]:invalid,.tsfem-form-validate .tsfem-form-setting-input input[type=week]:invalid,.tsfem-form-validate .tsfem-form-setting-input select:invalid,.tsfem-form-validate .tsfem-form-setting-input textarea:invalid{border-left:3px solid #d14b44}.tsfem-form-validate .tsfem-form-multi-valid,.tsfem-form-validate .tsfem-form-setting-input input[type=color]:valid,.tsfem-form-validate .tsfem-form-setting-input input[type=date]:valid,.tsfem-form-validate .tsfem-form-setting-input input[type=hidden]:valid,.tsfem-form-validate .tsfem-form-setting-input input[type=month]:valid,.tsfem-form-validate .tsfem-form-setting-input input[type=number]:valid,.tsfem-form-validate .tsfem-form-setting-input input[type=password]:valid,.tsfem-form-validate .tsfem-form-setting-input input[type=range]:valid,.tsfem-form-validate .tsfem-form-setting-input input[type=search]:valid,.tsfem-form-validate .tsfem-form-setting-input input[type=tel]:valid,.tsfem-form-validate .tsfem-form-setting-input input[type=text]:valid,.tsfem-form-validate .tsfem-form-setting-input input[type=time]:valid,.tsfem-form-validate .tsfem-form-setting-input input[type=url]:valid,.tsfem-form-validate .tsfem-form-setting-input input[type=week]:valid,.tsfem-form-validate .tsfem-form-setting-input select:valid,.tsfem-form-validate .tsfem-form-setting-input textarea:valid{border-left:3px solid #008964}#tsfem-page-wrap .tsfem-form-validate .tsfem-form-setting-input input[type=color]:invalid:focus,#tsfem-page-wrap .tsfem-form-validate .tsfem-form-setting-input input[type=date]:invalid:focus,#tsfem-page-wrap .tsfem-form-validate .tsfem-form-setting-input input[type=hidden]:invalid:focus,#tsfem-page-wrap .tsfem-form-validate .tsfem-form-setting-input input[type=month]:invalid:focus,#tsfem-page-wrap .tsfem-form-validate .tsfem-form-setting-input input[type=number]:invalid:focus,#tsfem-page-wrap .tsfem-form-validate .tsfem-form-setting-input input[type=password]:invalid:focus,#tsfem-page-wrap .tsfem-form-validate .tsfem-form-setting-input input[type=range]:invalid:focus,#tsfem-page-wrap .tsfem-form-validate .tsfem-form-setting-input input[type=search]:invalid:focus,#tsfem-page-wrap .tsfem-form-validate .tsfem-form-setting-input input[type=tel]:invalid:focus,#tsfem-page-wrap .tsfem-form-validate .tsfem-form-setting-input input[type=text]:invalid:focus,#tsfem-page-wrap .tsfem-form-validate .tsfem-form-setting-input input[type=time]:invalid:focus,#tsfem-page-wrap .tsfem-form-validate .tsfem-form-setting-input input[type=url]:invalid:focus,#tsfem-page-wrap .tsfem-form-validate .tsfem-form-setting-input input[type=week]:invalid:focus,#tsfem-page-wrap .tsfem-form-validate .tsfem-form-setting-input select:invalid:focus,#tsfem-page-wrap .tsfem-form-validate .tsfem-form-setting-input textarea:invalid:focus{border-color:#0ebfe9} diff --git a/lib/css/tsfem-manager.css b/lib/css/tsfem-manager.css index 40e227ee..fbe66f89 100644 --- a/lib/css/tsfem-manager.css +++ b/lib/css/tsfem-manager.css @@ -91,7 +91,7 @@ .tsfem-extension-entry .tsfem-extension-title:after { content: "\2022\00fe0e"; - margin-left: .5em; + margin-left: .75ch; font-weight: 400; font-size: 1em; color: transparent; @@ -99,7 +99,7 @@ } body.rtl .tsfem-extension-entry .tsfem-extension-title:after { margin-left: initial; - margin-right: .5em; + margin-right: .75ch; } .tsfem-extension-entry.tsfem-extension-activated .tsfem-extension-title:after { diff --git a/lib/css/tsfem-manager.min.css b/lib/css/tsfem-manager.min.css index 7b1b0718..d017732e 100644 --- a/lib/css/tsfem-manager.min.css +++ b/lib/css/tsfem-manager.min.css @@ -1 +1 @@ -.tsfem-extension-entry-wrap{padding:0;margin:0;width:100%}.tsfem-extension-entry-wrap{margin-bottom:1.5em}.tsfem-extension-entry-wrap:last-of-type{margin-bottom:0}.tsfem-extension-entry{margin:0;padding:.33em;min-height:122px;border-radius:6px;border:1px solid #ccd0d4;align-self:auto;justify-content:center}.tsfem-extension-entry>*{min-height:100px;margin:0;padding:0;box-sizing:border-box;display:flex;flex-wrap:wrap;flex-direction:column;width:auto;justify-content:flex-start}.tsfem-extension-icon-wrap{flex-grow:0;flex-basis:120px;align-self:center;padding:1em}.tsfem-extension-entry-icon{fill:#333;color:#333;transition:color .5s}.tsfem-extension-activated .tsfem-extension-entry-icon{color:#0ebfe9}.tsfem-extension-about-wrap{flex-direction:column;justify-content:space-around;padding:.667em 1em;flex-grow:1;flex-basis:170px}.tsfem-extension-header{gap:1ch}.tsfem-extension-description-wrap{flex-grow:3;flex-basis:280px}.tsfem-extension-description-footer{display:block;width:100%;white-space:pre-line}.tsfem-extension-title{font-size:1.33em;word-break:break-word;font-weight:600;margin:0;padding:0;transition:color .5s}.tsfem-extension-activated .tsfem-extension-title{color:#0ebfe9}.tsfem-extension-entry .tsfem-extension-title:after{content:"\2022\00fe0e";margin-left:.5em;font-weight:400;font-size:1em;color:transparent;transition:color .5s}body.rtl .tsfem-extension-entry .tsfem-extension-title:after{margin-left:initial;margin-right:.5em}.tsfem-extension-entry.tsfem-extension-activated .tsfem-extension-title:after{color:currentColor}.tsfem-extension-type{margin:0;padding:0;font-size:.83em}.tsfem-extension-subheader{padding:.667em 0}.tsfem-extension-action input,.tsfem-extension-action>*{width:100%;text-align:center}.tsfem-extension-party:before{display:inline-block;width:1em;line-height:1;font-family:dashicons;font-style:normal;font-weight:400;font-size:1.225em;vertical-align:middle;content:"";margin-right:.333em}body.rtl .tsfem-extension-party:before{margin-right:initial;margin-left:.333em}.tsfem-extension-first-party-icon:before{content:"\f338"}.tsfem-extension-third-party-icon:before{content:"\f307"}.tsfem-extension-description{padding:1em}.tsfem-extension-description-header{padding-bottom:1em}.tsfem-feed-wrap{align-self:flex-start}.tsfem-enable-feed-button{flex-basis:50%;max-width:300px}.tsfem-feed-entry,.tsfem-feed-top{width:100%;margin:0;padding:0;overflow:hidden}.tsfem-feed-content,.tsfem-feed-entry,.tsfem-feed-top{margin-bottom:1em}.tsfem-feed-top{word-wrap:break-word;overflow-wrap:break-word;white-space:pre-wrap;overflow-wrap:normal;align-items:baseline}.tsfem-feed-top h4{white-space:pre-wrap;margin:0;padding:0}.tsfem-feed-top time{white-space:pre;white-space:nowrap;word-wrap:break-word;overflow-wrap:break-word;word-spacing:-1px;font-size:.9em;margin:0 7px;padding:0;font-style:oblique}.tsfem-feed-entry{border-bottom:1px solid #dedede;margin:0 -1em 1em;padding:0 1em}.tsfem-feed-entry:last-of-type,.tsfem-feed-entry:last-of-type .tsfem-feed-content{margin-bottom:0;border-bottom:none}#tsfem-page-wrap #input-activation{width:100%;margin:0;padding:0}#tsfem-page-wrap #input-activation input{margin-left:0;margin-right:0;width:100%;font-size:13px}#tsfem-page-wrap #input-activation input{margin:.2em 0}#tsfem-page-wrap #input-activation input:first-child{margin-top:0}.tsfem-flip-hide-down,.tsfem-flip-hide-up{transform:rotateX(90deg) translateY(.5em);transform-origin:0 100% 0;opacity:0;transition:transform .25s,opacity .25s}.tsfem-flip-hide-down{transform:rotateX(-90deg) translateY(-2em)}.tsfem-flip-show-down,.tsfem-flip-show-up{transform:rotateX(0) translateY(0);opacity:1;transition:transform .25s,opacity .125s} +.tsfem-extension-entry-wrap{padding:0;margin:0;width:100%}.tsfem-extension-entry-wrap{margin-bottom:1.5em}.tsfem-extension-entry-wrap:last-of-type{margin-bottom:0}.tsfem-extension-entry{margin:0;padding:.33em;min-height:122px;border-radius:6px;border:1px solid #ccd0d4;align-self:auto;justify-content:center}.tsfem-extension-entry>*{min-height:100px;margin:0;padding:0;box-sizing:border-box;display:flex;flex-wrap:wrap;flex-direction:column;width:auto;justify-content:flex-start}.tsfem-extension-icon-wrap{flex-grow:0;flex-basis:120px;align-self:center;padding:1em}.tsfem-extension-entry-icon{fill:#333;color:#333;transition:color .5s}.tsfem-extension-activated .tsfem-extension-entry-icon{color:#0ebfe9}.tsfem-extension-about-wrap{flex-direction:column;justify-content:space-around;padding:.667em 1em;flex-grow:1;flex-basis:170px}.tsfem-extension-header{gap:1ch}.tsfem-extension-description-wrap{flex-grow:3;flex-basis:280px}.tsfem-extension-description-footer{display:block;width:100%;white-space:pre-line}.tsfem-extension-title{font-size:1.33em;word-break:break-word;font-weight:600;margin:0;padding:0;transition:color .5s}.tsfem-extension-activated .tsfem-extension-title{color:#0ebfe9}.tsfem-extension-entry .tsfem-extension-title:after{content:"\2022\00fe0e";margin-left:.75ch;font-weight:400;font-size:1em;color:transparent;transition:color .5s}body.rtl .tsfem-extension-entry .tsfem-extension-title:after{margin-left:initial;margin-right:.75ch}.tsfem-extension-entry.tsfem-extension-activated .tsfem-extension-title:after{color:currentColor}.tsfem-extension-type{margin:0;padding:0;font-size:.83em}.tsfem-extension-subheader{padding:.667em 0}.tsfem-extension-action input,.tsfem-extension-action>*{width:100%;text-align:center}.tsfem-extension-party:before{display:inline-block;width:1em;line-height:1;font-family:dashicons;font-style:normal;font-weight:400;font-size:1.225em;vertical-align:middle;content:"";margin-right:.333em}body.rtl .tsfem-extension-party:before{margin-right:initial;margin-left:.333em}.tsfem-extension-first-party-icon:before{content:"\f338"}.tsfem-extension-third-party-icon:before{content:"\f307"}.tsfem-extension-description{padding:1em}.tsfem-extension-description-header{padding-bottom:1em}.tsfem-feed-wrap{align-self:flex-start}.tsfem-enable-feed-button{flex-basis:50%;max-width:300px}.tsfem-feed-entry,.tsfem-feed-top{width:100%;margin:0;padding:0;overflow:hidden}.tsfem-feed-content,.tsfem-feed-entry,.tsfem-feed-top{margin-bottom:1em}.tsfem-feed-top{word-wrap:break-word;overflow-wrap:break-word;white-space:pre-wrap;overflow-wrap:normal;align-items:baseline}.tsfem-feed-top h4{white-space:pre-wrap;margin:0;padding:0}.tsfem-feed-top time{white-space:pre;white-space:nowrap;word-wrap:break-word;overflow-wrap:break-word;word-spacing:-1px;font-size:.9em;margin:0 7px;padding:0;font-style:oblique}.tsfem-feed-entry{border-bottom:1px solid #dedede;margin:0 -1em 1em;padding:0 1em}.tsfem-feed-entry:last-of-type,.tsfem-feed-entry:last-of-type .tsfem-feed-content{margin-bottom:0;border-bottom:none}#tsfem-page-wrap #input-activation{width:100%;margin:0;padding:0}#tsfem-page-wrap #input-activation input{margin-left:0;margin-right:0;width:100%;font-size:13px}#tsfem-page-wrap #input-activation input{margin:.2em 0}#tsfem-page-wrap #input-activation input:first-child{margin-top:0}.tsfem-flip-hide-down,.tsfem-flip-hide-up{transform:rotateX(90deg) translateY(.5em);transform-origin:0 100% 0;opacity:0;transition:transform .25s,opacity .25s}.tsfem-flip-hide-down{transform:rotateX(-90deg) translateY(-2em)}.tsfem-flip-show-down,.tsfem-flip-show-up{transform:rotateX(0) translateY(0);opacity:1;transition:transform .25s,opacity .125s} diff --git a/lib/css/tsfem-ui.css b/lib/css/tsfem-ui.css index 6d82c34f..966925e4 100644 --- a/lib/css/tsfem-ui.css +++ b/lib/css/tsfem-ui.css @@ -4,14 +4,6 @@ /* # Start Normalize. /*----------------------------------------------------------------------------*/ -/* ## Notices: Annoyances. -/* We don't ever want to remove potentially non-recurring notices. -/*--------------------------------------*/ -body.tsfem .update-nag, -body.tsfem #update-nag { - display: none; -} - /* ## Notices: Core + self. /*--------------------------------------*/ /* "margin: 1.2% 0 0" Edge/Firefox inline margins issue */ @@ -19,8 +11,20 @@ body.tsfem #update-nag { #tsfem-page-wrap .error, #tsfem-page-wrap .warning, #tsfem-page-wrap .tsf-notice { - margin: 1.2vw 0 0; - box-shadow: 0 .1em .4em rgba(0,0,0,.18); + display: block; + margin: 1.2vw .6vw 0; + box-shadow: 0 0.1em 0.4em rgba(0, 0, 0, .18); + border-radius: 3px; +} + +/* ### Notices: Annoyances. +/* We don't ever want to remove potentially non-recurring notices. +/*--------------------------------------*/ +body.tsfem .update-nag, /* before displacement */ +body.tsfem #update-nag, /* before displacement */ +#tsfem-page-wrap .update-nag, /* after displacement */ +#tsfem-page-wrap #update-nag { /* after displacement */ + display: none; } /* ## Loading... @@ -142,12 +146,8 @@ body.tsfem.rtl .wrap { } #tsfem-notice-wrap { - margin: 1.2vw; - margin-bottom: 0; -} - -#tsfem-notice-wrap:empty { - margin: 0; + margin: 0 auto; + max-width: 1240px; } /* Stretches content over the page, pushing footer down. */ @@ -162,7 +162,7 @@ body.tsfem.rtl .wrap { margin-bottom: 1.2vw; top: 32px; z-index: 9980; /* Admin sidebar is 9990 */ - background: linear-gradient( to bottom, rgba(0,0,0,.25), rgba(241,241,241,0 ) ); + background: linear-gradient( to bottom, rgba(0,0,0,.3), rgba(0,0,0,0) ); /* Revealed when notices appear */ } @media screen and ( max-width: 782px ) { @@ -178,18 +178,23 @@ body.tsfem.rtl .wrap { @media screen and ( max-width: 600px ) { #tsfem-sticky-top { position: initial; /* unsticky */ + background: none; } } -.tsfem-top-wrap { +#tsfem-top-super-wrap { background-color: #fff; border-bottom: 1px solid #ccd0d4; width: 100%; - margin: 0; +} + +#tsfem-top-wrap { + margin: 0 auto; padding: 0 1.2vw; + max-width: 1240px; } -.tsfem-top-wrap > * { +#tsfem-top-wrap > * { padding: 1em 0; } @@ -221,7 +226,7 @@ body.tsfem.rtl .wrap { .tsfem-top-actions, .tsfem-top-about, -.tsfem-top-wrap .tsfem-title { +#tsfem-top-wrap .tsfem-title { text-align: left; align-items: center; } @@ -260,7 +265,7 @@ body.rtl .tsfem-top-actions > *:nth-last-child(n+2) { /* ## Title. /*--------------------------------------*/ -.tsfem-top-wrap .tsfem-title h1 { +#tsfem-top-wrap .tsfem-title h1 { display: flex; font-family: Verdana, Geneva, sans-serif; word-break: break-word; @@ -272,14 +277,37 @@ body.rtl .tsfem-top-actions > *:nth-last-child(n+2) { padding: 0; } -.tsfem-top-wrap .tsfem-title .tsfem-logo > svg { +#tsfem-top-wrap .tsfem-title .tsfem-logo > svg { padding: 0; - margin-right: 7px; + margin-right: .75ch; vertical-align: top; } -body.rtl .tsfem-top-wrap .tsfem-title .tsfem-logo > svg { +body.rtl #tsfem-top-wrap .tsfem-title .tsfem-logo > svg { margin-right: 0; - margin-left: 7px; + margin-left: .75ch; +} + +/* ##Logger +/*----------------------------------------------------------------------------*/ + +.tsfem-logger { + display: block; + padding: 1em 1em .5em; + margin: 0; + line-height: 1.5; + word-break: break-all; + word-wrap: break-word; + color: #333; + background-color: #f5f5f5; + border: 1px solid #ccc; + border-radius: 3px; + overflow: auto; + min-height: calc( 2.5em * 1.5 ); /* 2.5 rows time 1.5 line height */ + height: calc( 14em * 1.5 ); /* 14 rows times 1.5 line height */ + resize: vertical; + white-space: pre-wrap; + word-break: break-word; + clear: both; } /* # Start Buttons. @@ -332,7 +360,7 @@ body.rtl .tsfem-top-wrap .tsfem-title .tsfem-logo > svg { display: inline-block; box-shadow: 0 0 0.3em rgba(0,0,0,0), 0 0 0 rgba(0,0,0,0) inset; filter: none; - transition: box-shadow .25s, color .25s .125s, background .125s .25s; + transition: box-shadow .125s, color .125s .0625s, background .0625s .125s; padding: .4em 1em .5em; font-size: 1em; font-weight: 400; @@ -581,7 +609,7 @@ body.rtl .tsfem-button-loading:after { transform: rotateX(-90deg); transform-origin: 0 100% 0; opacity: 0; - transition: transform 1.2s, opacity 0.6s; + transition: transform 1.2s, opacity .6s; } .tsfem-switch-button-container input[type=checkbox]:checked:before { @@ -592,7 +620,7 @@ body.rtl .tsfem-button-loading:after { transform: rotateX(90deg) translateY(-100%); opacity: 0; z-index: 0; - transition: transform 1.2s, opacity 0.6s; + transition: transform 1.2s, opacity .6s; } .tsfem-switch-button-container input[type=checkbox]:checked ~ button { @@ -608,13 +636,17 @@ body.rtl .tsfem-button-loading:after { .tsfem-button:disabled, .tsfem-button:disabled:hover, .tsfem-button:disabled:active, +.tsfem-button:disabled:focus, .tsfem-button-primary:disabled, .tsfem-button-primary:disabled:hover, .tsfem-button-primary:disabled:active, +.tsfem-button-primary:disabled:focus, .tsfem-button-disabled, .tsfem-button-disabled:hover, .tsfem-button-disabled:active, +.tsfem-button-disabled:focus, .tsfem-button-disabled:disabled, +.tsfem-button-disabled:disabled:focus, .tsfem-button-disabled:disabled:hover, .tsfem-button-disabled:disabled:active { background: #ddd; @@ -704,6 +736,12 @@ body.rtl .tsfem-button-loading:after { /* # Global panes wrap. /*--------------------------------------*/ /* "margin: .6%" Edge/Firefox inline margins issue */ +.tsfem-panes-super-wrap { + width: 100%; + max-width: 1240px; + margin: 0 auto; +} + .tsfem-panes-wrap { padding: 0; margin: 0 1.2vw; @@ -808,12 +846,12 @@ body.rtl .tsfem-button-loading:after { color: #057f9c; font-weight: 400; justify-content: flex-start; - flex-basis: 200px; + width: max-content; } .tsfem-pane-header svg, .tsfem-pane-header img { - margin-right: .333em; + margin-right: .75ch; } .tsfem-pane-content h4.tsfem-status-title, @@ -836,13 +874,13 @@ body.rtl .tsfem-button-loading:after { padding: 0; } -.tsfem-pane-inner-collapsable-settings-wrap .tsfem-pane-inner-pad { +.tsfem-pane-inner-pad { width: 100%; padding: 1.5em; } /* Grab every consecutive call */ -.tsfem-pane-inner-collapsable-settings-wrap .tsfem-pane-inner-pad + .tsfem-pane-inner-pad { +.tsfem-pane-inner-pad + .tsfem-pane-inner-pad { padding-top: 0; } @@ -1175,8 +1213,6 @@ h4.tsfem-cp-title { cursor: pointer; padding: 1em; width: 100%; - -moz-transition: all .33s; - -o-transition: all .33s; transition: all .33s; -webkit-user-select: none; -khtml-user-select: none; @@ -1220,7 +1256,7 @@ body.rtl .tsfem-modal-buttons button:nth-child(n+2) { cursor: pointer; text-decoration: none; z-index: 6; - transition: color 0.15s; + transition: color .15s; } body.rtl .tsfem-modal-dismiss { @@ -1260,12 +1296,3 @@ body.rtl .tsfem-modal-dismiss { .tsfem-ltr { direction: ltr; } - -body.js .tsfem-flex-hide-if-js, -.tsfem-flex-hide-if-no-js { - display: none; -} - -body.js .tsfem-flex-hide-if-no-js { - display: flex; -} diff --git a/lib/css/tsfem-ui.min.css b/lib/css/tsfem-ui.min.css index 5ede8921..757382a0 100644 --- a/lib/css/tsfem-ui.min.css +++ b/lib/css/tsfem-ui.min.css @@ -1 +1 @@ -body.tsfem #update-nag,body.tsfem .update-nag{display:none}#tsfem-page-wrap .error,#tsfem-page-wrap .notice,#tsfem-page-wrap .tsf-notice,#tsfem-page-wrap .warning{margin:1.2vw 0 0;box-shadow:0 .1em .4em rgba(0,0,0,.18)}.tsfem-flex-status-loading>span{display:inline-block;font-size:30px;text-align:center}.tsfem-flex-status-loading>span:after{display:inline-block;width:1em;content:"\f463";font-size:1em;line-height:1em;font-family:dashicons;text-decoration:inherit;font-weight:400;font-style:normal;vertical-align:middle;color:#0ebfe9;line-height:1em;font-size:1em;animation:tsfem-spin 1.5s linear infinite}.tsfem-disable-cursor{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.tsfem-question-cursor{cursor:help}.tsfem-flex{box-sizing:border-box;display:flex;flex:1 1 auto;flex-wrap:wrap;flex-direction:column;justify-content:flex-start}.tsfem-flex-rtl{justify-content:flex-start}.tsfem-flex-row{flex-direction:row}.tsfem-flex-nowrap{flex-wrap:nowrap}.tsfem-flex-grow{flex-grow:1}.tsfem-flex-nogrow{flex:0 1 auto}.tsfem-flex-noshrink{flex:1 0 auto}.tsfem-flex-nogrowshrink{flex:0 0 auto}.tsfem-flex-space{justify-content:space-between}.tsfem-flex-center{justify-content:center}.tsfem-flex-end{justify-content:flex-end}body.tsfem #wpbody-content{padding-bottom:0}body.tsfem .wrap{display:grid;grid-auto-rows:max-content minmax(max-content,1fr) max-content;min-height:calc(100vh - 32px);padding:0;margin:0;margin-left:-20px}body.tsfem.rtl .wrap{margin-left:0;margin-right:-20px}#tsfem-notice-wrap{margin:1.2vw;margin-bottom:0}#tsfem-notice-wrap:empty{margin:0}.tsfem-connect-wrap{flex:1 1 auto;margin:1.2vw}#tsfem-sticky-top{position:-webkit-sticky;position:sticky;margin-bottom:1.2vw;top:32px;z-index:9980;background:linear-gradient(to bottom,rgba(0,0,0,.25),rgba(241,241,241,0))}@media screen and (max-width:782px){body.tsfem .wrap{margin-left:-10px}#tsfem-sticky-top{top:46px}}@media screen and (max-width:600px){#tsfem-sticky-top{position:initial}}.tsfem-top-wrap{background-color:#fff;border-bottom:1px solid #ccd0d4;width:100%;margin:0;padding:0 1.2vw}.tsfem-top-wrap>*{padding:1em 0}.tsfem-footer-wrap{padding:calc(1em + .6vw) 0;margin:0 1.2vw;overflow:hidden;white-space:nowrap;text-align:center}.tsfem-flex-textarea-wrap{flex-direction:row;min-width:100%;width:100%;flex:0 0 auto}.tsfem-flex-textarea-wrap textarea{display:block;min-width:100%}.tsfem-top-about,.tsfem-top-actions,.tsfem-top-wrap .tsfem-title{text-align:left;align-items:center}.tsfem-top-about,.tsfem-top-actions{justify-content:flex-end;min-width:200px}.tsfem-top-about{flex:1 1 50%}.tsfem-top-actions>div{display:inline-block;white-space:pre}.tsfem-top-actions>:nth-last-child(n+2){margin-right:1em}body.rtl .tsfem-top-actions>:nth-last-child(n+2){margin-right:0;margin-left:1em}.tsfem-top-about>div{display:inline-block;text-align:justify;max-width:calc(100% - 1.2em - 1em);color:#333;letter-spacing:.2px}.tsfem-top-wrap .tsfem-title h1{display:flex;font-family:Verdana,Geneva,sans-serif;word-break:break-word;font-size:1.9em;font-weight:400;line-height:1.1em;color:#333;margin:0;padding:0}.tsfem-top-wrap .tsfem-title .tsfem-logo>svg{padding:0;margin-right:7px;vertical-align:top}body.rtl .tsfem-top-wrap .tsfem-title .tsfem-logo>svg{margin-right:0;margin-left:7px}#tsfem-page-wrap input[type=checkbox]:focus,#tsfem-page-wrap input[type=color]:focus,#tsfem-page-wrap input[type=date]:focus,#tsfem-page-wrap input[type=datetime-local]:focus,#tsfem-page-wrap input[type=datetime]:focus,#tsfem-page-wrap input[type=email]:focus,#tsfem-page-wrap input[type=month]:focus,#tsfem-page-wrap input[type=number]:focus,#tsfem-page-wrap input[type=password]:focus,#tsfem-page-wrap input[type=radio]:focus,#tsfem-page-wrap input[type=search]:focus,#tsfem-page-wrap input[type=tel]:focus,#tsfem-page-wrap input[type=text]:focus,#tsfem-page-wrap input[type=time]:focus,#tsfem-page-wrap input[type=url]:focus,#tsfem-page-wrap input[type=week]:focus,#tsfem-page-wrap select:focus,#tsfem-page-wrap textarea:focus{border-color:#057f9c;box-shadow:0 0 2px rgba(66,144,183,.8)}#tsfem-page-wrap .tsf-remove-image-button,#tsfem-page-wrap .tsf-remove-image-button:active,#tsfem-page-wrap .tsf-remove-image-button:focus,#tsfem-page-wrap .tsf-remove-image-button:hover,#tsfem-page-wrap .tsf-set-image-button,#tsfem-page-wrap .tsf-set-image-button:active,#tsfem-page-wrap .tsf-set-image-button:focus,#tsfem-page-wrap .tsf-set-image-button:hover,.tsfem-button,.tsfem-button-primary,.tsfem-button-primary:active,.tsfem-button-primary:focus,.tsfem-button-primary:hover,.tsfem-button:active,.tsfem-button:focus,.tsfem-button:hover{display:inline-block;box-shadow:0 0 .3em transparent,0 0 0 transparent inset;filter:none;transition:box-shadow .25s,color .25s .125s,background .125s .25s;padding:.4em 1em .5em;font-size:1em;font-weight:400;line-height:1em;text-decoration:none;text-align:center;cursor:pointer;outline:0;color:#444;background:#f6f8f9;border:1px solid currentColor;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:3px}.tsfem-button,.tsfem-button:active,.tsfem-button:focus,.tsfem-button:hover{color:#666}.tsfem-button-red,.tsfem-button-red:focus,.tsfem-button-red:hover{color:#dd3811}#tsfem-page-wrap .tsf-set-image-button,#tsfem-page-wrap .tsf-set-image-button:active,#tsfem-page-wrap .tsf-set-image-button:focus,#tsfem-page-wrap .tsf-set-image-button:hover,.tsfem-button-primary,.tsfem-button-primary:active,.tsfem-button-primary:focus,.tsfem-button-primary:hover{background:#05809e;color:#fff}#tsfem-page-wrap .tsf-set-image-button:focus,#tsfem-page-wrap .tsf-set-image-button:hover,.tsfem-button-primary:focus,.tsfem-button-primary:hover,.tsfem-button:focus,.tsfem-button:hover{box-shadow:0 .1em .5em rgba(0,0,0,.3),0 0 0 transparent inset}#tsfem-page-wrap .tsf-set-image-button:active,.tsfem-button-primary:active,.tsfem-button:active{box-shadow:0 0 .3em transparent,0 0 .3em 0 rgba(0,0,0,.3) inset;transition-duration:.15s}.tsfem-button-small,.tsfem-button-small:active,.tsfem-button-small:focus,.tsfem-button-small:hover{font-size:.9em;padding:.5em 1em}.tsfem-button-primary-bright,.tsfem-button-primary-bright:focus,.tsfem-button-primary-bright:hover{background:#0ebfe9}.tsfem-button-primary-dark,.tsfem-button-primary-dark:focus,.tsfem-button-primary-dark:hover{background:#535353}.tsfem-button-clipboard:after,.tsfem-button-cloud:after,.tsfem-button-download:after,.tsfem-button-external:after,.tsfem-button-flag:after,.tsfem-button-image:after,.tsfem-button-index:after,.tsfem-button-loading:after,.tsfem-button-love:after,.tsfem-button-star:after,.tsfem-button-upload:after,.tsfem-button-warning:after{display:inline-block;width:1em;height:1em;line-height:1em;font-size:1em;content:"\f155";font-family:dashicons;text-decoration:inherit;font-weight:400;font-style:normal;vertical-align:top;margin-left:.5em}body.rtl .tsfem-button-clipboard:after,body.rtl .tsfem-button-cloud:after,body.rtl .tsfem-button-download:after,body.rtl .tsfem-button-external:after,body.rtl .tsfem-button-flag:after,body.rtl .tsfem-button-image:after,body.rtl .tsfem-button-index:after,body.rtl .tsfem-button-loading:after,body.rtl .tsfem-button-love:after,body.rtl .tsfem-button-star:after,body.rtl .tsfem-button-upload:after,body.rtl .tsfem-button-warning:after{margin-left:0;margin-right:.5em}.tsfem-button-small:after{font-size:.9em}.tsfem-button-flag:after{content:"\f227"}.tsfem-button-warning:after{content:"\f534"}.tsfem-button-love:after{content:"\f487"}.tsfem-button-upload:after{content:"\f317"}.tsfem-button-download:after{content:"\f316"}.tsfem-button-clipboard:after{content:"\f481"}.tsfem-button-index:after{content:"\f510"}.tsfem-button-cloud:after{content:"\f176"}.tsfem-button-external:after{content:"\f504"}.tsfem-button-image:after{content:"\f128"}.tsfem-button-loading:after{content:"\f463";-moz-animation:tsfem-spin 1.5s linear infinite;-o-animation:tsfem-spin 1.5s linear infinite;animation:tsfem-spin 1.5s linear infinite}.tsfem-switch-button-container-wrap{display:inline-block;perspective:800px;perspective-origin:50%}.tsfem-switch-button-container{position:relative;margin:0;width:140px;height:2em;padding:0;display:block}.tsfem-switch-button-container input[type=checkbox]{position:absolute;width:0;height:0;opacity:0;margin:0;padding:0;border:0;z-index:-1;-webkit-appearance:none;-moz-appearance:none;appearance:none}.tsfem-switch-button-container button,.tsfem-switch-button-container label{position:absolute;left:0;top:0;text-align:center;transition:transform 1s,opacity .5s;z-index:1;width:100%;display:block;width:140px;box-sizing:border-box}.tsfem-switch-button-container input[type=checkbox]~button{visibility:hidden}.tsfem-switch-button-container input[type=checkbox]+label{z-index:2;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.tsfem-switch-button-container input[type=checkbox]:focus+label{background:#eee;color:#666;box-shadow:0 .2em 1em rgba(0,0,0,.3),0 0 0 transparent inset}.tsfem-switch-button-container button{transform:rotateX(-90deg);transform-origin:0 100% 0;opacity:0;transition:transform 1.2s,opacity .6s}.tsfem-switch-button-container input[type=checkbox]:checked:before{content:none}.tsfem-switch-button-container input[type=checkbox]:checked+label{transform:rotateX(90deg) translateY(-100%);opacity:0;z-index:0;transition:transform 1.2s,opacity .6s}.tsfem-switch-button-container input[type=checkbox]:checked~button{transform:rotateX(0);opacity:1;z-index:2;transition:transform 1s,opacity .5s;visibility:visible}.tsfem-button-disabled,.tsfem-button-disabled:active,.tsfem-button-disabled:disabled,.tsfem-button-disabled:disabled:active,.tsfem-button-disabled:disabled:hover,.tsfem-button-disabled:hover,.tsfem-button-primary:disabled,.tsfem-button-primary:disabled:active,.tsfem-button-primary:disabled:hover,.tsfem-button:disabled,.tsfem-button:disabled:active,.tsfem-button:disabled:hover{background:#ddd;color:#aaa;font-weight:400;box-shadow:none;cursor:not-allowed;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.tsfem-about-activation:before{display:inline-block;color:#0ebfe9;content:"\f112";font-size:1.2em;line-height:1.2em;font-family:dashicons;text-decoration:inherit;font-weight:400;font-style:normal;margin:0 7px;vertical-align:baseline;white-space:pre-wrap}.tsfem-connect-option{width:100%;max-width:690px;padding:1em 1.5em;margin:0 auto 1.2vw;box-sizing:border-box;background:#fff;border:1px solid #ccd0d4;border-radius:4px}.tsfem-connect-row{align-items:center;justify-content:space-between}.tsfem-connect-option:last-of-type{margin-bottom:0}.tsfem-connect-action,.tsfem-connect-text{margin:.5em 0;flex:1 1 49%;min-width:175px;padding:3px}.tsfem-connect-action form{margin:0;padding:0}.tsfem-connect-description>*{margin:0 0 1em;display:block}.tsfem-connect-description>:last-child{margin-bottom:0}.tsfem-connect-wrap h3{font-size:1.66em;color:#057f9c;font-weight:400}.tsfem-panes-wrap{padding:0;margin:0 1.2vw;display:grid;grid-template:repeat(auto-fit,minmax(auto,310px))/repeat(auto-fit,minmax(310px,1fr));grid-template:repeat(auto-fit,minmax(max-content,310px))/repeat(auto-fit,minmax(310px,1fr));grid-gap:1.2vw;grid-auto-flow:row dense;align-self:flex-start;align-self:start}.tsfem-pane-section{margin-bottom:2em}.tsfem-pane-section:last-of-type{margin-bottom:0}.tsfem-pane{height:100%;height:max-content;padding:0}@media screen and (min-width:660px){.tsfem-pane{grid-column:auto/1 span;grid-row:auto/1 span}.tsfem-pane-tall{grid-row:auto/2 span}.tsfem-pane-wide{grid-column:auto/2 span}.tsfem-pane-full{grid-row:auto/2 span;grid-column:auto/2 span}}.tsfem-pane-wrap{width:100%;height:max-content;background:#fff;border:1px solid #ccd0d4;border-radius:4px}.tsfem-pane-header{z-index:5}.tsfem-pane-content,.tsfem-pane-header{margin:0;padding:1em 1.5em}.tsfem-pane-content{padding:0;border-radius:4px;height:100%}.tsfem-pane-header>*{font-size:1em;line-height:1em;margin:0;padding:0;box-sizing:border-box;display:flex;flex:1 1 auto;flex-direction:row;align-items:center;justify-content:flex-end}.tsfem-pane-header .tsfem-ajax{max-width:initial;font-size:1em;word-break:break-word;text-align:right;flex:0 1 auto;margin-left:.5em}.tsfem-pane-header h3{font-size:1.33em;color:#057f9c;font-weight:400;justify-content:flex-start;flex-basis:200px}.tsfem-pane-header img,.tsfem-pane-header svg{margin-right:.333em}.tsfem-pane-content h4.tsfem-status-title,.tsfem-pane-content h5.tsfem-status-title,.tsfem-pane-content h6.tsfem-status-title{margin:0}.tsfem-pane-inner-wrap{background:#fff;box-sizing:border-box;padding:1.5em;height:auto;max-height:100%;width:100%;border-radius:4px}.tsfem-pane-inner-collapsable-settings-wrap{padding:0}.tsfem-pane-inner-collapsable-settings-wrap .tsfem-pane-inner-pad{width:100%;padding:1.5em}.tsfem-pane-inner-collapsable-settings-wrap .tsfem-pane-inner-pad+.tsfem-pane-inner-pad{padding-top:0}.tsfem-pane-footer-wrap{margin:0;padding:1em 1.5em;border-top:1px solid rgba(0,0,0,.18);z-index:5}.tsfem-pane-footer-wrap>:nth-last-child(n+2){margin-right:1em}body.rtl .tsfem-pane-footer-wrap>:nth-last-child(n+2){margin-right:0;margin-left:1em}.tsfem-flex-account-info-rows,.tsfem-flex-account-setting-rows{line-height:1.625em;max-width:400px}.tsfem-actions-account-info-title{font-weight:600}h4.tsfem-action-title,h4.tsfem-cp-title,h4.tsfem-form-title,h4.tsfem-info-title,h4.tsfem-status-title,h4.tsfem-support-title{padding:0;margin:0 0 1em;font-size:1.16em;font-weight:400;color:#057f9c}.tsfem-account-upgrade,.tsfem-cp-buttons,.tsfem-support-buttons{max-width:300px}.tsfem-cp-buttons,.tsfem-support-buttons{margin-bottom:.8em;flex-basis:50%}.tsfem-cp-buttons:last-of-type,.tsfem-support-buttons:last-of-type{margin-bottom:0}.tsfem-cp-buttons>a,.tsfem-cp-buttons>form,.tsfem-support-buttons>a{cursor:pointer;min-width:50%;display:inline-block}.tsfem-cp-buttons button{width:100%}.tsfem-description{font-size:.93em;font-style:oblique;text-indent:.5em}.tsfem-ajax:after,.tsfem-dashicon:after{display:inline-block;line-height:1;font-family:dashicons;font-style:normal;font-weight:400;width:1em;vertical-align:baseline;content:"";margin-left:.333em}.tsfem-ajax:after{font-size:1.225em;line-height:.816em}.tsfem-dashicon:after{font-size:1.2em;vertical-align:text-bottom;margin-left:2px}.tsfem-ajax.tsfem-loading:after{content:"\f463";color:#057c99;animation:tsfem-spin 1.5s linear infinite}.tsfem-dashicon-fadeout-3000:after{animation:tsfem-fadeout 3s linear 1}.tsfem-ajax.tsfem-error:after,.tsfem-dashicon.tsfem-error:after{content:"\f158";color:#dd3811}.tsfem-ajax.tsfem-success:after,.tsfem-dashicon.tsfem-success:after{content:"\f147";color:#0cc34b}.tsfem-ajax.tsfem-unknown:after,.tsfem-dashicon.tsfem-unknown:after{content:"\f223";color:#057c99}.tsfem-dashicon.tsfem-warning:after{content:"\f227";color:#ffa01b}.tsfem-dashicon.tsfem-edit:after{content:"\f464";color:#ffa01b}.tsfem-dashicon.tsfem-close:after{content:"\f158";color:#ffa01b}@keyframes tsfem-spin{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes tsfem-fadeout{100%{opacity:0}}#tsfem-page-wrap #wpfooter{z-index:-1;padding:0}.tsfem-footer-wrap .tsfem-footer-motto,.tsfem-footer-wrap .tsfem-footer-title{font-size:1em;margin:0;color:#646d78;font-weight:300;cursor:default}.tsfem-footer-wrap .tsfem-footer-title{font-weight:400;color:#555d66}.tsfem-modal-mask,.tsfem-modal-mask-noscroll{position:fixed;top:0;right:0;bottom:0;left:0;z-index:2147483644;opacity:0}.tsfem-modal-mask-noscroll{position:absolute;z-index:2147483645}.tsfem-modal-buttons,.tsfem-modal-container,.tsfem-modal-dialog,.tsfem-modal-dialog-wrap,.tsfem-modal-select,.tsfem-modal-select-option,.tsfem-modal-title{display:flex}.tsfem-modal-container{position:fixed;top:0;left:0;overflow:hidden;width:100%;height:100%;background:rgba(33,33,33,.15);opacity:0;z-index:2147483646}.tsfem-modal-dialog-wrap{position:absolute;top:0;right:0;left:0;bottom:0;justify-content:center;align-items:center}.tsfem-modal-dialog{flex-direction:column;background:#fff;border-radius:3px;min-width:240px;max-width:calc(90% - 240px);max-height:80%;position:relative;overflow:hidden;box-shadow:0 0 4px 2px #aaa;z-index:2147483647}.tsfem-modal-buttons,.tsfem-modal-title{margin:0;padding:1em calc(1em + .6vw);box-shadow:0 1px 3px 0 #ccc;z-index:5;flex-shrink:0}.tsfem-modal-buttons{box-shadow:0 -1px 3px 0 #ccc;justify-content:flex-end}.tsfem-modal-title h4{margin:0;padding:0;font-size:1.4em;line-height:1.2em;color:#057f9c;font-weight:400}.tsfem-modal-inner{padding:calc(1em + .6vw);max-width:640px;overflow:auto;overflow-x:hidden}.tsfem-modal-text p:first-of-type{margin:0}.tsfem-modal-text p:last-of-type{margin-bottom:0}.tsfem-modal-text+.tsfem-modal-select{margin-top:calc(1em + .6vw)}.tsfem-modal-select{flex-wrap:wrap;align-items:flex-start}.tsfem-modal-select-option{margin-bottom:1em;padding:0 .5em;flex-basis:140px;max-width:calc(50% - 1em);flex-grow:1}.tsfem-modal-select-option:first-of-type:last-of-type{max-width:none}.tsfem-modal-select-option input{position:absolute;width:0;height:0;opacity:0;margin:0!important;padding:0!important;border:0;-webkit-appearance:none;-moz-appearance:none;appearance:none}.tsfem-modal-select-option label{font-weight:600;border:1px solid transparent;background:#fefefe;box-shadow:0 1px 3px 1px #ccc;cursor:pointer;padding:1em;width:100%;-moz-transition:all .33s;-o-transition:all .33s;transition:all .33s;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.tsfem-modal-select-option:hover input:not(:checked)+label{filter:brightness(.95)}.tsfem-modal-select-option>input:checked+label{border-color:#05809e}.tsfem-modal-select-option input:active+label,.tsfem-modal-select-option input:focus+label,.tsfem-modal-select-option:hover input:checked+label{box-shadow:0 1px 3px 1px #05809e}.tsfem-modal-buttons button:nth-child(n+2){margin-left:1em}body.rtl .tsfem-modal-buttons button:nth-child(n+2){margin-left:initial;margin-right:1em}.tsfem-modal-dismiss{position:absolute;top:0;right:0;border:none;margin:7px;padding:7px;background:0 0;color:#b4b9be;cursor:pointer;text-decoration:none;z-index:6;transition:color .15s}body.rtl .tsfem-modal-dismiss{right:initial;left:0}.tsfem-modal-dismiss:hover{color:#d14b44}.tsfem-modal-dismiss:before{background:0 0;content:"\f153";display:block;font:400 16px/20px dashicons;speak:none;height:20px;text-align:center;width:20px;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.tsfem-hidden{display:none}.tsfem-has-hover-balloon{position:relative;cursor:help}.tsfem-ltr{direction:ltr}.tsfem-flex-hide-if-no-js,body.js .tsfem-flex-hide-if-js{display:none}body.js .tsfem-flex-hide-if-no-js{display:flex} +#tsfem-page-wrap .error,#tsfem-page-wrap .notice,#tsfem-page-wrap .tsf-notice,#tsfem-page-wrap .warning{display:block;margin:1.2vw .6vw 0;box-shadow:0 .1em .4em rgba(0,0,0,.18);border-radius:3px}#tsfem-page-wrap #update-nag,#tsfem-page-wrap .update-nag,body.tsfem #update-nag,body.tsfem .update-nag{display:none}.tsfem-flex-status-loading>span{display:inline-block;font-size:30px;text-align:center}.tsfem-flex-status-loading>span:after{display:inline-block;width:1em;content:"\f463";font-size:1em;line-height:1em;font-family:dashicons;text-decoration:inherit;font-weight:400;font-style:normal;vertical-align:middle;color:#0ebfe9;line-height:1em;font-size:1em;animation:tsfem-spin 1.5s linear infinite}.tsfem-disable-cursor{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.tsfem-question-cursor{cursor:help}.tsfem-flex{box-sizing:border-box;display:flex;flex:1 1 auto;flex-wrap:wrap;flex-direction:column;justify-content:flex-start}.tsfem-flex-rtl{justify-content:flex-start}.tsfem-flex-row{flex-direction:row}.tsfem-flex-nowrap{flex-wrap:nowrap}.tsfem-flex-grow{flex-grow:1}.tsfem-flex-nogrow{flex:0 1 auto}.tsfem-flex-noshrink{flex:1 0 auto}.tsfem-flex-nogrowshrink{flex:0 0 auto}.tsfem-flex-space{justify-content:space-between}.tsfem-flex-center{justify-content:center}.tsfem-flex-end{justify-content:flex-end}body.tsfem #wpbody-content{padding-bottom:0}body.tsfem .wrap{display:grid;grid-auto-rows:max-content minmax(max-content,1fr) max-content;min-height:calc(100vh - 32px);padding:0;margin:0;margin-left:-20px}body.tsfem.rtl .wrap{margin-left:0;margin-right:-20px}#tsfem-notice-wrap{margin:0 auto;max-width:1240px}.tsfem-connect-wrap{flex:1 1 auto;margin:1.2vw}#tsfem-sticky-top{position:-webkit-sticky;position:sticky;margin-bottom:1.2vw;top:32px;z-index:9980;background:linear-gradient(to bottom,rgba(0,0,0,.3),rgba(0,0,0,0))}@media screen and (max-width:782px){body.tsfem .wrap{margin-left:-10px}#tsfem-sticky-top{top:46px}}@media screen and (max-width:600px){#tsfem-sticky-top{position:initial;background:0 0}}#tsfem-top-super-wrap{background-color:#fff;border-bottom:1px solid #ccd0d4;width:100%}#tsfem-top-wrap{margin:0 auto;padding:0 1.2vw;max-width:1240px}#tsfem-top-wrap>*{padding:1em 0}.tsfem-footer-wrap{padding:calc(1em + .6vw) 0;margin:0 1.2vw;overflow:hidden;white-space:nowrap;text-align:center}.tsfem-flex-textarea-wrap{flex-direction:row;min-width:100%;width:100%;flex:0 0 auto}.tsfem-flex-textarea-wrap textarea{display:block;min-width:100%}#tsfem-top-wrap .tsfem-title,.tsfem-top-about,.tsfem-top-actions{text-align:left;align-items:center}.tsfem-top-about,.tsfem-top-actions{justify-content:flex-end;min-width:200px}.tsfem-top-about{flex:1 1 50%}.tsfem-top-actions>div{display:inline-block;white-space:pre}.tsfem-top-actions>:nth-last-child(n+2){margin-right:1em}body.rtl .tsfem-top-actions>:nth-last-child(n+2){margin-right:0;margin-left:1em}.tsfem-top-about>div{display:inline-block;text-align:justify;max-width:calc(100% - 1.2em - 1em);color:#333;letter-spacing:.2px}#tsfem-top-wrap .tsfem-title h1{display:flex;font-family:Verdana,Geneva,sans-serif;word-break:break-word;font-size:1.9em;font-weight:400;line-height:1.1em;color:#333;margin:0;padding:0}#tsfem-top-wrap .tsfem-title .tsfem-logo>svg{padding:0;margin-right:.75ch;vertical-align:top}body.rtl #tsfem-top-wrap .tsfem-title .tsfem-logo>svg{margin-right:0;margin-left:.75ch}.tsfem-logger{display:block;padding:1em 1em .5em;margin:0;line-height:1.5;word-break:break-all;word-wrap:break-word;color:#333;background-color:#f5f5f5;border:1px solid #ccc;border-radius:3px;overflow:auto;min-height:calc(2.5em * 1.5);height:calc(14em * 1.5);resize:vertical;white-space:pre-wrap;word-break:break-word;clear:both}#tsfem-page-wrap input[type=checkbox]:focus,#tsfem-page-wrap input[type=color]:focus,#tsfem-page-wrap input[type=date]:focus,#tsfem-page-wrap input[type=datetime-local]:focus,#tsfem-page-wrap input[type=datetime]:focus,#tsfem-page-wrap input[type=email]:focus,#tsfem-page-wrap input[type=month]:focus,#tsfem-page-wrap input[type=number]:focus,#tsfem-page-wrap input[type=password]:focus,#tsfem-page-wrap input[type=radio]:focus,#tsfem-page-wrap input[type=search]:focus,#tsfem-page-wrap input[type=tel]:focus,#tsfem-page-wrap input[type=text]:focus,#tsfem-page-wrap input[type=time]:focus,#tsfem-page-wrap input[type=url]:focus,#tsfem-page-wrap input[type=week]:focus,#tsfem-page-wrap select:focus,#tsfem-page-wrap textarea:focus{border-color:#057f9c;box-shadow:0 0 2px rgba(66,144,183,.8)}#tsfem-page-wrap .tsf-remove-image-button,#tsfem-page-wrap .tsf-remove-image-button:active,#tsfem-page-wrap .tsf-remove-image-button:focus,#tsfem-page-wrap .tsf-remove-image-button:hover,#tsfem-page-wrap .tsf-set-image-button,#tsfem-page-wrap .tsf-set-image-button:active,#tsfem-page-wrap .tsf-set-image-button:focus,#tsfem-page-wrap .tsf-set-image-button:hover,.tsfem-button,.tsfem-button-primary,.tsfem-button-primary:active,.tsfem-button-primary:focus,.tsfem-button-primary:hover,.tsfem-button:active,.tsfem-button:focus,.tsfem-button:hover{display:inline-block;box-shadow:0 0 .3em transparent,0 0 0 transparent inset;filter:none;transition:box-shadow .125s,color .125s .0625s,background .0625s .125s;padding:.4em 1em .5em;font-size:1em;font-weight:400;line-height:1em;text-decoration:none;text-align:center;cursor:pointer;outline:0;color:#444;background:#f6f8f9;border:1px solid currentColor;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:3px}.tsfem-button,.tsfem-button:active,.tsfem-button:focus,.tsfem-button:hover{color:#666}.tsfem-button-red,.tsfem-button-red:focus,.tsfem-button-red:hover{color:#dd3811}#tsfem-page-wrap .tsf-set-image-button,#tsfem-page-wrap .tsf-set-image-button:active,#tsfem-page-wrap .tsf-set-image-button:focus,#tsfem-page-wrap .tsf-set-image-button:hover,.tsfem-button-primary,.tsfem-button-primary:active,.tsfem-button-primary:focus,.tsfem-button-primary:hover{background:#05809e;color:#fff}#tsfem-page-wrap .tsf-set-image-button:focus,#tsfem-page-wrap .tsf-set-image-button:hover,.tsfem-button-primary:focus,.tsfem-button-primary:hover,.tsfem-button:focus,.tsfem-button:hover{box-shadow:0 .1em .5em rgba(0,0,0,.3),0 0 0 transparent inset}#tsfem-page-wrap .tsf-set-image-button:active,.tsfem-button-primary:active,.tsfem-button:active{box-shadow:0 0 .3em transparent,0 0 .3em 0 rgba(0,0,0,.3) inset;transition-duration:.15s}.tsfem-button-small,.tsfem-button-small:active,.tsfem-button-small:focus,.tsfem-button-small:hover{font-size:.9em;padding:.5em 1em}.tsfem-button-primary-bright,.tsfem-button-primary-bright:focus,.tsfem-button-primary-bright:hover{background:#0ebfe9}.tsfem-button-primary-dark,.tsfem-button-primary-dark:focus,.tsfem-button-primary-dark:hover{background:#535353}.tsfem-button-clipboard:after,.tsfem-button-cloud:after,.tsfem-button-download:after,.tsfem-button-external:after,.tsfem-button-flag:after,.tsfem-button-image:after,.tsfem-button-index:after,.tsfem-button-loading:after,.tsfem-button-love:after,.tsfem-button-star:after,.tsfem-button-upload:after,.tsfem-button-warning:after{display:inline-block;width:1em;height:1em;line-height:1em;font-size:1em;content:"\f155";font-family:dashicons;text-decoration:inherit;font-weight:400;font-style:normal;vertical-align:top;margin-left:.5em}body.rtl .tsfem-button-clipboard:after,body.rtl .tsfem-button-cloud:after,body.rtl .tsfem-button-download:after,body.rtl .tsfem-button-external:after,body.rtl .tsfem-button-flag:after,body.rtl .tsfem-button-image:after,body.rtl .tsfem-button-index:after,body.rtl .tsfem-button-loading:after,body.rtl .tsfem-button-love:after,body.rtl .tsfem-button-star:after,body.rtl .tsfem-button-upload:after,body.rtl .tsfem-button-warning:after{margin-left:0;margin-right:.5em}.tsfem-button-small:after{font-size:.9em}.tsfem-button-flag:after{content:"\f227"}.tsfem-button-warning:after{content:"\f534"}.tsfem-button-love:after{content:"\f487"}.tsfem-button-upload:after{content:"\f317"}.tsfem-button-download:after{content:"\f316"}.tsfem-button-clipboard:after{content:"\f481"}.tsfem-button-index:after{content:"\f510"}.tsfem-button-cloud:after{content:"\f176"}.tsfem-button-external:after{content:"\f504"}.tsfem-button-image:after{content:"\f128"}.tsfem-button-loading:after{content:"\f463";-moz-animation:tsfem-spin 1.5s linear infinite;-o-animation:tsfem-spin 1.5s linear infinite;animation:tsfem-spin 1.5s linear infinite}.tsfem-switch-button-container-wrap{display:inline-block;perspective:800px;perspective-origin:50%}.tsfem-switch-button-container{position:relative;margin:0;width:140px;height:2em;padding:0;display:block}.tsfem-switch-button-container input[type=checkbox]{position:absolute;width:0;height:0;opacity:0;margin:0;padding:0;border:0;z-index:-1;-webkit-appearance:none;-moz-appearance:none;appearance:none}.tsfem-switch-button-container button,.tsfem-switch-button-container label{position:absolute;left:0;top:0;text-align:center;transition:transform 1s,opacity .5s;z-index:1;width:100%;display:block;width:140px;box-sizing:border-box}.tsfem-switch-button-container input[type=checkbox]~button{visibility:hidden}.tsfem-switch-button-container input[type=checkbox]+label{z-index:2;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.tsfem-switch-button-container input[type=checkbox]:focus+label{background:#eee;color:#666;box-shadow:0 .2em 1em rgba(0,0,0,.3),0 0 0 transparent inset}.tsfem-switch-button-container button{transform:rotateX(-90deg);transform-origin:0 100% 0;opacity:0;transition:transform 1.2s,opacity .6s}.tsfem-switch-button-container input[type=checkbox]:checked:before{content:none}.tsfem-switch-button-container input[type=checkbox]:checked+label{transform:rotateX(90deg) translateY(-100%);opacity:0;z-index:0;transition:transform 1.2s,opacity .6s}.tsfem-switch-button-container input[type=checkbox]:checked~button{transform:rotateX(0);opacity:1;z-index:2;transition:transform 1s,opacity .5s;visibility:visible}.tsfem-button-disabled,.tsfem-button-disabled:active,.tsfem-button-disabled:disabled,.tsfem-button-disabled:disabled:active,.tsfem-button-disabled:disabled:focus,.tsfem-button-disabled:disabled:hover,.tsfem-button-disabled:focus,.tsfem-button-disabled:hover,.tsfem-button-primary:disabled,.tsfem-button-primary:disabled:active,.tsfem-button-primary:disabled:focus,.tsfem-button-primary:disabled:hover,.tsfem-button:disabled,.tsfem-button:disabled:active,.tsfem-button:disabled:focus,.tsfem-button:disabled:hover{background:#ddd;color:#aaa;font-weight:400;box-shadow:none;cursor:not-allowed;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.tsfem-about-activation:before{display:inline-block;color:#0ebfe9;content:"\f112";font-size:1.2em;line-height:1.2em;font-family:dashicons;text-decoration:inherit;font-weight:400;font-style:normal;margin:0 7px;vertical-align:baseline;white-space:pre-wrap}.tsfem-connect-option{width:100%;max-width:690px;padding:1em 1.5em;margin:0 auto 1.2vw;box-sizing:border-box;background:#fff;border:1px solid #ccd0d4;border-radius:4px}.tsfem-connect-row{align-items:center;justify-content:space-between}.tsfem-connect-option:last-of-type{margin-bottom:0}.tsfem-connect-action,.tsfem-connect-text{margin:.5em 0;flex:1 1 49%;min-width:175px;padding:3px}.tsfem-connect-action form{margin:0;padding:0}.tsfem-connect-description>*{margin:0 0 1em;display:block}.tsfem-connect-description>:last-child{margin-bottom:0}.tsfem-connect-wrap h3{font-size:1.66em;color:#057f9c;font-weight:400}.tsfem-panes-super-wrap{width:100%;max-width:1240px;margin:0 auto}.tsfem-panes-wrap{padding:0;margin:0 1.2vw;display:grid;grid-template:repeat(auto-fit,minmax(auto,310px))/repeat(auto-fit,minmax(310px,1fr));grid-template:repeat(auto-fit,minmax(max-content,310px))/repeat(auto-fit,minmax(310px,1fr));grid-gap:1.2vw;grid-auto-flow:row dense;align-self:flex-start;align-self:start}.tsfem-pane-section{margin-bottom:2em}.tsfem-pane-section:last-of-type{margin-bottom:0}.tsfem-pane{height:100%;height:max-content;padding:0}@media screen and (min-width:660px){.tsfem-pane{grid-column:auto/1 span;grid-row:auto/1 span}.tsfem-pane-tall{grid-row:auto/2 span}.tsfem-pane-wide{grid-column:auto/2 span}.tsfem-pane-full{grid-row:auto/2 span;grid-column:auto/2 span}}.tsfem-pane-wrap{width:100%;height:max-content;background:#fff;border:1px solid #ccd0d4;border-radius:4px}.tsfem-pane-header{z-index:5}.tsfem-pane-content,.tsfem-pane-header{margin:0;padding:1em 1.5em}.tsfem-pane-content{padding:0;border-radius:4px;height:100%}.tsfem-pane-header>*{font-size:1em;line-height:1em;margin:0;padding:0;box-sizing:border-box;display:flex;flex:1 1 auto;flex-direction:row;align-items:center;justify-content:flex-end}.tsfem-pane-header .tsfem-ajax{max-width:initial;font-size:1em;word-break:break-word;text-align:right;flex:0 1 auto;margin-left:.5em}.tsfem-pane-header h3{font-size:1.33em;color:#057f9c;font-weight:400;justify-content:flex-start;width:max-content}.tsfem-pane-header img,.tsfem-pane-header svg{margin-right:.75ch}.tsfem-pane-content h4.tsfem-status-title,.tsfem-pane-content h5.tsfem-status-title,.tsfem-pane-content h6.tsfem-status-title{margin:0}.tsfem-pane-inner-wrap{background:#fff;box-sizing:border-box;padding:1.5em;height:auto;max-height:100%;width:100%;border-radius:4px}.tsfem-pane-inner-collapsable-settings-wrap{padding:0}.tsfem-pane-inner-pad{width:100%;padding:1.5em}.tsfem-pane-inner-pad+.tsfem-pane-inner-pad{padding-top:0}.tsfem-pane-footer-wrap{margin:0;padding:1em 1.5em;border-top:1px solid rgba(0,0,0,.18);z-index:5}.tsfem-pane-footer-wrap>:nth-last-child(n+2){margin-right:1em}body.rtl .tsfem-pane-footer-wrap>:nth-last-child(n+2){margin-right:0;margin-left:1em}.tsfem-flex-account-info-rows,.tsfem-flex-account-setting-rows{line-height:1.625em;max-width:400px}.tsfem-actions-account-info-title{font-weight:600}h4.tsfem-action-title,h4.tsfem-cp-title,h4.tsfem-form-title,h4.tsfem-info-title,h4.tsfem-status-title,h4.tsfem-support-title{padding:0;margin:0 0 1em;font-size:1.16em;font-weight:400;color:#057f9c}.tsfem-account-upgrade,.tsfem-cp-buttons,.tsfem-support-buttons{max-width:300px}.tsfem-cp-buttons,.tsfem-support-buttons{margin-bottom:.8em;flex-basis:50%}.tsfem-cp-buttons:last-of-type,.tsfem-support-buttons:last-of-type{margin-bottom:0}.tsfem-cp-buttons>a,.tsfem-cp-buttons>form,.tsfem-support-buttons>a{cursor:pointer;min-width:50%;display:inline-block}.tsfem-cp-buttons button{width:100%}.tsfem-description{font-size:.93em;font-style:oblique;text-indent:.5em}.tsfem-ajax:after,.tsfem-dashicon:after{display:inline-block;line-height:1;font-family:dashicons;font-style:normal;font-weight:400;width:1em;vertical-align:baseline;content:"";margin-left:.333em}.tsfem-ajax:after{font-size:1.225em;line-height:.816em}.tsfem-dashicon:after{font-size:1.2em;vertical-align:text-bottom;margin-left:2px}.tsfem-ajax.tsfem-loading:after{content:"\f463";color:#057c99;animation:tsfem-spin 1.5s linear infinite}.tsfem-dashicon-fadeout-3000:after{animation:tsfem-fadeout 3s linear 1}.tsfem-ajax.tsfem-error:after,.tsfem-dashicon.tsfem-error:after{content:"\f158";color:#dd3811}.tsfem-ajax.tsfem-success:after,.tsfem-dashicon.tsfem-success:after{content:"\f147";color:#0cc34b}.tsfem-ajax.tsfem-unknown:after,.tsfem-dashicon.tsfem-unknown:after{content:"\f223";color:#057c99}.tsfem-dashicon.tsfem-warning:after{content:"\f227";color:#ffa01b}.tsfem-dashicon.tsfem-edit:after{content:"\f464";color:#ffa01b}.tsfem-dashicon.tsfem-close:after{content:"\f158";color:#ffa01b}@keyframes tsfem-spin{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes tsfem-fadeout{100%{opacity:0}}#tsfem-page-wrap #wpfooter{z-index:-1;padding:0}.tsfem-footer-wrap .tsfem-footer-motto,.tsfem-footer-wrap .tsfem-footer-title{font-size:1em;margin:0;color:#646d78;font-weight:300;cursor:default}.tsfem-footer-wrap .tsfem-footer-title{font-weight:400;color:#555d66}.tsfem-modal-mask,.tsfem-modal-mask-noscroll{position:fixed;top:0;right:0;bottom:0;left:0;z-index:2147483644;opacity:0}.tsfem-modal-mask-noscroll{position:absolute;z-index:2147483645}.tsfem-modal-buttons,.tsfem-modal-container,.tsfem-modal-dialog,.tsfem-modal-dialog-wrap,.tsfem-modal-select,.tsfem-modal-select-option,.tsfem-modal-title{display:flex}.tsfem-modal-container{position:fixed;top:0;left:0;overflow:hidden;width:100%;height:100%;background:rgba(33,33,33,.15);opacity:0;z-index:2147483646}.tsfem-modal-dialog-wrap{position:absolute;top:0;right:0;left:0;bottom:0;justify-content:center;align-items:center}.tsfem-modal-dialog{flex-direction:column;background:#fff;border-radius:3px;min-width:240px;max-width:calc(90% - 240px);max-height:80%;position:relative;overflow:hidden;box-shadow:0 0 4px 2px #aaa;z-index:2147483647}.tsfem-modal-buttons,.tsfem-modal-title{margin:0;padding:1em calc(1em + .6vw);box-shadow:0 1px 3px 0 #ccc;z-index:5;flex-shrink:0}.tsfem-modal-buttons{box-shadow:0 -1px 3px 0 #ccc;justify-content:flex-end}.tsfem-modal-title h4{margin:0;padding:0;font-size:1.4em;line-height:1.2em;color:#057f9c;font-weight:400}.tsfem-modal-inner{padding:calc(1em + .6vw);max-width:640px;overflow:auto;overflow-x:hidden}.tsfem-modal-text p:first-of-type{margin:0}.tsfem-modal-text p:last-of-type{margin-bottom:0}.tsfem-modal-text+.tsfem-modal-select{margin-top:calc(1em + .6vw)}.tsfem-modal-select{flex-wrap:wrap;align-items:flex-start}.tsfem-modal-select-option{margin-bottom:1em;padding:0 .5em;flex-basis:140px;max-width:calc(50% - 1em);flex-grow:1}.tsfem-modal-select-option:first-of-type:last-of-type{max-width:none}.tsfem-modal-select-option input{position:absolute;width:0;height:0;opacity:0;margin:0!important;padding:0!important;border:0;-webkit-appearance:none;-moz-appearance:none;appearance:none}.tsfem-modal-select-option label{font-weight:600;border:1px solid transparent;background:#fefefe;box-shadow:0 1px 3px 1px #ccc;cursor:pointer;padding:1em;width:100%;transition:all .33s;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.tsfem-modal-select-option:hover input:not(:checked)+label{filter:brightness(.95)}.tsfem-modal-select-option>input:checked+label{border-color:#05809e}.tsfem-modal-select-option input:active+label,.tsfem-modal-select-option input:focus+label,.tsfem-modal-select-option:hover input:checked+label{box-shadow:0 1px 3px 1px #05809e}.tsfem-modal-buttons button:nth-child(n+2){margin-left:1em}body.rtl .tsfem-modal-buttons button:nth-child(n+2){margin-left:initial;margin-right:1em}.tsfem-modal-dismiss{position:absolute;top:0;right:0;border:none;margin:7px;padding:7px;background:0 0;color:#b4b9be;cursor:pointer;text-decoration:none;z-index:6;transition:color .15s}body.rtl .tsfem-modal-dismiss{right:initial;left:0}.tsfem-modal-dismiss:hover{color:#d14b44}.tsfem-modal-dismiss:before{background:0 0;content:"\f153";display:block;font:400 16px/20px dashicons;speak:none;height:20px;text-align:center;width:20px;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.tsfem-hidden{display:none}.tsfem-has-hover-balloon{position:relative;cursor:help}.tsfem-ltr{direction:ltr} diff --git a/lib/js/tsfem-form.js b/lib/js/tsfem-form.js index 5e0d7ca1..0f500019 100644 --- a/lib/js/tsfem-form.js +++ b/lib/js/tsfem-form.js @@ -451,26 +451,25 @@ window.tsfemForm = { let fillingAddress = false; - let buttonWrap; - let reverseWarningWrap; //= Fills buttonWrap, with GC for stray items. - (function() { - buttonWrap = document.createElement( 'div' ); + { + var buttonWrap = document.createElement( 'div' ); buttonWrap.className = 'tsfem-form-setting-action tsfem-flex'; buttonWrap.dataset.geoApiIsButtonWrap = 1; buttonWrap.style.opacity = 0; + let button = document.createElement( 'button' ); button.className = 'tsfem-button-primary tsfem-button-primary-bright tsfem-button-cloud'; button.innerHTML = tsfemForm.i18n['validate']; button.type = 'button'; buttonWrap.appendChild( button ); - reverseWarningWrap = document.createElement( 'div' ); + var reverseWarningWrap = document.createElement( 'div' ); reverseWarningWrap.className = 'tsfem-description tsfem-form-option-description'; reverseWarningWrap.dataset.geoApiIsButtonWarning = 1; reverseWarningWrap.style.opacity = 0; reverseWarningWrap.innerHTML = tsfemForm.i18n['reverseGeoWarning']; - })(); + }; /** * Fills in address fields by requested input data from dialog. @@ -1407,14 +1406,14 @@ window.tsfemForm = { let trapClass = 'tsfem-form-checkbox-required'; - let trap = document.createElement( 'input' ); - (function() { + { + var trap = document.createElement( 'input' ); trap.className = trapClass; trap.setAttribute( 'type', 'checkbox' ); trap.setAttribute( 'required', 'required' ); trap.setAttribute( 'value', '1' ); trap.setAttribute( 'tabIndex', '-1' ); - })(); + } /** * Adds or subtracts error count for $box. diff --git a/lib/js/tsfem-form.min.js b/lib/js/tsfem-form.min.js index 648911e5..2f1eaea0 100644 --- a/lib/js/tsfem-form.min.js +++ b/lib/js/tsfem-form.min.js @@ -1 +1 @@ -'use strict';window.tsfemForm={nonce:tsfemFormL10n.nonce,i18n:tsfemFormL10n.i18n,callee:tsfemFormL10n.callee,parseElementData:function(a,b){let c,d=tsfemForm.getElementData(a,b);try{c=d&&JSON.parse(d)}catch(a){c=d}finally{return c}},getElementData:function(a,b){return 0===b.indexOf("data-")&&(b=jQuery.camelCase(b.slice(5))),!!a.dataset.hasOwnProperty(b)&&a.dataset[b]},setupIterations:function(){let a=jQuery(".tsfem-form-iterator-selector-wrap input");if(!a.length)return;tsfemForm.prepareItItems(a);let b,c,d,e,f,g,h,i,j=0,k=1500,l=0,m=0,n=2;const o=function(){b=document.createElement("span"),b.className="tsfem-form-iterator-timer",c=document.createElement("span"),c.style.width="0%",b.appendChild(c)};o(),d=k/(100*n),d*=.975;const p=function(){c.style.width=++l/n+"%"},q=function(){l=0,c.style.width="0%",b.classList.remove("tsfem-form-iterator-timer-invalid")},r=function(){l=100,c.style.width=l+"%",b.classList.add("tsfem-form-iterator-timer-invalid")},s=function(){h&&h.remove(),f=g=h=void 0,o(),setTimeout(function(){a.prop("disabled",!1)},500),a.off("input.fIt").on("input.fIt",e)},t=function(){a.off("input.fIt"),a=jQuery(".tsfem-form-iterator-selector-wrap input"),tsfemForm.prepareItItems(a),tsfemForm.prepareCollapseItems(),tsfemForm.setupGeo(),"tsfMedia"in window&&tsfMedia.resetImageEditorActions(),a.on("input.fIt",e),tsfTT.triggerReset(),i=void 0},u=function(){i&&(i.value=i.dataset.tsfemFormPrevValue)};let v=jQuery(window);v.on("tsfemForm.iterationFail",u),v.on("tsfemForm.iterationLoad tsfemForm.deiterationLoad tsfemForm.iterationFail",s),v.on("tsfemForm.iterationComplete tsfemForm.deiterationComplete tsfemForm.iterationFail",t),e=function(l){return clearInterval(m),clearTimeout(j),q(),tsfemForm.disableSubmit(l.target.form),i=l.target,l.target.checkValidity()?void(m=setInterval(p,d),!f&&(f=jQuery(l.target),g=f.closest(".tsfem-form-setting").find(".tsfem-form-setting-label-inner-wrap"),a.not(l.target).prop("disabled",!0),g.append(b),h=g.find("span.tsfem-form-iterator-timer")),j=setTimeout(function(){if(f.prop("disabled",!0),clearInterval(m),c.style.width="100%",+l.target.value==+l.target.dataset.tsfemFormPrevValue)s(),tsfemForm.enableSubmit(l.target.form);else if(+l.target.value<+l.target.dataset.tsfemFormPrevValue)tsfemForm.unloadIterations(l.target),tsfemForm.enableSubmit(l.target.form);else if(40<+l.target.value){200<+l.target.value?tsfem_ui.dialog({title:tsfemFormL10n.i18n.performanceWarning,text:[tsfemFormL10n.i18n.itHugeConfirm,tsfemFormL10n.i18n.aysProceed],confirm:tsfemFormL10n.i18n.proceed,cancel:tsfemFormL10n.i18n.cancel}):tsfem_ui.dialog({title:tsfemFormL10n.i18n.performanceWarning,text:[tsfemFormL10n.i18n.itLargeConfirm,tsfemFormL10n.i18n.aysProceed],confirm:tsfemFormL10n.i18n.proceed,cancel:tsfemFormL10n.i18n.cancel});let a={};a._cancel=function(){u(),s(),a._reset(),tsfemForm.enableSubmit(l.target.form)},a._confirm=function(){tsfemForm.loadIterations(l.target),a._reset()},a._reset=function(){window.removeEventListener("tsfem_modalCancel",a._cancel),window.removeEventListener("tsfem_modalConfirm",a._confirm)},window.addEventListener("tsfem_modalCancel",a._cancel),window.addEventListener("tsfem_modalConfirm",a._confirm)}else tsfemForm.loadIterations(l.target)},k)):void r()},a.on("input.fIt",e)},unloadIterations:function(a){if(a){let b=a.id.slice(0,a.id.lastIndexOf("[")),c=document.getElementById(b+"-wrapper");c.style.willChange="contents";let d=jQuery(window),e=jQuery(c).children(".tsfem-form-collapse").slice(a.value);d.trigger("tsfemForm.deiterationLoad",[a,e]),e.remove(),d.trigger("tsfemForm.deiterationComplete"),c.style.willChange="auto"}},loadIterations:function(a){if(!a)return;let b=a.id.slice(0,a.id.lastIndexOf("[")),c=jQuery(a).closest(".tsfem-pane-wrap").find(".tsfem-pane-header .tsfem-ajax"),d=0,e="",f=document.getElementById(b+"-wrapper"),g=document.createElement("div");g.className="tsfem-flex-status-loading tsfem-flex tsfem-flex-center",g.appendChild(document.createElement("span")),f.appendChild(g),tsfem.resetAjaxLoader(c),tsfem.setAjaxLoader(c),jQuery.ajax({method:"POST",url:ajaxurl,dataType:"html",data:{action:"tsfemForm_iterate",nonce:tsfemForm.nonce,args:{caller:b,callee:tsfemForm.callee,previousIt:a.dataset.tsfemFormPrevValue,newIt:a.value}},timeout:1e4,async:!0}).done(function(b,c,g){let h=g.getResponseHeader("content-type"),i=jQuery(window);i.trigger("tsfemForm.iterationLoad"),-1h[0].remove())}},h=function(a,b,c){g(a,b,c)},i=function(a){let b=a&&a.find(".tsfem-form-multi-setting-label-inner-wrap")||void 0;b&&b.find(b.children("[data-geo-api-is-button-wrap], [data-geo-api-is-button-warning]")).fadeOut(300,function(){this.remove(),a.removeData("geo-api-has-button")})},j=function(a){return a.find("[data-geo-api=\"1\"]")},k=function(a){let b=j(a);if(!b.length)return 0;let c,d=0,e=b.filter("[data-geo-api-component=\"lat\"]").first().val(),f=b.filter("[data-geo-api-component=\"lng\"]").first().val();if(b.each(function(a,b){if(-1=e?2:0,d=d&&/^(\-|\+)?([0-9]+(\.[0-9]+)?)$/.test(f)&&-180<=f&&180>=f?2:0;else if(c&&(d=/^((([0-9\/-]+([\/-0-9A-Z]+)?(\s|(,\s)))([\u00a1-\uffffa-zA-Z\.\s]|[0-9_/-])+))|(([\u00a1-\uffffa-zA-Z\.\s]|[0-9_/-])+)((\s|(,\s))([0-9\/-]+([\/-0-9A-Z]+)?))$/.test(c)?1:0,!d)){let a=0;b.not("[data-geo-api-component=\"lat\"], [data-geo-api-component=\"lng\"]").each((b,c)=>{if(c.value.length&&1<++a)return!1}),d=1 input").off("change.tsfemForm.prepareItems").on("change.tsfemForm.prepareItems",function(a){tsfemForm.prepareCollapseTitles(a),tsfemForm.prepareCollapseValidity(a)}).trigger("change.tsfemForm.prepareItems")},prepareCollapseTitles:function(a){const b=function(a){let b=jQuery(a.data._tsfemFormLabel),c=b.find(".tsfem-form-collapse-title"),d=b.data("dyntitleprep"),e=jQuery(a.target).val();e?c.text(d+" - "+e):c.text(d)},c=function(a){let b=a.target.checked||!1,c=jQuery(a.data._tsfemFormLabel),d=c.find(".tsfem-form-collapse-title"),e=c.data("dyntitleprep");b?d.text(e+" - "+c.data("dyntitlechecked")):d.text(e)},d=function(a){let b=jQuery(a.data._tsfemFormLabel),c=b.find(".tsfem-form-collapse-title"),d=b.data("dyntitleprep"),e=[];jQuery(a.data._tsfemFormThings).map((a,b)=>{b.checked&&e.push(b.value)});let f=e.join(", ");f?c.text(d+" - "+f):c.text(d)};(function(a){let e,f=jQuery(a.target).siblings("label"),g=f.data("dyntitletype"),h=f.data("dyntitleid")+"["+f.data("dyntitlekey")+"]";switch(g){case"single":case"checkbox":let a=document.getElementById(h);e="input.tsfemForm.doTitleChangeSingle",jQuery(a).off(e).on(e,{_tsfemFormLabel:f},"checkbox"===g?c:b).trigger(e);break;case"plural":let i=jQuery(document.getElementById(h)).find("input[type=checkbox]");e="input.tsfemForm.doTitleChangeSingle";;i.off(e).on(e,{_tsfemFormLabel:f,_tsfemFormThings:i},d).trigger(e);}})(a)},prepareCollapseValidity:function(a){const b=function(a,b){let c="tsfem-form-collapse-header-error",d="tsfem-form-collapse-header-good",e=".tsfem-form-title-icon",f="tsfem-form-title-icon-unknown",g="tsfem-form-title-icon-error",h="tsfem-form-title-icon-good";0b?(a.removeClass(c+" "+d).addClass(d),a.find(e).removeClass(h+" "+g).addClass(f)):(a.removeClass(c).addClass(d),a.find(e).removeClass(f+" "+g).addClass(h))},c=function(a,c){let d,e;a.each(function(a,f){d=jQuery(f),e=(+d.data("tsfemErrorCount")||0)+c,d.data("tsfemErrorCount",e),b(d,e)})},d=function(a){let b=0;b="tsfemWasValid"in a.target.dataset?+a.target.dataset.tsfemWasValid:a.target.dataset.tsfemWasValid=1;let d=a.target.disabled||a.target.checkValidity(),e=jQuery(a.target).parents(".tsfem-form-collapse").children(".tsfem-form-collapse-header"),f=+e.data("tsfemErrorCount");f?b&&!d?(a.target.dataset.tsfemWasValid=+d,c(e,+1)):!b&&d&&(a.target.dataset.tsfemWasValid=+d,c(e,-1)):d?c(e,0):(a.target.dataset.tsfemWasValid=+d,c(e,+1))},e=function(a,b){let c=a.dataset.tsfemDidInitialValidation||0;c||(b.on("tsfemForm.first.checkValidity",d),b.each((a,b)=>{jQuery(b).trigger("tsfemForm.first.checkValidity")}),b.off("tsfemForm.first.checkValidity"),a.dataset.tsfemDidInitialValidation=1)};(function(a){let b=jQuery(a.target).siblings(".tsfem-form-collapse-content").find("input, select, textarea").not(".tsfem-form-collapse-checkbox");b.off("change.tsfemForm.checkValidity"),a.target.checked||b.on("change.tsfemForm.checkValidity",d),e(a.target,b)})(a),tsfemForm.prepareCustomChecks(d),tsfemForm.prepareDeiterationValidityChecks(d)},prepareCustomChecks:function(a){const b=function(b,c){if(c){let b=jQuery(c);b.length&&(b.one("tsfemForm.temp.customChecks.checkValidityCb",a),b.trigger("tsfemForm.temp.customChecks.checkValidityCb"))}},c=function(){jQuery(window).on("tsfemForm.customValidationChecks",b)},d=function(){jQuery(window).off("tsfemForm.customValidationChecks")};(function(){d(),c(),jQuery(window).off("tsfemForm.iterationLoad.customValidation").off("tsfemForm.iterationFail.customValidation").off("tsfemForm.iterationComplete.customValidation").on("tsfemForm.iterationLoad.customValidation",d).on("tsfemForm.iterationFail.customValidation",c).on("tsfemForm.iterationComplete.customValidation",c)})()},triggerCustomValidation:function(a){a instanceof HTMLElement&&jQuery(window).trigger("tsfemForm.customValidationChecks",[a])},prepareDeiterationValidityChecks:function(a){const b=function(b,c,d){if(d){let b=d.children(".tsfem-form-collapse-content").find("input:invalid, select:invalid, textarea:invalid");b&&(b.prop("disabled",!0),b.on("tsfemForm.temp.disableAndValidate.checkValidityCb",a),b.each((a,b)=>{jQuery(b).trigger("tsfemForm.temp.disableAndValidate.checkValidityCb")}),b.off("tsfemForm.temp.disableAndValidate.checkValidityCb"))}};(function(){jQuery(window).off("tsfemForm.deiterationLoad.disableAndValidate").on("tsfemForm.deiterationLoad.disableAndValidate",b)})()},setupSpecialRequired:function(){let a="tsfem-form-checkbox-required",b=document.createElement("input");(function(){b.className=a,b.setAttribute("type","checkbox"),b.setAttribute("required","required"),b.setAttribute("value","1"),b.setAttribute("tabIndex","-1")})();const c=function(a,b){let c=(+a.data("required-check-count")||0)+b;return a.data("required-check-count",c),c},d=function(c){if(!c.children("."+a).length){let a=b.cloneNode(!1);a.setCustomValidity(tsfemForm.i18n.requiredSelectAny),jQuery(a).prependTo(c),tsfemForm.triggerCustomValidation(a)}c.removeClass("tsfem-form-multi-valid").addClass("tsfem-form-multi-invalid")},e=function(b){let c=b.children("."+a);if(c.length){let a=c[0].cloneNode(!1);c.remove(),a.checked=!0,a.required=!1,jQuery(a).prependTo(b),tsfemForm.triggerCustomValidation(a),a.remove()}b.removeClass("tsfem-form-multi-invalid").addClass("tsfem-form-multi-valid")},f=function(a){if(a.target.disabled)return;let b,f=jQuery(a.target).closest(".tsfem-form-multi-select-wrap[data-required=\"1\"]"),g=f.data("required-check-count");b=a.target.checked?c(f,+1):c(f,-1),1>g?b>g&&e(f):1>b&&d(f)},g=function(){let a,b,e,g=jQuery(".tsfem-form-multi-select-wrap[data-required=\"1\"]");g.each(function(g,h){b=h.dataset.requiredCheckCount,void 0===b&&(a=jQuery(h),a.find("input").on("change.tsfemForm.testChecked",f),e=a.find("input:checked").length,e?a.addClass("tsfem-form-multi-valid"):d(a),c(a,+e))})};g(),jQuery(window).on("tsfemForm.iterationComplete",g)},setupTypeListener:function(){const a=function(b,c){let d;if("object"==typeof b)for(let e in b)if(d=a(b[e],c),!1!==d)return e;return c===b&&c},b=function(b){let c,d,e;return(b.target.dataset.typeInitTested=1,c="checkbox"===b.target.type.toLowerCase()?b.target.checked?b.target.value:"0":b.target.value,!c)?(b.target.dataset.type="",void jQuery(b.target).trigger("tsfemForm.typeIsSet")):void(d=tsfemForm.parseElementData(b.target,"setTypeToIfValue"),d&&(e=a(d,c),b.target.dataset.type=e||"",jQuery(b.target).trigger("tsfemForm.typeIsSet")))},c=function(){let a=jQuery("[data-is-type-listener=\"1\"]");a.each(function(a,c){c.dataset.typeInitTested||jQuery(c).off("change.tsfemForm.typeListener").on("change.tsfemForm.typeListener",b).trigger("change.tsfemForm.typeListener")})};c(),jQuery(window).on("tsfemForm.iterationComplete",c)},setupShowIfListener:function(){let a="input, select, textarea",b={duration:150,easing:"linear",queue:!1,start:function(){this.style.willChange="opacity"},done:function(){let a=this.style.display;this.removeAttribute("style"),this.style.display=a}};const c=a=>{let b,c=a.dataset.isShown||void 0;try{b=JSON.parse(c)}catch(a){}if(b)for(let a in b)if(-1==+b[a])return!1;return!0},d=(a,b,c)=>{let d,e=a.dataset.isShown||void 0;try{d=JSON.parse(e)}catch(a){}return(d=d||{},!(d.hasOwnProperty(b)&&+c==+d[b]))&&(d[b]=+c,a.dataset.isShown=JSON.stringify(d),!0)},e=(a,b)=>{let c=a.dataset.disabledShowif;return a.dataset.disabledShowif=c?+c+b:+b},f=(c,f)=>{if(d(c,f,-1)){let d=jQuery(c);d.is(a)?(d.closest(".tsfem-form-setting").fadeOut(b),c.disabled=!0,e(c,1),tsfemForm.triggerCustomValidation(c)):(d.fadeOut(b),d.find(a).each((a,b)=>{b.disabled=!0,e(b,1),tsfemForm.triggerCustomValidation(b)}))}},g=(f,g)=>{if(d(f,g,1)){let d=jQuery(f);if(d.is(a))1>e(f,-1)&&(f.disabled=!1,tsfemForm.triggerCustomValidation(f),d.closest(".tsfem-form-setting").fadeIn(b));else{let f,g=d.hasClass("tsfem-form-multi-setting");d.find(a).each((a,b)=>(f=e(b,-1),g&&!c(b)||void(1>f&&(b.disabled=!1,tsfemForm.triggerCustomValidation(b),jQuery(b).show())))),d.fadeIn(b)}}},h=(a,c,d)=>{let e={};jQuery(c).off("tsfemForm.typeIsSet",a).on("tsfemForm.typeIsSet",a,()=>{clearTimeout(e[c.id]),e[c.id]=setTimeout(()=>{c.dataset.type===d?g(a,c.id):f(a,c.id),delete e[c.id]},b.duration+25)}),c.dataset.type===d?g(a,c.id):f(a,c.id)},i=a=>{let b=tsfemForm.parseElementData(a,"showif");if(!b)return;let c,d,e=jQuery(a).closest(".tsfem-form-collapse-content");for(d in e.length||(e=jQuery(a).closest(".tsfem-form-multi-setting")),b)break;if(d)return c=e?e.find("[data-showif-catcher=\""+d+"\"]"):jQuery(a.form).find("[data-showif-catcher=\""+d+"\"]"),c.length?{target:c[0],value:b[d]}:void 0},j=a=>{a.dataset.showIfIsInit="1";let b=i(a);b&&h(a,b.target,b.value)},k=()=>{let a=jQuery("[data-is-showif-listener=\"1\"]"),c=b.duration;b.duration=0,a.each((a,b)=>{b.dataset.showIfIsInit||j(b)}),b.duration=c};k(),jQuery(window).on("tsfemForm.iterationComplete",k)},adjustSubmit:function(){let a=jQuery("form.tsfem-form");a.each((a,b)=>{jQuery(document.querySelectorAll("[type=submit][form=\""+b.id+"\"]")).attr("onclick","tsfemForm.saveInput( event )")})},doValidityRoutine:function(a,b){if(b=b||tsfemFormL10n.i18n.collapseValidate,!a.checkValidity()){let c=a.querySelector("input:invalid, select:invalid, textarea:invalid"),d=!1;const e=function(){if(d)return void f();try{return void c.reportValidity()}catch(a){tsf.l10n.states.debug}},f=function(){let a=jQuery(c).parents(".tsfem-form-collapse"),g=-1;if(a=jQuery(a.get().reverse()),jQuery(a).each((a,b)=>{let c=jQuery(b).children(".tsfem-form-collapse-checkbox").is(":checked");if(c)return g=a,!1}),-1===g)return d=!1,e();d=!0;let h,i,j;h=a.slice(g).first(),i=h.find(".tsfem-form-collapse-header").first(),j=h.find(".tsfem-form-collapse-checkbox").first();let k;const l=function(a){clearTimeout(k),k=setTimeout(function(){let b=document.documentElement.scrollTop,c=document.documentElement.clientHeight,d=a.offset().top,e=jQuery("#tsfem-sticky-top").height()+1*c/3,f=!1;d-ec+b&&(f=!0),f&&jQuery(document.documentElement).animate({scrollTop:d-e},500)},50)};tsfTT.doTooltip(void 0,i,b),l(i),j.off("change.tsfemForm.removeToolTip").on("change.tsfemForm.removeToolTip",function(a){let b=jQuery(a.target);tsfTT.removeTooltip(b.siblings(".tsfem-form-collapse-header")),b.off("change.tsfemForm.removeToolTip"),f()})};return c?f():e(),!1}return!0},saveInput:function(a){tsfemForm.preventSubmit(a);let b,c,d=a.target.getAttribute("form");if(d){if(b=document.getElementById(d),!b)return tsfemForm.preventSubmit(a);if(!tsfemForm.doValidityRoutine(b,tsfemFormL10n.i18n.collapseValidate))return;c=a.target}else b=a.target,c=document.querySelector("[type=submit][form=\""+b.id+"\"]");let e=jQuery(b).closest(".tsfem-pane-wrap").find(".tsfem-pane-header .tsfem-ajax"),f=0,g="";return tsfemForm.disableButton(c),tsfem.resetAjaxLoader(e),tsfem.setAjaxLoader(e),jQuery.ajax({method:"POST",url:ajaxurl,dataType:"json",data:{action:"tsfemForm_save",nonce:tsfemForm.nonce,data:jQuery(b).serialize()},processData:!0,timeout:14e3,async:!0}).done(function(a){a=tsf.convertJSONResponse(a),tsf.l10n.states.debug&&void 0;let b=a&&a.data||void 0,c=a&&a.type||void 0;if(!b||!c)g=tsfem.i18n.InvalidResponse;else{let a=b.results&&b.results.code||void 0;a?(b.data&&b.data.failed&&b.data.failed.length?(f=0,g=tsfem.i18n.UnknownError):f=1,tsfem_ui.setTopNotice(a)):g=tsfem.i18n.UnknownError}}).fail(function(a,b,c){g=tsfem.getAjaxError(a,b,c),tsfem_ui.setTopNotice(1071101),c&&tsfem_ui.setTopNotice(-1,"Thrown error: "+c)}).always(function(){tsfem.updatedResponse(e,f,g),tsfemForm.enableButton(c)}),!0},preventSubmit:function(a){return a.preventDefault(),a.stopPropagation(),!1},enableButton:function(a){setTimeout(function(){let b=jQuery(a);b.removeClass("tsfem-button-disabled tsfem-button-loading"),b.prop("disabled",!1)},1)},disableButton:function(a,b){b=!(void 0!==b)||b;let c=jQuery(a);b?c.addClass("tsfem-button-disabled tsfem-button-loading"):c.addClass("tsfem-button-disabled"),c.prop("disabled",!0)},enableSubmit:function(a){a&&a.id&&tsfemForm.enableButton("[form=\""+a.id+"\"]")},disableSubmit:function(a,b){a&&a.id&&tsfemForm.disableButton("[form=\""+a.id+"\"]",b)},doReady:function(){tsfemForm.setupIterations(),tsfemForm.setupGeo(),"tsfMedia"in window&&tsfMedia.resetImageEditorActions(),tsfemForm.setupSpecialRequired(),tsfemForm.setupTypeListener(),tsfemForm.setupShowIfListener(),tsfemForm.prepareCollapseItems(),tsfemForm.adjustSubmit()}},jQuery(tsfemForm.doReady); +'use strict';window.tsfemForm={nonce:tsfemFormL10n.nonce,i18n:tsfemFormL10n.i18n,callee:tsfemFormL10n.callee,parseElementData:function(a,b){let c,d=tsfemForm.getElementData(a,b);try{c=d&&JSON.parse(d)}catch(a){c=d}finally{return c}},getElementData:function(a,b){return 0===b.indexOf("data-")&&(b=jQuery.camelCase(b.slice(5))),!!a.dataset.hasOwnProperty(b)&&a.dataset[b]},setupIterations:function(){let a=jQuery(".tsfem-form-iterator-selector-wrap input");if(!a.length)return;tsfemForm.prepareItItems(a);let b,c,d,e,f,g,h,i,j=0,k=1500,l=0,m=0,n=2;const o=function(){b=document.createElement("span"),b.className="tsfem-form-iterator-timer",c=document.createElement("span"),c.style.width="0%",b.appendChild(c)};o(),d=k/(100*n),d*=.975;const p=function(){c.style.width=++l/n+"%"},q=function(){l=0,c.style.width="0%",b.classList.remove("tsfem-form-iterator-timer-invalid")},r=function(){l=100,c.style.width=l+"%",b.classList.add("tsfem-form-iterator-timer-invalid")},s=function(){h&&h.remove(),f=g=h=void 0,o(),setTimeout(function(){a.prop("disabled",!1)},500),a.off("input.fIt").on("input.fIt",e)},t=function(){a.off("input.fIt"),a=jQuery(".tsfem-form-iterator-selector-wrap input"),tsfemForm.prepareItItems(a),tsfemForm.prepareCollapseItems(),tsfemForm.setupGeo(),"tsfMedia"in window&&tsfMedia.resetImageEditorActions(),a.on("input.fIt",e),tsfTT.triggerReset(),i=void 0},u=function(){i&&(i.value=i.dataset.tsfemFormPrevValue)};let v=jQuery(window);v.on("tsfemForm.iterationFail",u),v.on("tsfemForm.iterationLoad tsfemForm.deiterationLoad tsfemForm.iterationFail",s),v.on("tsfemForm.iterationComplete tsfemForm.deiterationComplete tsfemForm.iterationFail",t),e=function(l){return clearInterval(m),clearTimeout(j),q(),tsfemForm.disableSubmit(l.target.form),i=l.target,l.target.checkValidity()?void(m=setInterval(p,d),!f&&(f=jQuery(l.target),g=f.closest(".tsfem-form-setting").find(".tsfem-form-setting-label-inner-wrap"),a.not(l.target).prop("disabled",!0),g.append(b),h=g.find("span.tsfem-form-iterator-timer")),j=setTimeout(function(){if(f.prop("disabled",!0),clearInterval(m),c.style.width="100%",+l.target.value==+l.target.dataset.tsfemFormPrevValue)s(),tsfemForm.enableSubmit(l.target.form);else if(+l.target.value<+l.target.dataset.tsfemFormPrevValue)tsfemForm.unloadIterations(l.target),tsfemForm.enableSubmit(l.target.form);else if(40<+l.target.value){200<+l.target.value?tsfem_ui.dialog({title:tsfemFormL10n.i18n.performanceWarning,text:[tsfemFormL10n.i18n.itHugeConfirm,tsfemFormL10n.i18n.aysProceed],confirm:tsfemFormL10n.i18n.proceed,cancel:tsfemFormL10n.i18n.cancel}):tsfem_ui.dialog({title:tsfemFormL10n.i18n.performanceWarning,text:[tsfemFormL10n.i18n.itLargeConfirm,tsfemFormL10n.i18n.aysProceed],confirm:tsfemFormL10n.i18n.proceed,cancel:tsfemFormL10n.i18n.cancel});let a={};a._cancel=function(){u(),s(),a._reset(),tsfemForm.enableSubmit(l.target.form)},a._confirm=function(){tsfemForm.loadIterations(l.target),a._reset()},a._reset=function(){window.removeEventListener("tsfem_modalCancel",a._cancel),window.removeEventListener("tsfem_modalConfirm",a._confirm)},window.addEventListener("tsfem_modalCancel",a._cancel),window.addEventListener("tsfem_modalConfirm",a._confirm)}else tsfemForm.loadIterations(l.target)},k)):void r()},a.on("input.fIt",e)},unloadIterations:function(a){if(a){let b=a.id.slice(0,a.id.lastIndexOf("[")),c=document.getElementById(b+"-wrapper");c.style.willChange="contents";let d=jQuery(window),e=jQuery(c).children(".tsfem-form-collapse").slice(a.value);d.trigger("tsfemForm.deiterationLoad",[a,e]),e.remove(),d.trigger("tsfemForm.deiterationComplete"),c.style.willChange="auto"}},loadIterations:function(a){if(!a)return;let b=a.id.slice(0,a.id.lastIndexOf("[")),c=jQuery(a).closest(".tsfem-pane-wrap").find(".tsfem-pane-header .tsfem-ajax"),d=0,e="",f=document.getElementById(b+"-wrapper"),g=document.createElement("div");g.className="tsfem-flex-status-loading tsfem-flex tsfem-flex-center",g.appendChild(document.createElement("span")),f.appendChild(g),tsfem.resetAjaxLoader(c),tsfem.setAjaxLoader(c),jQuery.ajax({method:"POST",url:ajaxurl,dataType:"html",data:{action:"tsfemForm_iterate",nonce:tsfemForm.nonce,args:{caller:b,callee:tsfemForm.callee,previousIt:a.dataset.tsfemFormPrevValue,newIt:a.value}},timeout:1e4,async:!0}).done(function(b,c,g){let h=g.getResponseHeader("content-type"),i=jQuery(window);i.trigger("tsfemForm.iterationLoad"),-1h[0].remove())}},h=function(a,b,c){g(a,b,c)},i=function(a){let b=a&&a.find(".tsfem-form-multi-setting-label-inner-wrap")||void 0;b&&b.find(b.children("[data-geo-api-is-button-wrap], [data-geo-api-is-button-warning]")).fadeOut(300,function(){this.remove(),a.removeData("geo-api-has-button")})},j=function(a){return a.find("[data-geo-api=\"1\"]")},k=function(a){let b=j(a);if(!b.length)return 0;let c,d=0,e=b.filter("[data-geo-api-component=\"lat\"]").first().val(),f=b.filter("[data-geo-api-component=\"lng\"]").first().val();if(b.each(function(a,b){if(-1=e?2:0,d=d&&/^(\-|\+)?([0-9]+(\.[0-9]+)?)$/.test(f)&&-180<=f&&180>=f?2:0;else if(c&&(d=/^((([0-9\/-]+([\/-0-9A-Z]+)?(\s|(,\s)))([\u00a1-\uffffa-zA-Z\.\s]|[0-9_/-])+))|(([\u00a1-\uffffa-zA-Z\.\s]|[0-9_/-])+)((\s|(,\s))([0-9\/-]+([\/-0-9A-Z]+)?))$/.test(c)?1:0,!d)){let a=0;b.not("[data-geo-api-component=\"lat\"], [data-geo-api-component=\"lng\"]").each((b,c)=>{if(c.value.length&&1<++a)return!1}),d=1 input").off("change.tsfemForm.prepareItems").on("change.tsfemForm.prepareItems",function(a){tsfemForm.prepareCollapseTitles(a),tsfemForm.prepareCollapseValidity(a)}).trigger("change.tsfemForm.prepareItems")},prepareCollapseTitles:function(a){const b=function(a){let b=jQuery(a.data._tsfemFormLabel),c=b.find(".tsfem-form-collapse-title"),d=b.data("dyntitleprep"),e=jQuery(a.target).val();e?c.text(d+" - "+e):c.text(d)},c=function(a){let b=a.target.checked||!1,c=jQuery(a.data._tsfemFormLabel),d=c.find(".tsfem-form-collapse-title"),e=c.data("dyntitleprep");b?d.text(e+" - "+c.data("dyntitlechecked")):d.text(e)},d=function(a){let b=jQuery(a.data._tsfemFormLabel),c=b.find(".tsfem-form-collapse-title"),d=b.data("dyntitleprep"),e=[];jQuery(a.data._tsfemFormThings).map((a,b)=>{b.checked&&e.push(b.value)});let f=e.join(", ");f?c.text(d+" - "+f):c.text(d)};(function(a){let e,f=jQuery(a.target).siblings("label"),g=f.data("dyntitletype"),h=f.data("dyntitleid")+"["+f.data("dyntitlekey")+"]";switch(g){case"single":case"checkbox":let a=document.getElementById(h);e="input.tsfemForm.doTitleChangeSingle",jQuery(a).off(e).on(e,{_tsfemFormLabel:f},"checkbox"===g?c:b).trigger(e);break;case"plural":let i=jQuery(document.getElementById(h)).find("input[type=checkbox]");e="input.tsfemForm.doTitleChangeSingle";;i.off(e).on(e,{_tsfemFormLabel:f,_tsfemFormThings:i},d).trigger(e);}})(a)},prepareCollapseValidity:function(a){const b=function(a,b){let c="tsfem-form-collapse-header-error",d="tsfem-form-collapse-header-good",e=".tsfem-form-title-icon",f="tsfem-form-title-icon-unknown",g="tsfem-form-title-icon-error",h="tsfem-form-title-icon-good";0b?(a.removeClass(c+" "+d).addClass(d),a.find(e).removeClass(h+" "+g).addClass(f)):(a.removeClass(c).addClass(d),a.find(e).removeClass(f+" "+g).addClass(h))},c=function(a,c){let d,e;a.each(function(a,f){d=jQuery(f),e=(+d.data("tsfemErrorCount")||0)+c,d.data("tsfemErrorCount",e),b(d,e)})},d=function(a){let b=0;b="tsfemWasValid"in a.target.dataset?+a.target.dataset.tsfemWasValid:a.target.dataset.tsfemWasValid=1;let d=a.target.disabled||a.target.checkValidity(),e=jQuery(a.target).parents(".tsfem-form-collapse").children(".tsfem-form-collapse-header"),f=+e.data("tsfemErrorCount");f?b&&!d?(a.target.dataset.tsfemWasValid=+d,c(e,+1)):!b&&d&&(a.target.dataset.tsfemWasValid=+d,c(e,-1)):d?c(e,0):(a.target.dataset.tsfemWasValid=+d,c(e,+1))},e=function(a,b){let c=a.dataset.tsfemDidInitialValidation||0;c||(b.on("tsfemForm.first.checkValidity",d),b.each((a,b)=>{jQuery(b).trigger("tsfemForm.first.checkValidity")}),b.off("tsfemForm.first.checkValidity"),a.dataset.tsfemDidInitialValidation=1)};(function(a){let b=jQuery(a.target).siblings(".tsfem-form-collapse-content").find("input, select, textarea").not(".tsfem-form-collapse-checkbox");b.off("change.tsfemForm.checkValidity"),a.target.checked||b.on("change.tsfemForm.checkValidity",d),e(a.target,b)})(a),tsfemForm.prepareCustomChecks(d),tsfemForm.prepareDeiterationValidityChecks(d)},prepareCustomChecks:function(a){const b=function(b,c){if(c){let b=jQuery(c);b.length&&(b.one("tsfemForm.temp.customChecks.checkValidityCb",a),b.trigger("tsfemForm.temp.customChecks.checkValidityCb"))}},c=function(){jQuery(window).on("tsfemForm.customValidationChecks",b)},d=function(){jQuery(window).off("tsfemForm.customValidationChecks")};(function(){d(),c(),jQuery(window).off("tsfemForm.iterationLoad.customValidation").off("tsfemForm.iterationFail.customValidation").off("tsfemForm.iterationComplete.customValidation").on("tsfemForm.iterationLoad.customValidation",d).on("tsfemForm.iterationFail.customValidation",c).on("tsfemForm.iterationComplete.customValidation",c)})()},triggerCustomValidation:function(a){a instanceof HTMLElement&&jQuery(window).trigger("tsfemForm.customValidationChecks",[a])},prepareDeiterationValidityChecks:function(a){const b=function(b,c,d){if(d){let b=d.children(".tsfem-form-collapse-content").find("input:invalid, select:invalid, textarea:invalid");b&&(b.prop("disabled",!0),b.on("tsfemForm.temp.disableAndValidate.checkValidityCb",a),b.each((a,b)=>{jQuery(b).trigger("tsfemForm.temp.disableAndValidate.checkValidityCb")}),b.off("tsfemForm.temp.disableAndValidate.checkValidityCb"))}};(function(){jQuery(window).off("tsfemForm.deiterationLoad.disableAndValidate").on("tsfemForm.deiterationLoad.disableAndValidate",b)})()},setupSpecialRequired:function(){let a="tsfem-form-checkbox-required";{var b=document.createElement("input");b.className=a,b.setAttribute("type","checkbox"),b.setAttribute("required","required"),b.setAttribute("value","1"),b.setAttribute("tabIndex","-1")}const c=function(a,b){let c=(+a.data("required-check-count")||0)+b;return a.data("required-check-count",c),c},d=function(c){if(!c.children("."+a).length){let a=b.cloneNode(!1);a.setCustomValidity(tsfemForm.i18n.requiredSelectAny),jQuery(a).prependTo(c),tsfemForm.triggerCustomValidation(a)}c.removeClass("tsfem-form-multi-valid").addClass("tsfem-form-multi-invalid")},e=function(b){let c=b.children("."+a);if(c.length){let a=c[0].cloneNode(!1);c.remove(),a.checked=!0,a.required=!1,jQuery(a).prependTo(b),tsfemForm.triggerCustomValidation(a),a.remove()}b.removeClass("tsfem-form-multi-invalid").addClass("tsfem-form-multi-valid")},f=function(a){if(a.target.disabled)return;let b,f=jQuery(a.target).closest(".tsfem-form-multi-select-wrap[data-required=\"1\"]"),g=f.data("required-check-count");b=a.target.checked?c(f,+1):c(f,-1),1>g?b>g&&e(f):1>b&&d(f)},g=function(){let a,b,e,g=jQuery(".tsfem-form-multi-select-wrap[data-required=\"1\"]");g.each(function(g,h){b=h.dataset.requiredCheckCount,void 0===b&&(a=jQuery(h),a.find("input").on("change.tsfemForm.testChecked",f),e=a.find("input:checked").length,e?a.addClass("tsfem-form-multi-valid"):d(a),c(a,+e))})};g(),jQuery(window).on("tsfemForm.iterationComplete",g)},setupTypeListener:function(){const a=function(b,c){let d;if("object"==typeof b)for(let e in b)if(d=a(b[e],c),!1!==d)return e;return c===b&&c},b=function(b){let c,d,e;return(b.target.dataset.typeInitTested=1,c="checkbox"===b.target.type.toLowerCase()?b.target.checked?b.target.value:"0":b.target.value,!c)?(b.target.dataset.type="",void jQuery(b.target).trigger("tsfemForm.typeIsSet")):void(d=tsfemForm.parseElementData(b.target,"setTypeToIfValue"),d&&(e=a(d,c),b.target.dataset.type=e||"",jQuery(b.target).trigger("tsfemForm.typeIsSet")))},c=function(){let a=jQuery("[data-is-type-listener=\"1\"]");a.each(function(a,c){c.dataset.typeInitTested||jQuery(c).off("change.tsfemForm.typeListener").on("change.tsfemForm.typeListener",b).trigger("change.tsfemForm.typeListener")})};c(),jQuery(window).on("tsfemForm.iterationComplete",c)},setupShowIfListener:function(){let a="input, select, textarea",b={duration:150,easing:"linear",queue:!1,start:function(){this.style.willChange="opacity"},done:function(){let a=this.style.display;this.removeAttribute("style"),this.style.display=a}};const c=a=>{let b,c=a.dataset.isShown||void 0;try{b=JSON.parse(c)}catch(a){}if(b)for(let a in b)if(-1==+b[a])return!1;return!0},d=(a,b,c)=>{let d,e=a.dataset.isShown||void 0;try{d=JSON.parse(e)}catch(a){}return(d=d||{},!(d.hasOwnProperty(b)&&+c==+d[b]))&&(d[b]=+c,a.dataset.isShown=JSON.stringify(d),!0)},e=(a,b)=>{let c=a.dataset.disabledShowif;return a.dataset.disabledShowif=c?+c+b:+b},f=(c,f)=>{if(d(c,f,-1)){let d=jQuery(c);d.is(a)?(d.closest(".tsfem-form-setting").fadeOut(b),c.disabled=!0,e(c,1),tsfemForm.triggerCustomValidation(c)):(d.fadeOut(b),d.find(a).each((a,b)=>{b.disabled=!0,e(b,1),tsfemForm.triggerCustomValidation(b)}))}},g=(f,g)=>{if(d(f,g,1)){let d=jQuery(f);if(d.is(a))1>e(f,-1)&&(f.disabled=!1,tsfemForm.triggerCustomValidation(f),d.closest(".tsfem-form-setting").fadeIn(b));else{let f,g=d.hasClass("tsfem-form-multi-setting");d.find(a).each((a,b)=>(f=e(b,-1),g&&!c(b)||void(1>f&&(b.disabled=!1,tsfemForm.triggerCustomValidation(b),jQuery(b).show())))),d.fadeIn(b)}}},h=(a,c,d)=>{let e={};jQuery(c).off("tsfemForm.typeIsSet",a).on("tsfemForm.typeIsSet",a,()=>{clearTimeout(e[c.id]),e[c.id]=setTimeout(()=>{c.dataset.type===d?g(a,c.id):f(a,c.id),delete e[c.id]},b.duration+25)}),c.dataset.type===d?g(a,c.id):f(a,c.id)},i=a=>{let b=tsfemForm.parseElementData(a,"showif");if(!b)return;let c,d,e=jQuery(a).closest(".tsfem-form-collapse-content");for(d in e.length||(e=jQuery(a).closest(".tsfem-form-multi-setting")),b)break;if(d)return c=e?e.find("[data-showif-catcher=\""+d+"\"]"):jQuery(a.form).find("[data-showif-catcher=\""+d+"\"]"),c.length?{target:c[0],value:b[d]}:void 0},j=a=>{a.dataset.showIfIsInit="1";let b=i(a);b&&h(a,b.target,b.value)},k=()=>{let a=jQuery("[data-is-showif-listener=\"1\"]"),c=b.duration;b.duration=0,a.each((a,b)=>{b.dataset.showIfIsInit||j(b)}),b.duration=c};k(),jQuery(window).on("tsfemForm.iterationComplete",k)},adjustSubmit:function(){let a=jQuery("form.tsfem-form");a.each((a,b)=>{jQuery(document.querySelectorAll("[type=submit][form=\""+b.id+"\"]")).attr("onclick","tsfemForm.saveInput( event )")})},doValidityRoutine:function(a,b){if(b=b||tsfemFormL10n.i18n.collapseValidate,!a.checkValidity()){let c=a.querySelector("input:invalid, select:invalid, textarea:invalid"),d=!1;const e=function(){if(d)return void f();try{return void c.reportValidity()}catch(a){tsf.l10n.states.debug}},f=function(){let a=jQuery(c).parents(".tsfem-form-collapse"),g=-1;if(a=jQuery(a.get().reverse()),jQuery(a).each((a,b)=>{let c=jQuery(b).children(".tsfem-form-collapse-checkbox").is(":checked");if(c)return g=a,!1}),-1===g)return d=!1,e();d=!0;let h,i,j;h=a.slice(g).first(),i=h.find(".tsfem-form-collapse-header").first(),j=h.find(".tsfem-form-collapse-checkbox").first();let k;const l=function(a){clearTimeout(k),k=setTimeout(function(){let b=document.documentElement.scrollTop,c=document.documentElement.clientHeight,d=a.offset().top,e=jQuery("#tsfem-sticky-top").height()+1*c/3,f=!1;d-ec+b&&(f=!0),f&&jQuery(document.documentElement).animate({scrollTop:d-e},500)},50)};tsfTT.doTooltip(void 0,i,b),l(i),j.off("change.tsfemForm.removeToolTip").on("change.tsfemForm.removeToolTip",function(a){let b=jQuery(a.target);tsfTT.removeTooltip(b.siblings(".tsfem-form-collapse-header")),b.off("change.tsfemForm.removeToolTip"),f()})};return c?f():e(),!1}return!0},saveInput:function(a){tsfemForm.preventSubmit(a);let b,c,d=a.target.getAttribute("form");if(d){if(b=document.getElementById(d),!b)return tsfemForm.preventSubmit(a);if(!tsfemForm.doValidityRoutine(b,tsfemFormL10n.i18n.collapseValidate))return;c=a.target}else b=a.target,c=document.querySelector("[type=submit][form=\""+b.id+"\"]");let e=jQuery(b).closest(".tsfem-pane-wrap").find(".tsfem-pane-header .tsfem-ajax"),f=0,g="";return tsfemForm.disableButton(c),tsfem.resetAjaxLoader(e),tsfem.setAjaxLoader(e),jQuery.ajax({method:"POST",url:ajaxurl,dataType:"json",data:{action:"tsfemForm_save",nonce:tsfemForm.nonce,data:jQuery(b).serialize()},processData:!0,timeout:14e3,async:!0}).done(function(a){a=tsf.convertJSONResponse(a),tsf.l10n.states.debug&&void 0;let b=a&&a.data||void 0,c=a&&a.type||void 0;if(!b||!c)g=tsfem.i18n.InvalidResponse;else{let a=b.results&&b.results.code||void 0;a?(b.data&&b.data.failed&&b.data.failed.length?(f=0,g=tsfem.i18n.UnknownError):f=1,tsfem_ui.setTopNotice(a)):g=tsfem.i18n.UnknownError}}).fail(function(a,b,c){g=tsfem.getAjaxError(a,b,c),tsfem_ui.setTopNotice(1071101),c&&tsfem_ui.setTopNotice(-1,"Thrown error: "+c)}).always(function(){tsfem.updatedResponse(e,f,g),tsfemForm.enableButton(c)}),!0},preventSubmit:function(a){return a.preventDefault(),a.stopPropagation(),!1},enableButton:function(a){setTimeout(function(){let b=jQuery(a);b.removeClass("tsfem-button-disabled tsfem-button-loading"),b.prop("disabled",!1)},1)},disableButton:function(a,b){b=!(void 0!==b)||b;let c=jQuery(a);b?c.addClass("tsfem-button-disabled tsfem-button-loading"):c.addClass("tsfem-button-disabled"),c.prop("disabled",!0)},enableSubmit:function(a){a&&a.id&&tsfemForm.enableButton("[form=\""+a.id+"\"]")},disableSubmit:function(a,b){a&&a.id&&tsfemForm.disableButton("[form=\""+a.id+"\"]",b)},doReady:function(){tsfemForm.setupIterations(),tsfemForm.setupGeo(),"tsfMedia"in window&&tsfMedia.resetImageEditorActions(),tsfemForm.setupSpecialRequired(),tsfemForm.setupTypeListener(),tsfemForm.setupShowIfListener(),tsfemForm.prepareCollapseItems(),tsfemForm.adjustSubmit()}},jQuery(tsfemForm.doReady); diff --git a/lib/js/tsfem-ui.js b/lib/js/tsfem-ui.js index b6c82dbd..1d897a41 100644 --- a/lib/js/tsfem-ui.js +++ b/lib/js/tsfem-ui.js @@ -433,31 +433,29 @@ window.tsfem_ui = function( $ ) { selectWrapItem.label = document.createElement( 'label' ); - (() => { - for ( let i in select ) { - let wrap = selectWrapItem.wrap.cloneNode( true ), - radio = selectWrapItem.radio.cloneNode( false ), - label = selectWrapItem.label.cloneNode( false ); - - radio.setAttribute( 'value', i ); - label.innerHTML = select[ i ]; - - //= i can be a string and integer because of "possible" JSON parsing. - if ( i == 0 ) { - radio.checked = true; - } + for ( let i in select ) { + let wrap = selectWrapItem.wrap.cloneNode( true ), + radio = selectWrapItem.radio.cloneNode( false ), + label = selectWrapItem.label.cloneNode( false ); + + radio.setAttribute( 'value', i ); + label.innerHTML = select[ i ]; - let id = 'tsfem-dialog-option-' + i; + //= i can be a string and integer because of "possible" JSON parsing. + if ( i == 0 ) { + radio.checked = true; + } - radio.setAttribute( 'id', id ); - label.setAttribute( 'for', id ); + let id = `tsfem-dialog-option-${i}`; - wrap.appendChild( radio ); - wrap.appendChild( label ); + radio.setAttribute( 'id', id ); + label.setAttribute( 'for', id ); - modal.selectWrap.appendChild( wrap ); - } - })(); + wrap.appendChild( radio ); + wrap.appendChild( label ); + + modal.selectWrap.appendChild( wrap ); + } modal.inner.appendChild( modal.selectWrap ); } @@ -529,7 +527,7 @@ window.tsfem_ui = function( $ ) { modal.maskNoScroll.addEventListener( 'wheel', preventDefault ); modal.maskNoScroll.addEventListener( 'touchmove', preventDefault ); - const resizeListener = function() { + const resizeListener = () => { modal.dialogWrap.style.marginLeft = document.getElementById( 'adminmenuwrap' ).offsetWidth + 'px'; modal.dialogWrap.style.marginTop = document.getElementById( 'wpadminbar' ).offsetHeight + 'px'; } @@ -549,6 +547,21 @@ window.tsfem_ui = function( $ ) { window.addEventListener( 'tsfem_modalConfirm', removeModal ); } + /** + * Observes loggers and autoscrolls them if necessary. + * + * @since 2.5.0 + * + * @param {HTMLElement} logger + */ + const observeLogger = logger => { + ( new MutationObserver( () => { + // If 66% of bottom still is in view, then scroll. + if ( logger.scrollHeight - logger.scrollTop < logger.clientHeight * ( 4 / 3 ) ) + logger.scrollTop = logger.scrollHeight; + } ) ).observe( logger, { childList: true } ); + } + /** * Runs document-on-ready actions. * @@ -560,6 +573,8 @@ window.tsfem_ui = function( $ ) { const _doReady = () => { // Reset switcher button to default when clicked outside. $( '.tsfem-switch-button-container-wrap' ).on( 'click', 'label', _engageSwitcher ); + + document.querySelectorAll( '.tsfem-logger' ).forEach( el => observeLogger( el ) ); } return Object.assign( { diff --git a/lib/js/tsfem-ui.min.js b/lib/js/tsfem-ui.min.js index 9a5d8631..661f33a0 100644 --- a/lib/js/tsfem-ui.min.js +++ b/lib/js/tsfem-ui.min.js @@ -1 +1 @@ -'use strict';window.tsfem_ui=function(a){const b=tsfemUIL10n.nonce,c=tsfemUIL10n.i18n,d=()=>{const b="click.tsfemResetSwitcher";a(window).off(b).on(b,c=>{let d=a(".tsfem-switch-button-container > input[type=\"checkbox\"]:checked");if("undefined"!=typeof d&&0a(c.target).closest(e).length&&(d.prop("checked",!1),a(window).off(b))}})},e=(a,b,c,d)=>{var e=Number.parseFloat;if(void 0===a||!a instanceof HTMLElement)return;if(!a.style||!("opacity"in a.style))return;a.style.willChange="opacity",b=b||250,d=!(void 0!==d)||d;let f,g,h,i=0;d?(a.style.opacity=0,a.style.display=null,h=d=>{f=f||d,g=(d-f)/b,i=e(g).toPrecision(2),a.style.opacity=i,1<=i?(a.style.opacity=1,a.style.willChange="auto","function"==typeof c&&c()):(a.style.opacity=i,requestAnimationFrame(h))}):(i=100,h=d=>{f=f||d,g=(d-f)/b,i=e(1-g).toPrecision(2),0>=i?(a.style.opacity=0,a.style.willChange="auto",setTimeout(()=>{a.style.display="none"},0),"function"==typeof c&&c()):(a.style.opacity=i,requestAnimationFrame(h))}),requestAnimationFrame(h)},f=(a,b,c)=>e(a,b,c,!1);let g=!1;const h=(b,c)=>{if(g)return void window.setTimeout(()=>{h(b,c)},500);g=!0;let d=c?1:0;a.ajax({method:"POST",url:ajaxurl,datatype:"json",data:{action:"tsfem_get_dismissible_notice",nonce:tsfem.insecureNonce,"tsfem-notice-key":b,"tsfem-notice-has-msg":d},timeout:7e3,async:!0}).done(b=>{b=tsf.convertJSONResponse(b),tsf.l10n.states.debug&&void 0;let e=b&&b.data||void 0,f=b&&b.type||void 0;if(!e||!f||"undefined"==typeof e.notice);else{let b=e.notice;d&&(b=a(b),window.isRtl?b.find("p").first().prepend(c+" "):b.find("p").first().append(" "+c)),i(b)}}).fail(()=>{tsf.l10n.states.debug&&(void 0,void 0);let a=d?wp.template("tsfem-fbtopnotice-msg"):wp.template("tsfem-fbtopnotice"),e=a({code:b,msg:c});i(e)}).always(()=>{g=!1})},i=b=>{let c=a("#tsfem-notice-wrap"),d=c.children(".tsf-notice, #tsfem-notice-wrap .notice"),e={duration:200,queue:!1};c.css("willChange","contents"),1this.remove()}))}),a(b).hide().appendTo(c).slideDown(a.extend(e,{complete:()=>c.css("maxHeight","")})),tsf.triggerNoticeReset()},j=a=>{a=tsf.convertJSONResponse(a)||void 0;let b=a&&a.data||void 0;tsf.l10n.states.debug&&void 0,b&&"results"in b&&"code"in b.results&&h(b.results.code,b.results.notice)},k=()=>{a(".tsfem-switch-button-container-wrap").on("click","label",d)};return Object.assign({load:()=>{document.body.addEventListener("tsf-ready",k)}},{},{fadeIn:e,fadeOut:f,setTopNotice:h,unexpectedAjaxErrorNotice:j,dialog:a=>{let b=a.title||"",c=a.text||"",d=a.select||"",g=a.confirm||"",h=a.cancel||"",j={};if(j.mask=document.createElement("div"),j.mask.className="tsfem-modal-mask",j.maskNoScroll=document.createElement("div"),j.maskNoScroll.className="tsfem-modal-mask-noscroll",j.mask.appendChild(j.maskNoScroll),j.container=document.createElement("div"),j.container.className="tsfem-modal-container",j.dialogWrap=document.createElement("div"),j.dialogWrap.className="tsfem-modal-dialog-wrap",j.dialogWrap.style.marginLeft=document.getElementById("adminmenuwrap").offsetWidth+"px",j.dialogWrap.style.marginTop=document.getElementById("wpadminbar").offsetHeight+"px",j.dialog=document.createElement("div"),j.dialog.className="tsfem-modal-dialog",j.trap=document.createElement("div"),j.trap.className="tsfem-modal-trap",j.trap.tabIndex=0,j.bottomTrap=j.trap.cloneNode(!1),j.dialog.appendChild(j.trap),j.x=document.createElement("div"),j.x.className="tsfem-modal-dismiss",j.x.addEventListener("click",()=>{window.dispatchEvent(new Event("tsfem_modalCancel"))}),j.dialog.appendChild(j.x),b&&(j.titleWrap=document.createElement("div"),j.titleWrap.className="tsfem-modal-title",j.titleWrapTitle=document.createElement("h4"),j.titleWrapTitle.innerHTML=b,j.titleWrap.appendChild(j.titleWrapTitle),j.dialog.appendChild(j.titleWrap)),j.inner=document.createElement("div"),j.inner.className="tsfem-modal-inner",c){if(j.textWrap=document.createElement("div"),j.textWrap.className="tsfem-modal-text",Array.isArray(c))for(let a in c)j.textWrapContent=document.createElement("p"),j.textWrapContent.innerHTML=c[a],j.textWrap.appendChild(j.textWrapContent);else j.textWrapContent=document.createElement("p"),j.textWrapContent.innerHTML=c,j.textWrap.appendChild(j.textWrapContent);j.inner.appendChild(j.textWrap)}let k=!1;if(d){k=!0,j.selectWrap=document.createElement("div"),j.selectWrap.className="tsfem-modal-select";let a={};a.wrap=document.createElement("div"),a.wrap.className="tsfem-modal-select-option",a.radio=document.createElement("input"),a.radio.setAttribute("type","radio"),a.radio.setAttribute("name","tsfem-modal-select-option-group"),a.radio.tabIndex=0,a.label=document.createElement("label"),(()=>{for(let b in d){let c=a.wrap.cloneNode(!0),e=a.radio.cloneNode(!1),f=a.label.cloneNode(!1);e.setAttribute("value",b),f.innerHTML=d[b],0==b&&(e.checked=!0);let g="tsfem-dialog-option-"+b;e.setAttribute("id",g),f.setAttribute("for",g),c.appendChild(e),c.appendChild(f),j.selectWrap.appendChild(c)}})(),j.inner.appendChild(j.selectWrap)}j.dialog.appendChild(j.inner),(g||h)&&(j.buttonWrap=document.createElement("div"),j.buttonWrap.className="tsfem-modal-buttons",g&&(j.confirmButton=document.createElement("button"),j.confirmButton.className="tsfem-modal-confirm tsfem-button-small",j.confirmButton.className+=k?" tsfem-button-primary tsfem-button-primary-bright":" tsfem-button",j.confirmButton.innerHTML=g,j.confirmButton.addEventListener("click",function(){let a;k&&(a={detail:{checked:document.querySelector(".tsfem-modal-select input:checked").value}}),window.dispatchEvent(new CustomEvent("tsfem_modalConfirm",a))}),j.buttonWrap.appendChild(j.confirmButton)),h&&(j.cancelButton=document.createElement("button"),j.cancelButton.className="tsfem-modal-cancel tsfem-button tsfem-button-small",j.cancelButton.innerHTML=h,j.cancelButton.addEventListener("click",()=>{window.dispatchEvent(new Event("tsfem_modalCancel"))}),j.buttonWrap.appendChild(j.cancelButton)),j.dialog.appendChild(j.buttonWrap)),j.dialog.appendChild(j.bottomTrap),j.dialogWrap.appendChild(j.dialog),j.container.appendChild(j.dialogWrap),document.body.appendChild(j.mask),document.body.appendChild(j.container);const l=()=>{j.trap.focus()};j.trap.addEventListener("focus",l),j.bottomTrap.addEventListener("focus",l),j.trap.focus(),e(j.mask),e(j.container);const m=a=>{a.preventDefault()};j.maskNoScroll.addEventListener("wheel",m),j.maskNoScroll.addEventListener("touchmove",m);const n=function(){j.dialogWrap.style.marginLeft=document.getElementById("adminmenuwrap").offsetWidth+"px",j.dialogWrap.style.marginTop=document.getElementById("wpadminbar").offsetHeight+"px"};window.addEventListener("resize",n);const o=()=>{j.maskNoScroll.removeEventListener("wheel",m),j.maskNoScroll.removeEventListener("touchmove",m),window.removeEventListener("tsfem_modalCancel",o),window.removeEventListener("tsfem_modalConfirm",o),window.removeEventListener("resize",n),f(j.mask,250,()=>j.mask.remove()),f(j.container,250,()=>j.container.remove())};window.addEventListener("tsfem_modalCancel",o),window.addEventListener("tsfem_modalConfirm",o)}})}(jQuery),window.tsfem_ui.load(); +'use strict';window.tsfem_ui=function(a){const b=tsfemUIL10n.nonce,c=tsfemUIL10n.i18n,d=()=>{const b="click.tsfemResetSwitcher";a(window).off(b).on(b,c=>{let d=a(".tsfem-switch-button-container > input[type=\"checkbox\"]:checked");if("undefined"!=typeof d&&0a(c.target).closest(e).length&&(d.prop("checked",!1),a(window).off(b))}})},e=(a,b,c,d)=>{var e=Number.parseFloat;if(void 0===a||!a instanceof HTMLElement)return;if(!a.style||!("opacity"in a.style))return;a.style.willChange="opacity",b=b||250,d=!(void 0!==d)||d;let f,g,h,i=0;d?(a.style.opacity=0,a.style.display=null,h=d=>{f=f||d,g=(d-f)/b,i=e(g).toPrecision(2),a.style.opacity=i,1<=i?(a.style.opacity=1,a.style.willChange="auto","function"==typeof c&&c()):(a.style.opacity=i,requestAnimationFrame(h))}):(i=100,h=d=>{f=f||d,g=(d-f)/b,i=e(1-g).toPrecision(2),0>=i?(a.style.opacity=0,a.style.willChange="auto",setTimeout(()=>{a.style.display="none"},0),"function"==typeof c&&c()):(a.style.opacity=i,requestAnimationFrame(h))}),requestAnimationFrame(h)},f=(a,b,c)=>e(a,b,c,!1);let g=!1;const h=(b,c)=>{if(g)return void window.setTimeout(()=>{h(b,c)},500);g=!0;let d=c?1:0;a.ajax({method:"POST",url:ajaxurl,datatype:"json",data:{action:"tsfem_get_dismissible_notice",nonce:tsfem.insecureNonce,"tsfem-notice-key":b,"tsfem-notice-has-msg":d},timeout:7e3,async:!0}).done(b=>{b=tsf.convertJSONResponse(b),tsf.l10n.states.debug&&void 0;let e=b&&b.data||void 0,f=b&&b.type||void 0;if(!e||!f||"undefined"==typeof e.notice);else{let b=e.notice;d&&(b=a(b),window.isRtl?b.find("p").first().prepend(c+" "):b.find("p").first().append(" "+c)),i(b)}}).fail(()=>{tsf.l10n.states.debug&&(void 0,void 0);let a=d?wp.template("tsfem-fbtopnotice-msg"):wp.template("tsfem-fbtopnotice"),e=a({code:b,msg:c});i(e)}).always(()=>{g=!1})},i=b=>{let c=a("#tsfem-notice-wrap"),d=c.children(".tsf-notice, #tsfem-notice-wrap .notice"),e={duration:200,queue:!1};c.css("willChange","contents"),1this.remove()}))}),a(b).hide().appendTo(c).slideDown(a.extend(e,{complete:()=>c.css("maxHeight","")})),tsf.triggerNoticeReset()},j=a=>{a=tsf.convertJSONResponse(a)||void 0;let b=a&&a.data||void 0;tsf.l10n.states.debug&&void 0,b&&"results"in b&&"code"in b.results&&h(b.results.code,b.results.notice)},k=a=>{new MutationObserver(()=>{a.scrollHeight-a.scrollTop{a(".tsfem-switch-button-container-wrap").on("click","label",d),document.querySelectorAll(".tsfem-logger").forEach(a=>k(a))};return Object.assign({load:()=>{document.body.addEventListener("tsf-ready",l)}},{},{fadeIn:e,fadeOut:f,setTopNotice:h,unexpectedAjaxErrorNotice:j,dialog:a=>{let b=a.title||"",c=a.text||"",d=a.select||"",g=a.confirm||"",h=a.cancel||"",j={};if(j.mask=document.createElement("div"),j.mask.className="tsfem-modal-mask",j.maskNoScroll=document.createElement("div"),j.maskNoScroll.className="tsfem-modal-mask-noscroll",j.mask.appendChild(j.maskNoScroll),j.container=document.createElement("div"),j.container.className="tsfem-modal-container",j.dialogWrap=document.createElement("div"),j.dialogWrap.className="tsfem-modal-dialog-wrap",j.dialogWrap.style.marginLeft=document.getElementById("adminmenuwrap").offsetWidth+"px",j.dialogWrap.style.marginTop=document.getElementById("wpadminbar").offsetHeight+"px",j.dialog=document.createElement("div"),j.dialog.className="tsfem-modal-dialog",j.trap=document.createElement("div"),j.trap.className="tsfem-modal-trap",j.trap.tabIndex=0,j.bottomTrap=j.trap.cloneNode(!1),j.dialog.appendChild(j.trap),j.x=document.createElement("div"),j.x.className="tsfem-modal-dismiss",j.x.addEventListener("click",()=>{window.dispatchEvent(new Event("tsfem_modalCancel"))}),j.dialog.appendChild(j.x),b&&(j.titleWrap=document.createElement("div"),j.titleWrap.className="tsfem-modal-title",j.titleWrapTitle=document.createElement("h4"),j.titleWrapTitle.innerHTML=b,j.titleWrap.appendChild(j.titleWrapTitle),j.dialog.appendChild(j.titleWrap)),j.inner=document.createElement("div"),j.inner.className="tsfem-modal-inner",c){if(j.textWrap=document.createElement("div"),j.textWrap.className="tsfem-modal-text",Array.isArray(c))for(let a in c)j.textWrapContent=document.createElement("p"),j.textWrapContent.innerHTML=c[a],j.textWrap.appendChild(j.textWrapContent);else j.textWrapContent=document.createElement("p"),j.textWrapContent.innerHTML=c,j.textWrap.appendChild(j.textWrapContent);j.inner.appendChild(j.textWrap)}let k=!1;if(d){k=!0,j.selectWrap=document.createElement("div"),j.selectWrap.className="tsfem-modal-select";let a={};for(let b in a.wrap=document.createElement("div"),a.wrap.className="tsfem-modal-select-option",a.radio=document.createElement("input"),a.radio.setAttribute("type","radio"),a.radio.setAttribute("name","tsfem-modal-select-option-group"),a.radio.tabIndex=0,a.label=document.createElement("label"),d){let c=a.wrap.cloneNode(!0),e=a.radio.cloneNode(!1),f=a.label.cloneNode(!1);e.setAttribute("value",b),f.innerHTML=d[b],0==b&&(e.checked=!0);let g=`tsfem-dialog-option-${b}`;e.setAttribute("id",g),f.setAttribute("for",g),c.appendChild(e),c.appendChild(f),j.selectWrap.appendChild(c)}j.inner.appendChild(j.selectWrap)}j.dialog.appendChild(j.inner),(g||h)&&(j.buttonWrap=document.createElement("div"),j.buttonWrap.className="tsfem-modal-buttons",g&&(j.confirmButton=document.createElement("button"),j.confirmButton.className="tsfem-modal-confirm tsfem-button-small",j.confirmButton.className+=k?" tsfem-button-primary tsfem-button-primary-bright":" tsfem-button",j.confirmButton.innerHTML=g,j.confirmButton.addEventListener("click",function(){let a;k&&(a={detail:{checked:document.querySelector(".tsfem-modal-select input:checked").value}}),window.dispatchEvent(new CustomEvent("tsfem_modalConfirm",a))}),j.buttonWrap.appendChild(j.confirmButton)),h&&(j.cancelButton=document.createElement("button"),j.cancelButton.className="tsfem-modal-cancel tsfem-button tsfem-button-small",j.cancelButton.innerHTML=h,j.cancelButton.addEventListener("click",()=>{window.dispatchEvent(new Event("tsfem_modalCancel"))}),j.buttonWrap.appendChild(j.cancelButton)),j.dialog.appendChild(j.buttonWrap)),j.dialog.appendChild(j.bottomTrap),j.dialogWrap.appendChild(j.dialog),j.container.appendChild(j.dialogWrap),document.body.appendChild(j.mask),document.body.appendChild(j.container);const l=()=>{j.trap.focus()};j.trap.addEventListener("focus",l),j.bottomTrap.addEventListener("focus",l),j.trap.focus(),e(j.mask),e(j.container);const m=a=>{a.preventDefault()};j.maskNoScroll.addEventListener("wheel",m),j.maskNoScroll.addEventListener("touchmove",m);const n=()=>{j.dialogWrap.style.marginLeft=document.getElementById("adminmenuwrap").offsetWidth+"px",j.dialogWrap.style.marginTop=document.getElementById("wpadminbar").offsetHeight+"px"};window.addEventListener("resize",n);const o=()=>{j.maskNoScroll.removeEventListener("wheel",m),j.maskNoScroll.removeEventListener("touchmove",m),window.removeEventListener("tsfem_modalCancel",o),window.removeEventListener("tsfem_modalConfirm",o),window.removeEventListener("resize",n),f(j.mask,250,()=>j.mask.remove()),f(j.container,250,()=>j.container.remove())};window.addEventListener("tsfem_modalCancel",o),window.addEventListener("tsfem_modalConfirm",o)}})}(jQuery),window.tsfem_ui.load(); diff --git a/lib/js/tsfem.js b/lib/js/tsfem.js index 12ed7da2..708b565c 100644 --- a/lib/js/tsfem.js +++ b/lib/js/tsfem.js @@ -131,7 +131,7 @@ window.tsfem = function( $ ) { notice ? $( '' ).html( notice ).text() : '' ).fadeOut( notice ? fade * 2 : fade - ); + ); } /** diff --git a/lib/js/tsfinstaller.js b/lib/js/tsfinstaller.js index 5b595bc8..fd53c013 100644 --- a/lib/js/tsfinstaller.js +++ b/lib/js/tsfinstaller.js @@ -9,27 +9,27 @@ * @source */ - 'use strict'; +'use strict'; - /* global pagenow */ +/* global pagenow */ - /** - * Hooks into WordPress's updates handler. - * This is a self-constructed function assigned as an object. - * - * @since 2.5.0 - * - * @constructor - * @param {jQuery} $ jQuery object. - * @param {object} wp WP object. - */ - window.tsfinstaller = function( $, wp ) { +/** + * Hooks into WordPress's updates handler. + * This is a self-constructed function assigned as an object. + * + * @since 2.5.0 + * + * @constructor + * @param {jQuery} $ jQuery object. + * @param {object} wp WP object. + */ +window.tsfinstaller = function( $, wp ) { - const $document = $( document ); + const $document = $( document ); - const { __, _x, sprintf } = wp.i18n; + const { __, _x, sprintf } = wp.i18n; - /** + /** * Updates the UI appropriately after a successful TSFEM install. * * @since 2.5.0 @@ -44,290 +44,290 @@ * FIXME? This property can be empty if the plugin is already activated... * probably due to reinstallation via this script: Mega-edge-case. */ - const installTsfSuccess = response => { - - let $button = $( '[data-slug="' + response.slug + '"]' ); - - // if ( ! response.activateUrl ) { - // $button.remove(); - // } else { - $button - .removeClass( 'install-now installed button-disabled updating-message' ) - .addClass( 'activate-now' ) - .attr( { - href: response.activateUrl + '&from=plugins', - 'aria-label': sprintf( - /* translators: %s: Plugin name. */ - 'plugins-network' === pagenow ? _x( 'Network Activate %s', 'plugin' ) : _x( 'Activate %s', 'plugin' ), - response.pluginName - ) - } ) - .text( 'plugins-network' === pagenow ? __( 'Network Activate' ) : __( 'Activate' ) ); - - let $successButton = $button.clone()[0].outerHTML; - - wp.updates.addAdminNotice( { - id: 'install-success', - className: 'notice-success is-dismissible', - message: __( 'Installation completed successfully.' ) + ' ' + $successButton, - } ); - - wp.a11y.speak( __( 'Installation completed successfully.' ) ); - - $document.trigger( 'tsfem-tsf-install-success', response ); - } - - /** - * Updates the UI appropriately after a failed TSF install. - * - * @since 2.5.0 - * @credit wp.updates.installImporterError - * - * @function - * @typedef {object} installTsfError - * @param {object} response Response from the server. - * @param {string} response.slug Slug of the plugin to be installed. - * @param {string=} response.pluginName Optional. Name of the plugin to be installed. - * @param {string} response.errorCode Error code for the error that occurred. - * @param {string} response.errorMessage The error that occurred. - */ - const installTsfError = response => { - let errorMessage = sprintf( - /* translators: %s: Error string for a failed installation. */ - __( 'Installation failed: %s' ), - response.errorMessage - ), - $installLink = $( '[data-slug="' + response.slug + '"]' ), - pluginName = $installLink.data( 'name' ); - - if ( ! wp.updates.isValidResponse( response, 'install' ) ) { - return; - } - - if ( wp.updates.maybeHandleCredentialError( response, 'install-plugin' ) ) { - return; - } - - wp.updates.addAdminNotice( { - id: response.errorCode, - className: 'notice-error is-dismissible', - message: errorMessage - } ); - - // This will insert a non-AJAX-enabled (direct) fallback link. - $installLink - .removeClass( 'updating-message' ) - .attr( - 'aria-label', - sprintf( - /* translators: %s: Plugin name. */ - _x( 'Install %s now', 'plugin' ), - pluginName - ) - ) - .text( __( 'Install Now' ) ); - - wp.a11y.speak( errorMessage, 'assertive' ); - - $document.trigger( 'tsfem-tsf-install-error', response ); - } - - /** - * Adds installation hooks on DOMContentLoaded. - * - * TODO rewrite as a standalone, being independent from WP's broken update.js? - * - * @since 2.5.0 - * - * @function - * @param {event} event - */ - const onReady = ( event ) => { - - if ( ! wp || ! wp.updates ) return; - - let prev_addCallbacks = wp.updates._addCallbacks; - - /** - * Hooks into the installation button, to prevent redirect. - * - * WordPress normally enforces a redirect when the actionable page uses index.php. - * Indicating that it's not a "valid" installation page. Which is odd, as - * it perfectly allows any other action. Plus it allows this action on any other page, - * but the admin "main === index.php" dashboard page, too. - * - * @source https://github.com/WordPress/WordPress/blob/4.9-branch/wp-admin/js/updates.js#L2395-L2415 - */ - $( '#plugin_install_from_iframe' ).on( 'click', function( event ) { - let target = window.parent === window ? null : window.parent, - install; - - //= Let the default handler take over. - if ( -1 === window.parent.location.pathname.indexOf( 'index.php' ) ) - return; - - //= Only enact when the slug matches. - if ( $( this ).data( 'slug' ) !== tsfinstallerL10n.slug ) - return; - - $.support.postMessage = !! window.postMessage; - - if ( false === $.support.postMessage || null === target ) { - return; - } - - event.preventDefault(); - - install = { - action: 'install-plugin', - data: { - slug: $( this ).data( 'slug' ) - } - }; - - target.postMessage( JSON.stringify( install ), window.location.origin ); - } ); - - //= Direct attach as WP is using preventDefault() when capturing. - $( '#tsfem-tsf-tb' ).on( 'click', () => { - let canReset = false; - - /** - * Overwrite installer callback catcher. - * This is duplicated code, basically, to revert whatever WP's installer was doing. - */ - wp.updates._addCallbacks = ( data, action ) => { - if ( 'install-plugin' === action && tsfinstallerL10n.slug === data.slug ) { - data.success = installTsfSuccess; - data.error = installTsfError; - - let $button = $( '[data-slug="' + data.slug + '"]' ); - $button - .addClass( 'updating-message' ) - .attr( - 'aria-label', - sprintf( - /* translators: %s: Plugin name and version. */ - _x( 'Installing %s...', 'plugin' ), - $button.data( 'name' ) - ) - ) - .text( __( 'Installing...' ) ); - - canReset = true; - } - - return data; - } - - // Thread lightly: Pure magic below. - $( window ).on( 'message', event => { - let message; - try { - message = JSON.parse( event.originalEvent.data ); - } catch ( e ) { - return; - } - if ( ! message || 'undefined' === typeof message.action ) { - return; - } - if ( message.action === 'install-plugin' ) { - //= Fail safe. - canReset = false; - } else { - //= Fail secure. - canReset = true; - } - } ); - let resetTicker, cbs; - const resetCb = () => { - wp.updates._addCallbacks = prev_addCallbacks; - clearInterval( resetTicker ); - $document.off( cbs, resetCb ); - } - const checkReset = () => { - canReset && resetCb(); - } - const prepareReset = () => { - resetTicker = setInterval( checkReset, 100 ); - setTimeout( resetCb, 750 ); - } - cbs = 'wp-plugin-installing wp-plugin-install-error wp-plugin-install-success'; - //= Fail secure. - $( 'body' ).one( 'thickbox:removed', prepareReset ); - $document.one( cbs, resetCb ); - } ); - - $document.on( 'click', '#tsfem-tsf-install', event => { - let $button = $( event.target ); - - if ( $button.hasClass( 'activate-now' ) ) { - //? Follow link, activating the plugin. - return; - } - event.preventDefault(); - - if ( $button.hasClass( 'updating-message' ) || $button.hasClass( 'button-disabled' ) ) { - return; - } - - if ( $button.html() !== __( 'Installing...' ) ) { - $button.data( 'originaltext', $button.html() ); - } - - $button - .addClass( 'updating-message' ) - .attr( - 'aria-label', - sprintf( - /* translators: %s: Plugin name and version. */ - _x( 'Installing %s...', 'plugin' ), - $button.data( 'name' ) - ) - ) - .text( __( 'Installing...' ) ); - - if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.ajaxLocked ) { - wp.updates.requestFilesystemCredentials( event ); - - $document.on( 'credential-modal-cancel', () => { - $button - .removeClass( 'updating-message' ) - .attr( - 'aria-label', - sprintf( - /* translators: %s: Plugin name. */ - _x( 'Install %s now', 'plugin' ), - pluginName - ) - ) - .text( __( 'Install Now' ) ); - - wp.a11y.speak( __( 'Update canceled.' ) ); - } ); - } - - wp.updates.installPlugin( { - slug: $button.data( 'slug' ), - pagenow: pagenow, - success: installTsfSuccess, - error: installTsfError - } ); - } ); - } - - return { - /** - * Runs this script on DOMContentLoaded when WordPress Shiny Updates is - * available. - * - * @since 2.5.0 - * - * @function - */ - load: () => { - $( onReady ); - } - }; - }( jQuery, window.wp ); - window.tsfinstaller.load(); + const installTsfSuccess = response => { + + let $button = $( '[data-slug="' + response.slug + '"]' ); + + // if ( ! response.activateUrl ) { + // $button.remove(); + // } else { + $button + .removeClass( 'install-now installed button-disabled updating-message' ) + .addClass( 'activate-now' ) + .attr( { + href: response.activateUrl + '&from=plugins', + 'aria-label': sprintf( + /* translators: %s: Plugin name. */ + 'plugins-network' === pagenow ? _x( 'Network Activate %s', 'plugin' ) : _x( 'Activate %s', 'plugin' ), + response.pluginName + ) + } ) + .text( 'plugins-network' === pagenow ? __( 'Network Activate' ) : __( 'Activate' ) ); + + let $successButton = $button.clone()[0].outerHTML; + + wp.updates.addAdminNotice( { + id: 'install-success', + className: 'notice-success is-dismissible', + message: __( 'Installation completed successfully.' ) + ' ' + $successButton, + } ); + + wp.a11y.speak( __( 'Installation completed successfully.' ) ); + + $document.trigger( 'tsfem-tsf-install-success', response ); + } + + /** + * Updates the UI appropriately after a failed TSF install. + * + * @since 2.5.0 + * @credit wp.updates.installImporterError + * + * @function + * @typedef {object} installTsfError + * @param {object} response Response from the server. + * @param {string} response.slug Slug of the plugin to be installed. + * @param {string=} response.pluginName Optional. Name of the plugin to be installed. + * @param {string} response.errorCode Error code for the error that occurred. + * @param {string} response.errorMessage The error that occurred. + */ + const installTsfError = response => { + let errorMessage = sprintf( + /* translators: %s: Error string for a failed installation. */ + __( 'Installation failed: %s' ), + response.errorMessage + ), + $installLink = $( '[data-slug="' + response.slug + '"]' ), + pluginName = $installLink.data( 'name' ); + + if ( ! wp.updates.isValidResponse( response, 'install' ) ) { + return; + } + + if ( wp.updates.maybeHandleCredentialError( response, 'install-plugin' ) ) { + return; + } + + wp.updates.addAdminNotice( { + id: response.errorCode, + className: 'notice-error is-dismissible', + message: errorMessage + } ); + + // This will insert a non-AJAX-enabled (direct) fallback link. + $installLink + .removeClass( 'updating-message' ) + .attr( + 'aria-label', + sprintf( + /* translators: %s: Plugin name. */ + _x( 'Install %s now', 'plugin' ), + pluginName + ) + ) + .text( __( 'Install Now' ) ); + + wp.a11y.speak( errorMessage, 'assertive' ); + + $document.trigger( 'tsfem-tsf-install-error', response ); + } + + /** + * Adds installation hooks on DOMContentLoaded. + * + * TODO rewrite as a standalone, being independent from WP's broken update.js? + * + * @since 2.5.0 + * + * @function + * @param {event} event + */ + const onReady = ( event ) => { + + if ( ! wp || ! wp.updates ) return; + + let prev_addCallbacks = wp.updates._addCallbacks; + + /** + * Hooks into the installation button, to prevent redirect. + * + * WordPress normally enforces a redirect when the actionable page uses index.php. + * Indicating that it's not a "valid" installation page. Which is odd, as + * it perfectly allows any other action. Plus it allows this action on any other page, + * but the admin "main === index.php" dashboard page, too. + * + * @source https://github.com/WordPress/WordPress/blob/4.9-branch/wp-admin/js/updates.js#L2395-L2415 + */ + $( '#plugin_install_from_iframe' ).on( 'click', function( event ) { + let target = window.parent === window ? null : window.parent, + install; + + //= Let the default handler take over. + if ( -1 === window.parent.location.pathname.indexOf( 'index.php' ) ) + return; + + //= Only enact when the slug matches. + if ( $( this ).data( 'slug' ) !== tsfinstallerL10n.slug ) + return; + + $.support.postMessage = !! window.postMessage; + + if ( false === $.support.postMessage || null === target ) { + return; + } + + event.preventDefault(); + + install = { + action: 'install-plugin', + data: { + slug: $( this ).data( 'slug' ) + } + }; + + target.postMessage( JSON.stringify( install ), window.location.origin ); + } ); + + //= Direct attach as WP is using preventDefault() when capturing. + $( '#tsfem-tsf-tb' ).on( 'click', () => { + let canReset = false; + + /** + * Overwrite installer callback catcher. + * This is duplicated code, basically, to revert whatever WP's installer was doing. + */ + wp.updates._addCallbacks = ( data, action ) => { + if ( 'install-plugin' === action && tsfinstallerL10n.slug === data.slug ) { + data.success = installTsfSuccess; + data.error = installTsfError; + + let $button = $( '[data-slug="' + data.slug + '"]' ); + $button + .addClass( 'updating-message' ) + .attr( + 'aria-label', + sprintf( + /* translators: %s: Plugin name and version. */ + _x( 'Installing %s...', 'plugin' ), + $button.data( 'name' ) + ) + ) + .text( __( 'Installing...' ) ); + + canReset = true; + } + + return data; + } + + // Thread lightly: Pure magic below. + $( window ).on( 'message', event => { + let message; + try { + message = JSON.parse( event.originalEvent.data ); + } catch ( e ) { + return; + } + if ( ! message || 'undefined' === typeof message.action ) { + return; + } + if ( message.action === 'install-plugin' ) { + //= Fail safe. + canReset = false; + } else { + //= Fail secure. + canReset = true; + } + } ); + let resetTicker, cbs; + const resetCb = () => { + wp.updates._addCallbacks = prev_addCallbacks; + clearInterval( resetTicker ); + $document.off( cbs, resetCb ); + } + const checkReset = () => { + canReset && resetCb(); + } + const prepareReset = () => { + resetTicker = setInterval( checkReset, 100 ); + setTimeout( resetCb, 750 ); + } + cbs = 'wp-plugin-installing wp-plugin-install-error wp-plugin-install-success'; + //= Fail secure. + $( 'body' ).one( 'thickbox:removed', prepareReset ); + $document.one( cbs, resetCb ); + } ); + + $document.on( 'click', '#tsfem-tsf-install', event => { + let $button = $( event.target ); + + if ( $button.hasClass( 'activate-now' ) ) { + //? Follow link, activating the plugin. + return; + } + event.preventDefault(); + + if ( $button.hasClass( 'updating-message' ) || $button.hasClass( 'button-disabled' ) ) { + return; + } + + if ( $button.html() !== __( 'Installing...' ) ) { + $button.data( 'originaltext', $button.html() ); + } + + $button + .addClass( 'updating-message' ) + .attr( + 'aria-label', + sprintf( + /* translators: %s: Plugin name and version. */ + _x( 'Installing %s...', 'plugin' ), + $button.data( 'name' ) + ) + ) + .text( __( 'Installing...' ) ); + + if ( wp.updates.shouldRequestFilesystemCredentials && ! wp.updates.ajaxLocked ) { + wp.updates.requestFilesystemCredentials( event ); + + $document.on( 'credential-modal-cancel', () => { + $button + .removeClass( 'updating-message' ) + .attr( + 'aria-label', + sprintf( + /* translators: %s: Plugin name. */ + _x( 'Install %s now', 'plugin' ), + pluginName + ) + ) + .text( __( 'Install Now' ) ); + + wp.a11y.speak( __( 'Update canceled.' ) ); + } ); + } + + wp.updates.installPlugin( { + slug: $button.data( 'slug' ), + pagenow: pagenow, + success: installTsfSuccess, + error: installTsfError + } ); + } ); + } + + return { + /** + * Runs this script on DOMContentLoaded when WordPress Shiny Updates is + * available. + * + * @since 2.5.0 + * + * @function + */ + load: () => { + $( onReady ); + } + }; +}( jQuery, window.wp ); +window.tsfinstaller.load(); diff --git a/readme.txt b/readme.txt index 8bf44f5e..7c16116f 100644 --- a/readme.txt +++ b/readme.txt @@ -41,6 +41,59 @@ Please refer to [the installation instructions on our website](https://kb.theseo == Changelog == += 2.6.0 = + +* Touched up the interface, it's now more compact and easier on your eyes. +* Now relies on The SEO Framework's JavaScript availability test, instead of WordPress's, making unresponsive interfaces a thing of the past. + +* TODO Add index.php files to extension top-folders +* TODO require TSF 4.2+ +* TODO Use tsf() insteadof the_seo_framework() +* TODO use API functions of TSF (memo, has_run, isset()?..: et al.) +* TODO refactor coalesce_var to PHP 7.0+. +* TODO Start requiring PHP 7.2+ + * We'd love to use 7.4+ but 13% of our users are on 7.3 or lower (measured 2022/05/07). + * Otto said we'd have to learn from because it's leading. It's only off by 40%. + * Let's henceforth rely on our data. TODO remeasure, compare change. +* Moved TSF installation hanlder to a different file. +* Improved letter spacing from logos. +TODO remove png files, all browsers support svg now. + * TSF site already dropped support. + * Use `` like on TSF site for improved painting performance? +TODO move get_view() to trait, using prescribed base URL. +TODO instead of "defined( 'TSF_EXTENSION_MANAGER_PRESENT' ) and $_class = TSF_Extension_Manager\Extension\Transport\get_active_class() and ", try a secret (for all extensions). +TODO 'a' . $b -> "a$b" (PHP) +TODO 'a' + b -> `a${b}` (JS) (.e.g. '.' + ...) +TODO implement views trait. +TODO remove typehinting +TODO introduced tsfem() + * Migrate tsf_extension_manager() calls to tsfem(). +TODO remove trends pane... we planned to add our blog items there, but that never came to fruition. + -> We kept it there to visually balance the page. + -> Should we let the extensions wrap side-by-side instead? flex base 400px, stretch to fit? + -> Copy from tsf.fyi/e? +TODO -> + also -> +TODO POT file. (also update related github) +TODO -> +TODO make Traits autoloadable? -> The Construct_* part is annoying -> \Construct\?. Extension_* needs to become \Extension\ + -> `use \TSF_Extension_Manager\Traits\{Construct_Master_Once_Interface,Time,UI,Extension_Options,Extension_Forms,Error};` +TODO //= //? //* -> // +TODO de-jQueryfy? + -> Especially form.js +TODO function(){} => ()=>{} +TODO coalesce_var() => ?? + +TODO add grid display to importer options... + +TODO convert post metadata from double-serialized to single-serialized (with perhaps WP interfering?) + -> We took control because WP was causing issues (which?), are those issues resolved? +TODO convert all serialized objects to JSON for future parsing, such as requesting updates. This improves security on OUR servers, not the users. + -> Increment API version number. + +TODO fix notice bounce (reintroduced for we ditched the stagnant :empty selector) +TODO fewer jQuery animations, more CSS animations. + = 2.5.3 = **Release date:** @@ -62,6 +115,8 @@ Please refer to [the installation instructions on our website](https://kb.theseo **Detailed log** +* **Improved:** WordPress update nags no longer cause a shadow effect on Extension Manager pages. + View the [detailed v2.5.2 changelog](https://tsf.fyi/p/3890). = 2.5.2 = @@ -110,7 +165,7 @@ View the [detailed v2.5.1 changelog](https://tsf.fyi/p/3779). In this update, we added quick-and bulk-edit support, improved browser scripting performance, and improved various extensions. Most notably, Honeypot catches modernized spammers, Articles can now be disabled per-post, and Focus's API is vastly expanded. -**Important release notes:** +**Transportant release notes:** * Henceforth, Extension Manager requires **The SEO Framework v4.1.2 or higher**. * This release brings support for WordPress v5.6's updated interface. Sorry for the delay! diff --git a/the-seo-framework-extension-manager.php b/the-seo-framework-extension-manager.php index cc27cc8e..9dbcfd8f 100644 --- a/the-seo-framework-extension-manager.php +++ b/the-seo-framework-extension-manager.php @@ -3,7 +3,7 @@ * Plugin Name: The SEO Framework - Extension Manager * Plugin URI: https://theseoframework.com/extension-manager/ * Description: Add more powerful SEO features to The SEO Framework. Right from your WordPress dashboard. - * Version: 2.5.3 + * Version: 2.6.0-dev-1 * Author: The SEO Framework Team * Author URI: https://theseoframework.com/ * License: GPLv3 @@ -92,5 +92,8 @@ if ( is_admin() || wp_doing_cron() ) require TSF_EXTENSION_MANAGER_BOOTSTRAP_PATH . 'update.php'; +if ( is_admin() ) + require TSF_EXTENSION_MANAGER_BOOTSTRAP_PATH . 'install.php'; + // Load plugin files. require TSF_EXTENSION_MANAGER_BOOTSTRAP_PATH . 'load.php'; diff --git a/views/forms/free.php b/views/forms/free.php index 18faa802..0db31b3e 100644 --- a/views/forms/free.php +++ b/views/forms/free.php @@ -12,7 +12,7 @@ $class_submit = isset( $classes ) && count( $classes ) > 0 ? implode( ' ', $classes ) : 'tsfem-button tsfem-button-secondary'; ?> -
+ _nonce_action_field( $this->request_name['activate-free'] ); ?> nonce_action['activate-free'], $this->nonce_name ); ?> diff --git a/views/forms/get.php b/views/forms/get.php index 9b9456aa..dca3694a 100644 --- a/views/forms/get.php +++ b/views/forms/get.php @@ -17,10 +17,10 @@ exit; // UNSAFE (and unused...) SCRIPT! Needs nonce. ?> - - - - - + + + + +
0 ? implode( ' ', $classes ) : 'tsfem-button-primary'; ?> -
- - + + + _nonce_action_field( $this->request_name['activate-key'] ); ?> nonce_action['activate-key'], $this->nonce_name ); ?> - +
-
+

-
+
_form_wrap( 'start', tsf_extension_manager()->get_admin_page_url( static::$settings_page_slug ), true ); diff --git a/views/layout/pages/meta.php b/views/layout/general/meta.php similarity index 100% rename from views/layout/pages/meta.php rename to views/layout/general/meta.php